UNPKG

pouncejs

Version:

A collection of UI components from Panther labs

399 lines (347 loc) 14.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = void 0; var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _react = _interopRequireDefault(require("react")); var _downshift = _interopRequireDefault(require("downshift")); var _fuzzaldrin = require("fuzzaldrin"); var _Box = _interopRequireDefault(require("../Box")); var _IconButton = _interopRequireDefault(require("../IconButton")); var _Flex = _interopRequireDefault(require("../Flex")); var _Input = require("../utils/Input"); var _Tag = _interopRequireDefault(require("./Tag")); var _helpers = require("../../utils/helpers"); var _Menu = _interopRequireDefault(require("../utils/Menu")); var _AbstractButton = _interopRequireDefault(require("../AbstractButton")); var _ComboBoxItems = _interopRequireDefault(require("../utils/ComboBoxItems/ComboBoxItems")); var _Icon = _interopRequireDefault(require("../Icon")); /* eslint-disable @typescript-eslint/no-explicit-any */ var stateReducer = function stateReducer(state, changes) { switch (changes.type) { case _downshift.default.stateChangeTypes.keyDownEnter: case _downshift.default.stateChangeTypes.clickItem: // Whenever the user makes a selection, make sure to reset the input so that he can keep // searching for more. We also make sure to always highlight the first item return (0, _extends2.default)({}, changes, { inputValue: '', highlightedIndex: state.highlightedIndex, isOpen: true }); case _downshift.default.stateChangeTypes.blurInput: return (0, _extends2.default)({}, changes, { inputValue: '' }); default: return changes; } }; function DefaultContent(_ref) { var value = _ref.value, itemToString = _ref.itemToString, removeItem = _ref.removeItem; return value.map(function (selectedItem) { return /*#__PURE__*/_react.default.createElement(_Tag.default, { as: "li", key: itemToString(selectedItem), m: 1, onRemove: function onRemove() { return removeItem(selectedItem); } }, itemToString(selectedItem)); }); } /** * A simple MultiCombobox can be thought of as a typical `<select>` component. Whenever you would * use a normal select, you should now pass the `<MultiCombobox>` component. */ function MultiCombobox(_ref2) { var onChange = _ref2.onChange, _onBlur = _ref2.onBlur, value = _ref2.value, _ref2$variant = _ref2.variant, variant = _ref2$variant === void 0 ? 'outline' : _ref2$variant, items = _ref2.items, _ref2$disableItem = _ref2.disableItem, disableItem = _ref2$disableItem === void 0 ? function () { return false; } : _ref2$disableItem, _ref2$searchable = _ref2.searchable, searchable = _ref2$searchable === void 0 ? false : _ref2$searchable, _ref2$label = _ref2.label, label = _ref2$label === void 0 ? '' : _ref2$label, hideLabel = _ref2.hideLabel, _ref2$disabled = _ref2.disabled, disabled = _ref2$disabled === void 0 ? false : _ref2$disabled, _ref2$placeholder = _ref2.placeholder, placeholder = _ref2$placeholder === void 0 ? '' : _ref2$placeholder, _ref2$itemToString = _ref2.itemToString, _itemToString = _ref2$itemToString === void 0 ? function (item) { return String(item); } : _ref2$itemToString, itemToGroup = _ref2.itemToGroup, _ref2$allowAdditions = _ref2.allowAdditions, allowAdditions = _ref2$allowAdditions === void 0 ? false : _ref2$allowAdditions, _ref2$validateAdditio = _ref2.validateAddition, validateAddition = _ref2$validateAdditio === void 0 ? function () { return true; } : _ref2$validateAdditio, _ref2$maxHeight = _ref2.maxHeight, maxHeight = _ref2$maxHeight === void 0 ? 300 : _ref2$maxHeight, maxContainerHeight = _ref2.maxContainerHeight, _ref2$maxWidth = _ref2.maxWidth, maxWidth = _ref2$maxWidth === void 0 ? 800 : _ref2$maxWidth, maxResults = _ref2.maxResults, canClearAllAfter = _ref2.canClearAllAfter, invalid = _ref2.invalid, hidden = _ref2.hidden, _ref2$renderContent = _ref2.renderContent, renderContent = _ref2$renderContent === void 0 ? DefaultContent : _ref2$renderContent, rest = (0, _objectWithoutPropertiesLoose2.default)(_ref2, ["onChange", "onBlur", "value", "variant", "items", "disableItem", "searchable", "label", "hideLabel", "disabled", "placeholder", "itemToString", "itemToGroup", "allowAdditions", "validateAddition", "maxHeight", "maxContainerHeight", "maxWidth", "maxResults", "canClearAllAfter", "invalid", "hidden", "renderContent"]); var triggerRef = _react.default.useRef(null); var getVariant = _react.default.useCallback(function (isOpen) { if (variant === 'solid') { return 'solid'; } return isOpen ? 'solid' : 'outline'; }, [variant]); var removeItem = function removeItem(item) { onChange(value.filter(function (i) { return i !== item; })); }; var handleChange = function handleChange(item) { if (item === null) { return; } var changedItems = Array.isArray(item) ? item : [item]; // If items are already added, remove them from the selected items list if (changedItems.every(function (item) { return value.map(_itemToString).includes(_itemToString(item)); })) { return onChange(value.filter(function (i) { return !changedItems.map(_itemToString).includes(_itemToString(i)); })); } // Append non existing items to the list of selected items var newItems = changedItems.filter(function (i) { return !value.map(_itemToString).includes(_itemToString(i)); }); return onChange([].concat(value, newItems)); }; var clearSelectedItems = function clearSelectedItems() { onChange([]); }; var itemsPt = hideLabel ? 3 : '19px'; return /*#__PURE__*/_react.default.createElement(_downshift.default, { stateReducer: stateReducer, onChange: handleChange, selectedItem: null, itemToString: function itemToString(item) { return item && !Array.isArray(item) ? _itemToString(item) : ''; }, initialInputValue: "" }, function (_ref3) { var getRootProps = _ref3.getRootProps, getInputProps = _ref3.getInputProps, getItemProps = _ref3.getItemProps, getMenuProps = _ref3.getMenuProps, getLabelProps = _ref3.getLabelProps, inputValue = _ref3.inputValue, isOpen = _ref3.isOpen, toggleMenu = _ref3.toggleMenu, openMenu = _ref3.openMenu, selectItem = _ref3.selectItem; var processUserValue = function processUserValue(inputVal) { return (inputVal || '').trim(); }; var validateUserValue = function validateUserValue(inputVal) { return inputVal !== '' && validateAddition(inputVal, value); }; var multiComboboxVariant = getVariant(isOpen); var results = items.slice(0, maxResults); // If it's searchable, only filter results by search term when the searching // functionality is available. if (searchable) { // We map the items to a new type in order to feed it to the fuzzySearch generic function. var itemsToSearch = items.map(function (i) { return { // Contains the string representation of the item that will be tested. searchString: itemToGroup ? "" + itemToGroup(i) + _itemToString(i) : _itemToString(i), // Include the actual item in the object so we can map it back after we are done with the search. item: i }; }); results = (0, _fuzzaldrin.filter)(itemsToSearch, inputValue || '', { key: 'searchString', maxResults: maxResults }).map(function (i) { return i.item; }); } // We add 2 types of additional data to the input that is going to be rendered: // 1. A handler for the `Delete` button, so that you can delete tokens with a single key // 3. When the combobox is not searchable, we make the input "behave" like a div. We // still want an input though for placeholder, spacings, etc. var additionalInputProps = (0, _extends2.default)({}, rest, !searchable && { cursor: 'pointer', onMouseDown: toggleMenu, readOnly: true, placeholder: !value.length ? placeholder : '' }, searchable && { placeholder: hideLabel && value && value.length && !isOpen || value && value.length ? '' : placeholder, p: isOpen ? 1 : null }, { position: 'absolute', width: '100%', height: '100%', top: 0, left: 0, onFocus: openMenu, onKeyDown: function onKeyDown(event) { // Allow deletions of selections by pressing backspace if (event.key === 'Backspace' && !inputValue) { removeItem(value[value.length - 1]); } // Allow the user to add custom selections if both `searchable` and `allowAdditions` // have a truthy value if ((event.key === 'Enter' || event.key === ',') && allowAdditions) { event.preventDefault(); // By default validateAddition always returns true. Can be overriden by the user // for fine-grained addition var processedUserValue = processUserValue(inputValue); if (validateUserValue(processedUserValue)) { selectItem(processedUserValue, { inputValue: '', isOpen: true }); } } }, onBlur: function onBlur(e) { var processedUserValue = processUserValue(inputValue); if (allowAdditions && validateUserValue(processedUserValue)) { selectItem(processedUserValue, { inputValue: '' }); } if (_onBlur) { _onBlur(e); } }, onPaste: function onPaste(e) { // prevent it when we can only select values from the dropdown if (!allowAdditions) { return; } // Get clipboard data and split them based on newline and/or commas var clipboardData = e.clipboardData.getData('Text'); var items = clipboardData.replace(/\r?\n/g, ',').split(',').map(processUserValue).filter(validateUserValue); if (items.length > 1) { // Prevent the text from actually being pasted to the underlying input e.preventDefault(); e.stopPropagation(); // extend existing values with new ones onChange([].concat(value, items)); } } }); return /*#__PURE__*/_react.default.createElement(_Box.default, getRootProps(), /*#__PURE__*/_react.default.createElement(_Box.default, { position: "relative", ref: triggerRef }, /*#__PURE__*/_react.default.createElement(_Input.InputControl, { invalid: invalid, disabled: disabled, variant: multiComboboxVariant, hidden: hidden }, /*#__PURE__*/_react.default.createElement(_Flex.default, { as: "ul", wrap: "wrap", align: "baseline", pl: 3, pr: 10, pt: itemsPt, pb: "2px", maxHeight: maxContainerHeight }, /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, renderContent({ itemToString: _itemToString, removeItem: removeItem, value: value, isOpen: isOpen }), /*#__PURE__*/_react.default.createElement(_Box.default, { as: "li", maxWidth: "100%", flexGrow: 1, position: isOpen && searchable ? 'relative' : 'initial' }, isOpen && searchable && /*#__PURE__*/_react.default.createElement(_Input.InputElement, { as: "span", px: 1, py: 0, standalone: hideLabel, visibility: "hidden", color: "transparent", whiteSpace: "pre" }, inputValue), /*#__PURE__*/_react.default.createElement(_Input.InputElement, (0, _extends2.default)({ type: "text", standalone: hideLabel }, getInputProps(additionalInputProps)))))), /*#__PURE__*/_react.default.createElement(_Input.InputLabel, (0, _extends2.default)({ visuallyHidden: hideLabel, raised: !!value.length || isOpen }, getLabelProps()), label)), items.length > 0 && /*#__PURE__*/_react.default.createElement(_Flex.default, { opacity: disabled ? 0.3 : 1, position: "absolute", top: 0, bottom: 0, right: 2, align: "center", justify: "center", pointerEvents: isOpen ? 'auto' : 'none' }, /*#__PURE__*/_react.default.createElement(_IconButton.default, { tabIndex: -1, icon: isOpen ? 'caret-up' : 'caret-down', size: "medium", "aria-label": "Toggle Menu", onClick: function onClick() { return toggleMenu({ inputValue: '' }); }, variant: "unstyled" }))), /*#__PURE__*/_react.default.createElement(_Menu.default, (0, _extends2.default)({ as: "ul", maxHeight: maxHeight, maxWidth: maxWidth, isOpen: isOpen && results.length > 0, triggerRef: triggerRef }, getMenuProps()), isOpen && canClearAllAfter && value.length >= canClearAllAfter && /*#__PURE__*/_react.default.createElement(_Box.default, { as: "li", listStyle: "none", backgroundColor: "navyblue-350", position: "sticky", top: 0, zIndex: 1 }, /*#__PURE__*/_react.default.createElement(_AbstractButton.default, { width: "100%", onClick: clearSelectedItems, fontSize: "x-small", color: "teal-200", _hover: { textDecoration: 'underline' } }, /*#__PURE__*/_react.default.createElement(_Flex.default, { as: "span", align: "center", spacing: "6px", py: "6px", px: 4 }, /*#__PURE__*/_react.default.createElement(_Icon.default, { size: "small", type: "close-circle" }), /*#__PURE__*/_react.default.createElement(_Box.default, { as: "span" }, "Clear Selection")))), /*#__PURE__*/_react.default.createElement(_ComboBoxItems.default, { items: results, disableItem: disableItem, getItemProps: getItemProps, itemToString: _itemToString, itemToGroup: itemToGroup, selectedItems: value, allowMultipleSelection: true }))); }); } var _default = (0, _helpers.typedMemo)(MultiCombobox); exports.default = _default;