UNPKG

@mskcc/carbon-react

Version:

Carbon react components for the MSKCC DSM

425 lines (414 loc) 15.6 kB
/** * MSKCC 2021, 2024 */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js'); var React = require('react'); var Downshift = require('downshift'); var cx = require('classnames'); var PropTypes = require('prop-types'); var iconsReact = require('@carbon/icons-react'); var index = require('../ListBox/index.js'); var mergeRefs = require('../../tools/mergeRefs.js'); var deprecate = require('../../prop-types/deprecate.js'); var usePrefix = require('../../internal/usePrefix.js'); require('../FluidForm/FluidForm.js'); var FormContext = require('../FluidForm/FormContext.js'); var setupGetInstanceId = require('../../tools/setupGetInstanceId.js'); var ListBoxPropTypes = require('../ListBox/ListBoxPropTypes.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var cx__default = /*#__PURE__*/_interopDefaultLegacy(cx); var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes); const getInstanceId = setupGetInstanceId["default"](); const { MenuBlur, MenuKeyDownArrowDown, MenuKeyDownArrowUp, MenuKeyDownEscape, ToggleButtonClick } = Downshift.useSelect.stateChangeTypes; const defaultItemToString = item => { if (typeof item === 'string') { return item; } if (typeof item === 'number') { return `${item}`; } if (item !== null && typeof item === 'object' && 'label' in item && typeof item['label'] === 'string') { return item['label']; } return ''; }; const Dropdown = /*#__PURE__*/React__default["default"].forwardRef((_ref, ref) => { let { className: containerClassName, disabled, direction, items, label, ['aria-label']: ariaLabel, ariaLabel: deprecatedAriaLabel, itemToString = defaultItemToString, itemToElement, renderSelectedItem, type, size, onChange, id, titleText, hideLabel, helperText, translateWithId, light, invalid, invalidText, warn, warnText, initialSelectedItem, selectedItem: controlledSelectedItem, downshiftProps, readOnly, ...other } = _ref; const prefix = usePrefix.usePrefix(); const [highlightedIndex, setHighlightedIndex] = React.useState(); const { isFluid } = React.useContext(FormContext.FormContext); const selectProps = { ...downshiftProps, items, itemToString, highlightedIndex, initialSelectedItem, onSelectedItemChange, onStateChange }; const { current: dropdownInstanceId } = React.useRef(getInstanceId()); function onStateChange(changes) { const { type } = changes; switch (type) { case MenuKeyDownArrowDown: case MenuKeyDownArrowUp: setHighlightedIndex(changes.highlightedIndex); break; case MenuBlur: case MenuKeyDownEscape: setHighlightedIndex(changes.highlightedIndex); break; case ToggleButtonClick: setHighlightedIndex(changes.highlightedIndex); break; } } // only set selectedItem if the prop is defined. Setting if it is undefined // will overwrite default selected items from useSelect if (controlledSelectedItem !== undefined) { selectProps.selectedItem = controlledSelectedItem; } const { isOpen, getToggleButtonProps, getLabelProps, getMenuProps, getItemProps, selectedItem } = Downshift.useSelect(selectProps); const inline = type === 'inline'; const showWarning = !invalid && warn; const [isFocused, setIsFocused] = React.useState(false); const className = cx__default["default"](`${prefix}--dropdown`, { [`${prefix}--dropdown--invalid`]: invalid, [`${prefix}--dropdown--warning`]: showWarning, [`${prefix}--dropdown--open`]: isOpen, [`${prefix}--dropdown--inline`]: inline, [`${prefix}--dropdown--disabled`]: disabled, [`${prefix}--dropdown--light`]: light, [`${prefix}--dropdown--readonly`]: readOnly, [`${prefix}--dropdown--${size}`]: size, [`${prefix}--list-box--up`]: direction === 'top' }); const titleClasses = cx__default["default"](`${prefix}--label`, { [`${prefix}--label--disabled`]: disabled, [`${prefix}--visually-hidden`]: hideLabel }); const helperClasses = cx__default["default"](`${prefix}--form__helper-text`, { [`${prefix}--form__helper-text--disabled`]: disabled }); const wrapperClasses = cx__default["default"](`${prefix}--dropdown__wrapper`, `${prefix}--list-box__wrapper`, containerClassName, { [`${prefix}--dropdown__wrapper--inline`]: inline, [`${prefix}--list-box__wrapper--inline`]: inline, [`${prefix}--dropdown__wrapper--inline--invalid`]: inline && invalid, [`${prefix}--list-box__wrapper--inline--invalid`]: inline && invalid, [`${prefix}--list-box__wrapper--fluid--invalid`]: isFluid && invalid, [`${prefix}--list-box__wrapper--fluid--focus`]: isFluid && isFocused && !isOpen }); const helperId = !helperText ? undefined : `dropdown-helper-text-${dropdownInstanceId}`; // needs to be Capitalized for react to render it correctly const ItemToElement = itemToElement; const toggleButtonProps = getToggleButtonProps(); const helper = helperText && !isFluid ? /*#__PURE__*/React__default["default"].createElement("div", { id: helperId, className: helperClasses }, helperText) : null; function onSelectedItemChange(_ref2) { let { selectedItem } = _ref2; setIsFocused(false); if (onChange) { onChange({ selectedItem: selectedItem ?? null }); } } const menuItemOptionRefs = React.useRef(items.map(_ => /*#__PURE__*/React__default["default"].createRef())); const handleFocus = evt => { setIsFocused(evt.type === 'focus' ? true : false); }; const mergedRef = mergeRefs["default"](toggleButtonProps.ref, ref); const [currTimer, setCurrTimer] = React.useState(); // eslint-disable-next-line prefer-const let [isTyping, setIsTyping] = React.useState(false); const readOnlyEventHandlers = readOnly ? { onClick: evt => { // NOTE: does not prevent click evt.preventDefault(); // focus on the element as per readonly input behavior if (mergedRef.current !== undefined) { mergedRef.current.focus(); } }, onKeyDown: evt => { const selectAccessKeys = ['ArrowDown', 'ArrowUp', ' ', 'Enter']; // This prevents the select from opening for the above keys if (selectAccessKeys.includes(evt.key)) { evt.preventDefault(); } } } : { onKeyDown: evt => { if (evt.code !== 'Space' || !['ArrowDown', 'ArrowUp', ' ', 'Enter'].includes(evt.key)) { setIsTyping(true); } if (isTyping && evt.code === 'Space' || !['ArrowDown', 'ArrowUp', ' ', 'Enter'].includes(evt.key)) { if (evt.code === 'Space') { evt.preventDefault(); return; } if (currTimer) { clearTimeout(currTimer); } setCurrTimer(setTimeout(() => { setIsTyping(false); }, 3000)); } toggleButtonProps.onKeyDown(evt); } }; const menuProps = getMenuProps(); return /*#__PURE__*/React__default["default"].createElement("div", _rollupPluginBabelHelpers["extends"]({ className: wrapperClasses }, other), titleText && /*#__PURE__*/React__default["default"].createElement("label", _rollupPluginBabelHelpers["extends"]({ className: titleClasses }, getLabelProps()), titleText), /*#__PURE__*/React__default["default"].createElement(index["default"], { onFocus: handleFocus, onBlur: handleFocus, "aria-label": deprecatedAriaLabel || ariaLabel, size: size, className: className, invalid: invalid, invalidText: invalidText, warn: warn, warnText: warnText, light: light, isOpen: isOpen, id: id }, invalid && /*#__PURE__*/React__default["default"].createElement(iconsReact.WarningFilled, { className: `${prefix}--list-box__invalid-icon` }), showWarning && /*#__PURE__*/React__default["default"].createElement(iconsReact.WarningAltFilled, { className: `${prefix}--list-box__invalid-icon ${prefix}--list-box__invalid-icon--warning` }), /*#__PURE__*/React__default["default"].createElement("button", _rollupPluginBabelHelpers["extends"]({ type: "button" // aria-expanded is already being passed through {...toggleButtonProps} , role: "combobox" // eslint-disable-line jsx-a11y/role-has-required-aria-props , "aria-owns": getMenuProps().id, "aria-controls": getMenuProps().id, className: `${prefix}--list-box__field`, disabled: disabled, "aria-disabled": readOnly ? true : undefined // aria-disabled to remain focusable , "aria-describedby": !inline && !invalid && !warn && helper ? helperId : undefined, title: selectedItem && itemToString !== undefined ? itemToString(selectedItem) : label }, toggleButtonProps, readOnlyEventHandlers, { ref: mergedRef }), /*#__PURE__*/React__default["default"].createElement("span", { className: `${prefix}--list-box__label` }, selectedItem ? renderSelectedItem ? renderSelectedItem(selectedItem) : itemToString(selectedItem) : label), /*#__PURE__*/React__default["default"].createElement(index["default"].MenuIcon, { isOpen: isOpen, translateWithId: translateWithId })), /*#__PURE__*/React__default["default"].createElement(index["default"].Menu, menuProps, isOpen && items.map((item, index$1) => { const isObject = item !== null && typeof item === 'object'; const disabled = isObject && 'disabled' in item && item.disabled === true; const itemProps = getItemProps({ item, index: index$1, disabled }); const title = isObject && 'text' in item && itemToElement ? item.text : itemToString(item); return /*#__PURE__*/React__default["default"].createElement(index["default"].MenuItem, _rollupPluginBabelHelpers["extends"]({ key: itemProps.id, isActive: selectedItem === item, isHighlighted: highlightedIndex === index$1, title: title, ref: { menuItemOptionRef: menuItemOptionRefs.current[index$1] } }, itemProps), typeof item === 'object' && ItemToElement !== undefined && ItemToElement !== null ? /*#__PURE__*/React__default["default"].createElement(ItemToElement, _rollupPluginBabelHelpers["extends"]({ key: itemProps.id }, item)) : itemToString(item), selectedItem === item && /*#__PURE__*/React__default["default"].createElement(iconsReact.Checkmark, { className: `${prefix}--list-box__menu-item__selected-icon` })); }))), !inline && !invalid && !warn && helper); }); Dropdown.displayName = 'Dropdown'; Dropdown.propTypes = { /** * 'aria-label' of the ListBox component. * Specify a label to be read by screen readers on the container node */ ['aria-label']: PropTypes__default["default"].string, /** * Deprecated, please use `aria-label` instead. * Specify a label to be read by screen readers on the container note. */ ariaLabel: deprecate["default"](PropTypes__default["default"].string, 'This prop syntax has been deprecated. Please use the new `aria-label`.'), /** * Provide a custom className to be applied on the bx--dropdown node */ className: PropTypes__default["default"].string, /** * Specify the direction of the dropdown. Can be either top or bottom. */ direction: PropTypes__default["default"].oneOf(['top', 'bottom']), /** * Disable the control */ disabled: PropTypes__default["default"].bool, /** * Additional props passed to Downshift */ downshiftProps: PropTypes__default["default"].object, /** * Provide helper text that is used alongside the control label for * additional help */ helperText: PropTypes__default["default"].node, /** * Specify whether the title text should be hidden or not */ hideLabel: PropTypes__default["default"].bool, /** * Specify a custom `id` */ id: PropTypes__default["default"].string.isRequired, /** * Allow users to pass in an arbitrary item or a string (in case their items are an array of strings) * from their collection that are pre-selected */ initialSelectedItem: PropTypes__default["default"].oneOfType([PropTypes__default["default"].object, PropTypes__default["default"].string, PropTypes__default["default"].number]), /** * Specify if the currently selected value is invalid. */ invalid: PropTypes__default["default"].bool, /** * Message which is displayed if the value is invalid. */ invalidText: PropTypes__default["default"].node, /** * Function to render items as custom components instead of strings. * Defaults to null and is overridden by a getter */ itemToElement: PropTypes__default["default"].func, /** * Helper function passed to downshift that allows the library to render a * given item to a string label. By default, it extracts the `label` field * from a given item to serve as the item label in the list. */ itemToString: PropTypes__default["default"].func, /** * We try to stay as generic as possible here to allow individuals to pass * in a collection of whatever kind of data structure they prefer */ items: PropTypes__default["default"].array.isRequired, /** * Generic `label` that will be used as the textual representation of what * this field is for */ label: PropTypes__default["default"].node.isRequired, /** * `true` to use the light version. */ light: deprecate["default"](PropTypes__default["default"].bool, 'The `light` prop for `Dropdown` has ' + 'been deprecated in favor of the new `Layer` component. It will be removed in the next major release.'), /** * `onChange` is a utility for this controlled component to communicate to a * consuming component what kind of internal state changes are occurring. */ onChange: PropTypes__default["default"].func, /** * Whether or not the Dropdown is readonly */ readOnly: PropTypes__default["default"].bool, /** * An optional callback to render the currently selected item as a react element instead of only * as a string. */ renderSelectedItem: PropTypes__default["default"].func, /** * In the case you want to control the dropdown selection entirely. */ selectedItem: PropTypes__default["default"].oneOfType([PropTypes__default["default"].object, PropTypes__default["default"].string, PropTypes__default["default"].number]), /** * Specify the size of the ListBox. Currently supports either `sm`, `md` or `lg` as an option. */ size: ListBoxPropTypes.ListBoxSize, /** * Provide the title text that will be read by a screen reader when * visiting this control */ titleText: PropTypes__default["default"].node.isRequired, /** * Callback function for translating ListBoxMenuIcon SVG title */ translateWithId: PropTypes__default["default"].func, /** * The dropdown type, `default` or `inline` */ type: ListBoxPropTypes.ListBoxType, /** * Specify whether the control is currently in warning state */ warn: PropTypes__default["default"].bool, /** * Provide the text that is displayed when the control is in warning state */ warnText: PropTypes__default["default"].node }; Dropdown.defaultProps = { disabled: false, type: 'default', itemToString: defaultItemToString, itemToElement: null, titleText: '', helperText: '', direction: 'bottom' }; exports["default"] = Dropdown;