@engie-group/fluid-design-system-react
Version:
Fluid Design System React
123 lines (120 loc) • 7.67 kB
JavaScript
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 };