UNPKG

@engie-group/fluid-design-system-react

Version:

Fluid Design System React

123 lines (120 loc) 7.67 kB
import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import React__default, { useMemo, useRef, useCallback } from 'react'; import { useStateControl, useInert } from '../../../utils/hook.js'; import { labelFromChildren } from '../../../utils/label-from-children.js'; import { Utils } from '../../../utils/util.js'; import { NJFormItem } from '../../form-item/NJFormItem.js'; import { NJMenuAnchor } from '../../menu/anchor/NJMenuAnchor.js'; import { NJMenuDropdown } from '../../menu/dropdown/NJMenuDropdown.js'; import { NJMenuGroup } from '../../menu/group/NJMenuGroup.js'; import '../../menu/item/NJMenuItem.js'; import { NJMenuRoot } from '../../menu/root/NJMenuRoot.js'; import { NJMenuSelection } from '../../menu/selection/NJMenuSelection.js'; import '../../menu/NJMenuContext.js'; import '../../menu/NJMenuItemContext.js'; import '../../menu/NJMenuSelectionContext.js'; import { NJTag } from '../../tag/NJTag.js'; import { NJMultiSelectTag } from '../tag/NJMultiSelectTag.js'; const NJMultiSelectRoot = React__default.forwardRef((props, forwardedRef) => { const { id, name, placeholder, form, isRequired, isDisabled, iconName, onChange, children, label, listNavigationLabel, tagColor = 'grey', size = 'md', displaySelectedItems = true, maxTagsToDisplay, selectedText = 'selected', tagCloseLabel = 'Deselect', tagResetSelectionLabel = 'Deselect all', className, value: controlledValue, initialValue, ...htmlProps } = props; const items = React__default.Children.map(children, (child, index) => { return { label: React__default.isValidElement(child) ? (child.props.label ?? labelFromChildren(child)) : '', value: React__default.isValidElement(child) && child.props.value ? child.props.value : '', index }; }); const optionsByIndex = new Map(items.map((item) => [item.index, item])); const [value, setValue] = useStateControl(initialValue, controlledValue); const [isOpen, setIsOpen] = React__default.useState(false); const { selectedValues, selectedIndexes, selectedLabels } = useMemo(() => (value ?? []).reduce((acc, option) => { const item = items?.find((item) => item.value === option); if (item) { return { ...acc, selectedIndexes: [...acc.selectedIndexes, item.index], selectedLabels: [...acc.selectedLabels, item.label], selectedValues: [...acc.selectedValues, item.value] }; } return acc; }, { selectedIndexes: [], selectedLabels: [], selectedValues: [] }), [value, items]); const rootEl = useRef(null); const buttonEl = useRef(null); const tagListWrapperEl = useRef(null); useInert(rootEl, isDisabled); const handleChange = useCallback((selectedIndexes) => { const newValue = selectedIndexes .map((index) => optionsByIndex.get(index)?.value) .filter(Boolean); setValue(newValue); onChange?.(newValue); }, [onChange, optionsByIndex]); const inputClass = Utils.classNames('nj-form-item--select', 'nj-form-item--custom-list', 'nj-form-item--multi-select', { ['nj-form-item--open']: isOpen }, className); const getTagScale = () => { switch (size) { case 'xl': return 'md'; case 'sm': return 'xs'; default: return 'sm'; } }; const tagScale = getTagScale(); function resetSelection() { handleChange([]); buttonEl?.current?.focus(); } const moveFocusToNextTag = (e) => { // When clicking with a mouse e.detail counts the number of clicks, however, when using keyboard it is always 0 const isEventTriggeredWithKeyboard = e?.detail === 0; if (!isEventTriggeredWithKeyboard) { return; } const tags = Array.from(tagListWrapperEl?.current?.children ?? []); const indexOfTagFocused = tags.findIndex((child) => child.querySelector('button') === e.currentTarget); const nextTagToFocus = tags[(indexOfTagFocused + 1) % tags.length]?.querySelector('button'); if (nextTagToFocus && tags.length > 1) { nextTagToFocus.focus(); } else { buttonEl?.current?.focus(); } }; const numberOfSelectedItems = selectedValues.length; const indexesToDisplay = selectedIndexes.slice(0, maxTagsToDisplay ?? numberOfSelectedItems); const additionalTagNumber = maxTagsToDisplay ? numberOfSelectedItems - maxTagsToDisplay : 0; const additionalTagLabel = `+ ${additionalTagNumber}`; const selectedTags = indexesToDisplay.map((selectedIndex, index) => { const label = selectedLabels[index] ?? ''; return (jsx(NJMultiSelectTag, { index: selectedIndex, variant: tagColor, scale: tagScale, label: label, closeAriaLabel: `${tagCloseLabel} ${label}`, onClose: (e) => { moveFocusToNextTag(e); } }, selectedIndex)); }); const getTagsToDisplayWhenWeShouldNotDisplaySelectedItems = () => { if (numberOfSelectedItems === 0) { return null; } const selectedIndex = selectedIndexes[0]; if (numberOfSelectedItems === 1) { const label = selectedLabels[0] ?? ''; return (jsx(NJMultiSelectTag, { index: selectedIndex, variant: tagColor, scale: tagScale, label: label, closeAriaLabel: `${tagCloseLabel} ${label}`, onClose: (e) => { moveFocusToNextTag(e); } })); } else { return (jsx(NJTag, { variant: tagColor, scale: tagScale, label: `${numberOfSelectedItems} ${selectedText}`, closeAriaLabel: tagResetSelectionLabel, onClose: () => { resetSelection(); } })); } }; const tagsToDisplay = displaySelectedItems ? (jsxs(Fragment, { children: [selectedTags, additionalTagNumber > 0 && (jsx(NJTag, { variant: tagColor, scale: tagScale, label: additionalTagLabel }))] })) : (getTagsToDisplayWhenWeShouldNotDisplaySelectedItems()); return (jsx(NJMenuRoot, { floatingRole: "select", onOpen: (isOpen) => setIsOpen(isOpen), children: jsx(NJMenuSelection, { multiselection: true, closeOnSelect: false, selectedIndexes: selectedIndexes, onSelection: handleChange, children: jsxs(NJFormItem, { label: label, isMultiline: false, id: id, onChange: undefined, value: undefined, isSelect: true, className: inputClass, iconName: iconName ?? 'keyboard_arrow_down', iconClassName: "nj-form-item__icon", labelClassName: "nj-form-item__label", size: size, ref: rootEl, children: [jsx("input", { "data-child-name": "inputField", type: "text", id: id, ref: forwardedRef, name: name, value: selectedValues.join(', '), disabled: isDisabled, required: isRequired, "aria-hidden": true, placeholder: placeholder || ' ', form: form, tabIndex: -1, className: "nj-form-item__field", readOnly: true }), jsxs("div", { "data-child-name": "additionalContent", children: [jsx("p", { id: `${id}-instructions`, hidden: true, children: listNavigationLabel }), jsx("div", { ref: tagListWrapperEl, className: "nj-form-item__selected-tags", children: numberOfSelectedItems > 0 && tagsToDisplay }), jsx(NJMenuAnchor, { children: jsx("button", { ...htmlProps, type: "button", "aria-label": label, className: "nj-form-item__custom-list-button", ref: buttonEl, "aria-describedby": `${id}-subscript ${id}-instructions` }) }), jsx(NJMenuDropdown, { scrollable: true, children: jsx(NJMenuGroup, { children: children }) })] })] }) }) })); }); NJMultiSelectRoot.displayName = 'NJMultiSelectRoot'; export { NJMultiSelectRoot };