@helpscout/hsds-react
Version:
React component library for Help Scout's Design System
466 lines (383 loc) • 19.7 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.default = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _react = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _useDeepCompareEffect = _interopRequireDefault(require("use-deep-compare-effect"));
var _classnames = _interopRequireDefault(require("classnames"));
var _lodash = _interopRequireDefault(require("lodash.debounce"));
var _lodash2 = _interopRequireDefault(require("lodash.isnil"));
var _headless = _interopRequireDefault(require("@tippyjs/react/headless"));
var _DropList = require("./DropList.constants");
var _DropList2 = require("./DropList.config");
var _DropList3 = require("./DropList.utils");
var _DropList4 = require("./DropList.togglers");
var _Animate = _interopRequireDefault(require("../Animate"));
var _jsxRuntime = require("react/jsx-runtime");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function noop() {}
function DropListManager(_ref) {
var _ref$animateOptions = _ref.animateOptions,
animateOptions = _ref$animateOptions === void 0 ? {} : _ref$animateOptions,
_ref$autoSetComboboxA = _ref.autoSetComboboxAt,
autoSetComboboxAt = _ref$autoSetComboboxA === void 0 ? 0 : _ref$autoSetComboboxA,
_ref$clearOnSelect = _ref.clearOnSelect,
clearOnSelect = _ref$clearOnSelect === void 0 ? false : _ref$clearOnSelect,
_ref$closeOnClickOuts = _ref.closeOnClickOutside,
closeOnClickOutside = _ref$closeOnClickOuts === void 0 ? true : _ref$closeOnClickOuts,
_ref$closeOnSelection = _ref.closeOnSelection,
closeOnSelection = _ref$closeOnSelection === void 0 ? true : _ref$closeOnSelection,
_ref$customEmptyList = _ref.customEmptyList,
customEmptyList = _ref$customEmptyList === void 0 ? null : _ref$customEmptyList,
customEmptyListItems = _ref.customEmptyListItems,
dataCy = _ref['data-cy'],
_ref$deactivateInputF = _ref.deactivateInputFilterAction,
deactivateInputFilterAction = _ref$deactivateInputF === void 0 ? false : _ref$deactivateInputF,
_ref$enableLeftRightN = _ref.enableLeftRightNavigation,
enableLeftRightNavigation = _ref$enableLeftRightN === void 0 ? false : _ref$enableLeftRightN,
_ref$focusTogglerOnMe = _ref.focusTogglerOnMenuClose,
focusTogglerOnMenuClose = _ref$focusTogglerOnMe === void 0 ? true : _ref$focusTogglerOnMe,
_ref$getTippyInstance = _ref.getTippyInstance,
getTippyInstance = _ref$getTippyInstance === void 0 ? noop : _ref$getTippyInstance,
_ref$inputPlaceholder = _ref.inputPlaceholder,
inputPlaceholder = _ref$inputPlaceholder === void 0 ? 'Search' : _ref$inputPlaceholder,
_ref$isMenuOpen = _ref.isMenuOpen,
isMenuOpen = _ref$isMenuOpen === void 0 ? false : _ref$isMenuOpen,
_ref$items = _ref.items,
items = _ref$items === void 0 ? [] : _ref$items,
_ref$menuAriaLabel = _ref.menuAriaLabel,
menuAriaLabel = _ref$menuAriaLabel === void 0 ? '' : _ref$menuAriaLabel,
menuCSS = _ref.menuCSS,
menuWidth = _ref.menuWidth,
_ref$onDropListLeave = _ref.onDropListLeave,
onDropListLeave = _ref$onDropListLeave === void 0 ? noop : _ref$onDropListLeave,
_ref$onInputChange = _ref.onInputChange,
onInputChange = _ref$onInputChange === void 0 ? noop : _ref$onInputChange,
_ref$onMenuBlur = _ref.onMenuBlur,
onMenuBlur = _ref$onMenuBlur === void 0 ? noop : _ref$onMenuBlur,
_ref$onMenuFocus = _ref.onMenuFocus,
onMenuFocus = _ref$onMenuFocus === void 0 ? noop : _ref$onMenuFocus,
_ref$onListItemSelect = _ref.onListItemSelectEvent,
onListItemSelectEvent = _ref$onListItemSelect === void 0 ? noop : _ref$onListItemSelect,
_ref$onOpenedStateCha = _ref.onOpenedStateChange,
onOpenedStateChange = _ref$onOpenedStateCha === void 0 ? noop : _ref$onOpenedStateCha,
_ref$onSelect = _ref.onSelect,
onSelect = _ref$onSelect === void 0 ? noop : _ref$onSelect,
_ref$renderCustomList = _ref.renderCustomListItem,
renderCustomListItem = _ref$renderCustomList === void 0 ? null : _ref$renderCustomList,
_ref$selection = _ref.selection,
selection = _ref$selection === void 0 ? null : _ref$selection,
_ref$tippyOptions = _ref.tippyOptions,
tippyOptions = _ref$tippyOptions === void 0 ? {} : _ref$tippyOptions,
_ref$toggler = _ref.toggler,
toggler = _ref$toggler === void 0 ? {} : _ref$toggler,
_ref$variant = _ref.variant,
variant = _ref$variant === void 0 ? _DropList.VARIANTS.SELECT : _ref$variant,
_ref$withMultipleSele = _ref.withMultipleSelection,
withMultipleSelection = _ref$withMultipleSele === void 0 ? false : _ref$withMultipleSele;
var _useState = (0, _react.useState)(false),
isOpen = _useState[0],
setOpenedState = _useState[1];
var tippyInstanceRef = (0, _react.useRef)(null);
var parsedSelection = (0, _DropList3.parseSelectionFromProps)({
withMultipleSelection: withMultipleSelection,
selection: selection
});
var _useState2 = (0, _react.useState)(parsedSelection),
selectedItem = _useState2[0],
setSelectedItem = _useState2[1];
var _useState3 = (0, _react.useState)(withMultipleSelection ? parsedSelection : []),
selectedItems = _useState3[0],
setSelectedItems = _useState3[1];
var _useState4 = (0, _react.useState)((0, _DropList3.flattenListItems)(items, withMultipleSelection)),
parsedItems = _useState4[0],
setParsedItems = _useState4[1];
var animateProps = (0, _DropList2.getAnimateProps)(animateOptions);
var tippyProps = (0, _DropList2.getTippyProps)(tippyOptions);
var Toggler = decorateUserToggler(toggler);
var DropListVariant = (0, _DropList3.getDropListVariant)({
autoSetComboboxAt: autoSetComboboxAt,
numberOfItems: parsedItems.filter(_DropList3.isItemRegular).length,
variant: variant
});
(0, _DropList3.useWarnings)({
toggler: toggler,
withMultipleSelection: withMultipleSelection,
menuCSS: menuCSS,
tippyOptions: tippyOptions
});
(0, _useDeepCompareEffect.default)(function () {
if (withMultipleSelection) {
setSelectedItems(parsedSelection);
} else {
setSelectedItem(parsedSelection);
}
}, [{
state: parsedSelection
}, withMultipleSelection]);
(0, _useDeepCompareEffect.default)(function () {
setParsedItems((0, _DropList3.flattenListItems)(items, withMultipleSelection));
}, [items, withMultipleSelection]);
(0, _react.useEffect)(function () {
setOpenedState(isMenuOpen);
}, [isMenuOpen]);
var debouncedOnDropListLeave = (0, _react.useMemo)(function () {
return (0, _lodash.default)(onDropListLeave, 300);
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[]);
function decorateUserToggler(userToggler) {
if ( /*#__PURE__*/_react.default.isValidElement(userToggler)) {
var _userToggler$props = userToggler.props,
className = _userToggler$props.className,
_onClick = _userToggler$props.onClick,
_onFocus = _userToggler$props.onFocus,
_onBlur = _userToggler$props.onBlur;
var togglerProps = {
className: (0, _classnames.default)(_DropList.DROPLIST_TOGGLER, className),
isActive: isOpen,
onBlur: function onBlur(e) {
_onBlur && _onBlur(e);
if (!isOpen) {
debouncedOnDropListLeave();
}
},
onClick: function onClick(e) {
_onClick && _onClick(e);
e.preventDefault();
toggleOpenedState(!isOpen);
},
onFocus: function onFocus(e) {
_onFocus && _onFocus(e);
var relatedTarget = e.relatedTarget;
if (isOpen && relatedTarget && relatedTarget.classList.contains('MenuList-Select')) {
toggleOpenedState(false);
}
}
};
if (userToggler.type === _DropList4.SelectTag) {
var text = userToggler.props.text;
if ((0, _lodash2.default)(text)) {
togglerProps.text = (0, _DropList3.itemToString)(selectedItem);
}
}
var _getTogglerPlacementP = (0, _DropList4.getTogglerPlacementProps)(userToggler, tippyOptions),
placement = _getTogglerPlacementP.placement,
offset = _getTogglerPlacementP.offset;
tippyProps.placement = placement;
tippyProps.offset = offset;
return /*#__PURE__*/_react.default.cloneElement(userToggler, togglerProps);
}
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_DropList4.SimpleButton, {
text: "Fallback Toggler"
});
}
function toggleOpenedState(shouldOpen) {
setOpenedState(shouldOpen);
onOpenedStateChange(shouldOpen);
}
function handleSelectedItemChange(_ref2) {
var selectedItem = _ref2.selectedItem;
if ((0, _lodash2.default)(selectedItem)) {
setSelectedItem(null);
return;
}
if (selectedItem.isDisabled || (0, _DropList3.isItemInert)(selectedItem)) {
return;
}
if ((0, _DropList3.isItemAction)(selectedItem)) {
onSelect(null, selectedItem);
return;
}
if (withMultipleSelection) {
if (selectedItem) {
var remove = selectedItem.remove;
var updatedSelection = [];
if (!Boolean(remove)) {
updatedSelection = selectedItems.concat(selectedItem);
} else {
var contentKey = (0, _DropList3.getItemContentKeyName)(selectedItem);
var itemToRemove = (0, _DropList3.findItemInArray)({
arr: selectedItems,
item: selectedItem,
key: contentKey
});
if (Boolean(itemToRemove)) {
updatedSelection = (0, _DropList3.removeItemFromArray)({
arr: selectedItems,
item: itemToRemove,
key: contentKey
});
}
}
setSelectedItems(updatedSelection);
setSelectedItem(updatedSelection.length ? selectedItem : null);
onSelect(updatedSelection, selectedItem);
}
} else {
setSelectedItem(selectedItem || null);
onSelect(selectedItem, selectedItem);
}
}
function focusToggler() {
tippyInstanceRef.current && tippyInstanceRef.current.reference.focus();
}
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_headless.default, (0, _extends2.default)({}, tippyProps, {
onCreate: function onCreate(instance) {
tippyInstanceRef.current = instance;
instance.popper && instance.popper.classList.add('DropList-Tippy');
getTippyInstance(instance);
},
onDestroy: function onDestroy() {
tippyInstanceRef.current = null;
},
showOnCreate: isMenuOpen,
onClickOutside: function onClickOutside(instance, _ref3) {
var target = _ref3.target;
if (target.dataset.ignoreToggling && target.dataset.ignoreToggling === 'true') {
return;
}
if (!closeOnClickOutside) {
return;
}
toggleOpenedState(false);
debouncedOnDropListLeave();
},
render: function render() {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Animate.default, (0, _extends2.default)({}, animateProps, {
in: isOpen,
onEnter: function onEnter(e) {
if (tippyInstanceRef.current) {
tippyInstanceRef.current.show();
if (tippyOptions.appendTo) {
tippyInstanceRef.current.popper.classList.add('hsds-react', 'hsds-beacon');
}
}
},
onEntered: function onEntered(element) {
var dropListEventDriverNode = element.querySelector('[data-event-driver]');
dropListEventDriverNode && dropListEventDriverNode.focus();
},
onExiting: function onExiting() {
focusTogglerOnMenuClose && focusToggler();
},
onExited: function onExited() {
tippyInstanceRef.current && tippyInstanceRef.current.hide();
},
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(DropListVariant, {
clearOnSelect: clearOnSelect,
closeOnSelection: closeOnSelection,
customEmptyList: customEmptyList,
customEmptyListItems: customEmptyListItems,
"data-cy": dataCy,
deactivateInputFilterAction: deactivateInputFilterAction,
enableLeftRightNavigation: enableLeftRightNavigation,
focusToggler: focusToggler,
handleSelectedItemChange: handleSelectedItemChange,
inputPlaceholder: inputPlaceholder,
isOpen: isOpen,
items: parsedItems,
menuAriaLabel: menuAriaLabel,
menuCSS: menuCSS,
menuWidth: (0, _DropList3.getMenuWidth)(DropListVariant.name, menuWidth),
onDropListLeave: debouncedOnDropListLeave,
onInputChange: onInputChange,
onMenuBlur: onMenuBlur,
onMenuFocus: onMenuFocus,
onListItemSelectEvent: onListItemSelectEvent,
renderCustomListItem: renderCustomListItem,
selectedItem: selectedItem,
selectedItems: selectedItems,
toggleOpenedState: toggleOpenedState,
withMultipleSelection: (0, _DropList3.isTogglerOfType)(toggler, _DropList4.SelectTag) ? false : withMultipleSelection
})
}));
},
children: Toggler
}));
}
var itemShape = _propTypes.default.shape({
className: _propTypes.default.string,
id: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]),
isDisabled: _propTypes.default.bool,
label: _DropList3.requiredItemPropsCheck,
value: _DropList3.requiredItemPropsCheck,
type: _propTypes.default.string
});
var dividerShape = _propTypes.default.shape({
type: _propTypes.default.oneOf(['divider', 'Divider']).isRequired
});
var groupShape = _propTypes.default.shape({
label: _DropList3.requiredItemPropsCheck,
value: _DropList3.requiredItemPropsCheck,
type: _propTypes.default.oneOf(['group', 'Group']).isRequired,
items: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.string, itemShape]))
});
DropListManager.propTypes = {
/** Props to configure the DropList animation (see [HSDS Animate](/?path=/docs/utilities-animation-animate--default-story)) */
animateOptions: _propTypes.default.object,
/** When the number of items is larger than this number, automatically set the variant to combobox */
autoSetComboboxAt: _propTypes.default.number,
/** Clears selected item on select */
clearOnSelect: _propTypes.default.bool,
/** Whether to close the DropList when clicking outside the droplist */
closeOnClickOutside: _propTypes.default.bool,
/** Whether to close the DropList when an item is selected */
closeOnSelection: _propTypes.default.bool,
/** Pass an Element to render a custom message or style when the List is empty */
customEmptyList: _propTypes.default.any,
/** To render "extra" items when the list is empty, as opposed to just customizing the rendering like `customEmptyList` does */
customEmptyListItems: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.string, itemShape, dividerShape, groupShape])),
/** Data attr applied to the DropList for Cypress tests. By default one of 'DropList.Select' or 'DropList.Combobox' depending on the variant used */
'data-cy': _propTypes.default.string,
/** On combobox, deactivate the default action of filtering so you can use the input as you please (like search) */
deactivateInputFilterAction: _propTypes.default.bool,
/** Enable navigation with Right and Left arrows (useful for horizontally rendered lists) */
enableLeftRightNavigation: _propTypes.default.bool,
/** Automatically moves the focus back to the toggler when the DropList is closed */
focusTogglerOnMenuClose: _propTypes.default.bool,
/** Retrieves the tippy instance */
getTippyInstance: _propTypes.default.any,
/** Customize the placeholder text on the combobox input */
inputPlaceholder: _propTypes.default.string,
/** Open/close the DropList externally */
isMenuOpen: _propTypes.default.bool,
/** Items to populate the list with */
items: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.string, itemShape, dividerShape, groupShape])),
/** Custom aria label for the Menu */
menuAriaLabel: _propTypes.default.string,
/** Custom css for the Menu */
menuCSS: _propTypes.default.any,
/** Custom width for the Menu */
menuWidth: _propTypes.default.any,
/** Callback that fires when combobox search input changes, gives acces to value, resulting filtered items and event (if deactivateInputFilterAction is on) `onInputChange(value, filteredItems, event)` */
onInputChange: _propTypes.default.func,
/** Callback that fires when the menu loses focus */
onMenuBlur: _propTypes.default.func,
/** Callback that fires when the menu gets focus */
onMenuFocus: _propTypes.default.func,
/** Callback that fires when leaving the DropList entirely (including the toggler) */
onDropListLeave: _propTypes.default.func,
/** Downshift does not provide the event on select, this callback fires when selecting an item (clicking, keydown enter (and space on Select variant)), gives you the DOM selected item as listItemNode and the original event */
onListItemSelectEvent: _propTypes.default.func,
/** Callback that fires whenever the DropList opens and closes */
onOpenedStateChange: _propTypes.default.func,
/** Callback that fires whenever the selection in the DropList changes, signature: `onSelect(selection, clickedItem)` */
onSelect: _propTypes.default.func,
/** Render prop that allows you to render a custom List Item */
renderCustomListItem: _propTypes.default.func,
/** An item or array of items to be selected */
selection: _propTypes.default.oneOfType([_propTypes.default.string, itemShape, _propTypes.default.arrayOf(itemShape)]),
/** Options to configure Tippy (https://atomiks.github.io/tippyjs/v6/all-props/)*/
tippyOptions: _propTypes.default.object,
/** A component to render as the "toggler" or "trigger", a set of built-in options are provided: Button, IconButton, MeatButton, SelectTag, SplitButton */
toggler: _propTypes.default.element,
/** The type of DropList, standard ("select") or searchable ("combobox") */
variant: _propTypes.default.oneOf(['select', 'Select', 'combobox', 'Combobox']),
/** Enable multiple selection of items */
withMultipleSelection: _propTypes.default.bool
};
var _default = DropListManager;
exports.default = _default;