UNPKG

@boomerang-io/carbon-addons-boomerang-react

Version:
178 lines (175 loc) 11.2 kB
import React, { useState } from 'react'; import { TextInput, Button, Tag, DismissibleTag } from '@carbon/react'; import { Information, Add, Draggable } from '@carbon/react/icons'; import { useSensors, useSensor, PointerSensor, DndContext, closestCenter } from '@dnd-kit/core'; import { SortableContext, verticalListSortingStrategy, useSortable, arrayMove } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import cx from 'classnames'; import TooltipHover from '../TooltipHover/TooltipHover.js'; import { isAccessibleKeyDownEvent } from '../../tools/accessibility.js'; import { prefix } from '../../internal/settings.js'; /* IBM Confidential 694970X, 69497O0 © Copyright IBM Corp. 2022, 2024 */ function CreatableComponent({ buttonClassName = `${prefix}--bmrg-creatable__button`, buttonContent = "Add", buttonProps, createKeyValuePair = false, disabled, id, initialValues: externalInitialValues, invalid, invalidText, helperText, key, keyHelperText, keyLabel, keyLabelText, keyPlaceholder, label, labelText = "", max, nonDeletable = false, onKeyBlur, onValueBlur, onInputBlur, onChange, placeholder, reorderable, tagProps, tagType = "teal", textInputProps, tooltipClassName = `${prefix}--bmrg-creatable__tooltip`, tooltipContent, tooltipProps = { direction: "top" }, type = "text", valueHelperText, valueLabel, valueLabelText, valuePlaceholder, value: externalValues, values, trimInput = false, removeOnlyFirst = false, }) { const [keyValue, setKeyValue] = useState(""); const [value, setValue] = useState(""); const [input, setInput] = useState(""); const inputRef = React.useRef(null); const inputLabel = labelText || label; const inputKeyLabel = keyLabelText || keyLabel; const inputValueLabel = valueLabelText || valueLabel; const [createdItems, setCreatedItems] = useState([]); const createButtonClassName = cx(buttonClassName, { "--no-label": (!createKeyValuePair && !inputLabel && !tooltipContent) || (createKeyValuePair && !inputKeyLabel && !inputValueLabel), }); /** Add support for csv strings */ let finalValues; let finalExternalValues; let finalExternalInitialValues; if (typeof values === "string") { finalValues = values === "" ? [] : values.split(","); } else { finalValues = values; } if (typeof externalValues === "string") { finalExternalValues = externalValues === "" ? [] : externalValues.split(","); } else { finalExternalValues = externalValues; } if (typeof externalInitialValues === "string") { finalExternalInitialValues = externalInitialValues.split(","); } else { finalExternalInitialValues = externalInitialValues; } const [initialItems] = useState(finalValues || finalExternalValues ? finalValues || finalExternalValues : []); const tagItems = (finalValues || finalExternalValues ? finalValues || finalExternalValues : createdItems); // Externally controlled if values props exists const initialTagItems = finalExternalInitialValues || initialItems || []; // Externally controlled if initialValues props exists const existValue = (keyValue && value) || (trimInput ? input?.trim() : input); const hasBothHelperText = keyHelperText && valueHelperText; const hasBothLabelText = inputKeyLabel && inputValueLabel; const tagsToShow = finalExternalInitialValues ? [...initialTagItems, ...tagItems] : [...tagItems]; const disableInputs = disabled || (max && tagsToShow.length >= max && max > 0); // Check if key value pair has colon const keyInputHasColon = keyValue?.includes(":"); const valueInputHasColon = value?.includes(":"); const isKeyInputValid = invalid || keyInputHasColon; const isValueInputValid = invalid || valueInputHasColon; const keyInputInvalidText = keyInputHasColon ? '":" is not allowed' : invalidText; const valueInputInvalidText = valueInputHasColon ? '":" is not allowed' : invalidText; const isAddButtonDisabled = disabled || !existValue || keyInputHasColon || valueInputHasColon; const onInputChange = (e) => { setInput(e.target.value); }; const onKeyChange = (e) => { setKeyValue(e.target.value); }; const onValueChange = (e) => { setValue(e.target.value); }; const addValue = (e) => { e.preventDefault(); const items = [...tagItems]; if (createKeyValuePair && keyValue && value) { items.push(`${keyValue}:${value}`); setKeyValue(""); setValue(""); } else { if (input) { // Check if input is a JSON object const inputToTest = input.trim(); if (inputToTest.slice(0, 1) === "{" && inputToTest.slice(-1) === "}") { const inputJsonObject = JSON.parse(input); if (typeof inputJsonObject === "object" && inputJsonObject !== null) { for (const [key, values] of Object.entries(inputJsonObject)) { const valuesArray = [...values]; valuesArray.forEach((value) => items.push(`${key}:${value}`)); } } } else { items.push(input); } } setInput(""); } setCreatedItems(items); if (onChange) onChange(items); inputRef.current?.focus(); }; const removeValue = (value) => { let items; if (removeOnlyFirst) { items = [...tagItems]; const index = items.indexOf(value); if (index !== -1) { items.splice(index, 1); } } else { items = tagItems.filter((item) => item !== value); } setCreatedItems(items); if (onChange) onChange(items); }; const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 }, })); function handleDragEnd(event) { const { active, over } = event; if (over && active.id !== over.id) { const oldIndex = tagItems.indexOf(active.id); const newIndex = tagItems.indexOf(over.id); const reorderedItems = arrayMove(tagItems, oldIndex, newIndex); setCreatedItems(reorderedItems); if (onChange) { onChange(reorderedItems); } } } return (React.createElement("div", { key: key, className: `${prefix}--bmrg-creatable` }, React.createElement("div", { className: `${prefix}--bmrg-creatable__input` }, createKeyValuePair ? (React.createElement("div", { className: `${prefix}--bmrg-creatable__key-value-inputs` }, React.createElement("div", { style: { marginBottom: hasBothHelperText || keyHelperText ? "0" : valueHelperText ? "1.5rem" : "0rem", marginTop: hasBothLabelText || inputKeyLabel ? "0" : inputValueLabel ? "1.5rem" : "0.5rem", } }, React.createElement(TextInput, { disabled: disableInputs, id: `${id}-key`, invalid: isKeyInputValid, invalidText: keyInputInvalidText, helperText: keyHelperText, labelText: inputKeyLabel, onBlur: onKeyBlur, onChange: onKeyChange, placeholder: keyPlaceholder, ref: inputRef, type: type, value: keyValue, ...textInputProps })), React.createElement("span", { className: `${prefix}--bmrg-creatable__colon`, style: { marginTop: inputKeyLabel || inputValueLabel ? "2.25rem" : "1.25rem", } }, ":"), React.createElement("div", { style: { marginBottom: hasBothHelperText || valueHelperText ? "0" : keyHelperText ? "1.5rem" : "0rem", marginTop: hasBothLabelText || inputValueLabel ? "0" : inputKeyLabel ? "1.5rem" : "0.5rem", } }, React.createElement(TextInput, { disabled: disableInputs, id: `${id}-value`, invalid: isValueInputValid, invalidText: valueInputInvalidText, helperText: valueHelperText, labelText: inputValueLabel, onBlur: onValueBlur, onChange: onValueChange, placeholder: valuePlaceholder, type: type, value: value, ...textInputProps })))) : (React.createElement(TextInput, { disabled: disableInputs, id: id, invalid: invalid, invalidText: invalidText, helperText: helperText, labelText: React.createElement("div", { style: { display: "flex" } }, React.createElement("div", null, inputLabel), tooltipContent && (React.createElement("div", { className: tooltipClassName }, React.createElement(TooltipHover, { ...tooltipProps, tooltipText: tooltipContent }, React.createElement(Information, { size: 16, fill: "currentColor" }))))), onBlur: onInputBlur, onChange: onInputChange, placeholder: placeholder, ref: inputRef, type: type, value: input, ...textInputProps })), React.createElement(Button, { className: createButtonClassName, disabled: isAddButtonDisabled, onClick: addValue, iconDescription: "Add", renderIcon: Add, size: "md", type: "button", ...buttonProps }, buttonContent)), reorderable ? (React.createElement(DndContext, { sensors: disabled ? [] : sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd }, React.createElement(SortableContext, { items: tagsToShow, strategy: verticalListSortingStrategy }, React.createElement("div", { className: `${prefix}--bmrg-creatable__tags--reorderable` }, tagsToShow.map((item, index) => (React.createElement(ReorderableTag, { key: `${item}-${index}`, disabled: disabled, initialTagItems: initialTagItems, item: item, nonDeletable: nonDeletable, removeValue: removeValue, tagProps: tagProps, tagType: tagType }))))))) : (React.createElement("div", { className: `${prefix}--bmrg-creatable__tags` }, tagsToShow.map((item, index) => (React.createElement(Tag, { key: `${item}-${index}`, disabled: disabled, type: tagType, onClick: nonDeletable && initialTagItems.includes(item) ? undefined : () => removeValue(item), onKeyDown: nonDeletable && initialTagItems.includes(item) ? undefined : (e) => isAccessibleKeyDownEvent(e) && removeValue(item), filter: !nonDeletable || (nonDeletable && !initialTagItems.includes(item)), ...tagProps }, item))))))); } function ReorderableTag({ disabled, initialTagItems, item, nonDeletable, removeValue, tagProps, tagType, }) { const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: item }); const style = { transform: CSS.Transform.toString(transform), transition, }; return (React.createElement("div", { ref: setNodeRef, ...attributes, ...(!disabled ? listeners : {}), style: style }, React.createElement(DismissibleTag, { disabled: disabled, type: tagType, onClose: nonDeletable && initialTagItems.includes(item) ? undefined : () => removeValue(item), renderIcon: Draggable, title: "", text: item, ...tagProps }))); } export { CreatableComponent as default };