@ntragas/pouncejstest
Version:
A collection of UI components from Panther labs
194 lines (181 loc) • 7.64 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/objectWithoutPropertiesLoose";
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react';
import Downshift from 'downshift';
import { filter as fuzzySearch } from 'fuzzaldrin';
import Box from '../Box';
import Flex from '../Flex';
import IconButton from '../IconButton';
import { InputControl, InputElement, InputLabel } from '../utils/Input';
import { typedMemo } from '../../utils/helpers';
import Menu from '../utils/Menu';
import ComboBoxItems from '../utils/ComboBoxItems/ComboBoxItems';
/**
* A simple Combobox can be thought of as a typical `<select>` component. Whenerever you would
* use a normal select, you should now pass the `<Combobox>` component.
*/
function Combobox(_ref) {
var onChange = _ref.onChange,
value = _ref.value,
items = _ref.items,
_ref$variant = _ref.variant,
variant = _ref$variant === void 0 ? 'outline' : _ref$variant,
_ref$searchable = _ref.searchable,
searchable = _ref$searchable === void 0 ? false : _ref$searchable,
_ref$label = _ref.label,
label = _ref$label === void 0 ? '' : _ref$label,
hideLabel = _ref.hideLabel,
_ref$disabled = _ref.disabled,
disabled = _ref$disabled === void 0 ? false : _ref$disabled,
_ref$disableItem = _ref.disableItem,
disableItem = _ref$disableItem === void 0 ? function () {
return false;
} : _ref$disableItem,
_ref$itemToString = _ref.itemToString,
itemToString = _ref$itemToString === void 0 ? function (item) {
return String(item);
} : _ref$itemToString,
itemToGroup = _ref.itemToGroup,
_ref$maxHeight = _ref.maxHeight,
maxHeight = _ref$maxHeight === void 0 ? 300 : _ref$maxHeight,
maxResults = _ref.maxResults,
invalid = _ref.invalid,
required = _ref.required,
hidden = _ref.hidden,
rest = _objectWithoutPropertiesLoose(_ref, ["onChange", "value", "items", "variant", "searchable", "label", "hideLabel", "disabled", "disableItem", "itemToString", "itemToGroup", "maxHeight", "maxResults", "invalid", "required", "hidden"]);
// convert item to a string with a fallback of empty string
var safeItemToString = function safeItemToString(item) {
return item != undefined ? itemToString(item) : '';
}; // Due to the way we want our Combobox to behave, we want to control the input value ourselves.
// We make sure to update it on every selection made, on every keystroke within the search input,
// plus some focus/blur events that are tied to the searchable behaviour (see below)
var _React$useState = React.useState(''),
inputValue = _React$useState[0],
setInputValue = _React$useState[1];
var getVariant = React.useCallback(function (isOpen) {
if (variant === 'solid') {
return 'solid';
}
return isOpen ? 'solid' : 'outline';
}, [variant]);
React.useLayoutEffect(function () {
setInputValue(safeItemToString(value));
}, [value]);
return /*#__PURE__*/React.createElement(Downshift, {
onSelect: function onSelect(selectedItem) {
return setInputValue(safeItemToString(selectedItem));
},
onChange: onChange,
selectedItem: value,
inputValue: inputValue,
itemToString: safeItemToString
}, function (_ref2) {
var getRootProps = _ref2.getRootProps,
getInputProps = _ref2.getInputProps,
getItemProps = _ref2.getItemProps,
getMenuProps = _ref2.getMenuProps,
getLabelProps = _ref2.getLabelProps,
selectedItem = _ref2.selectedItem,
isOpen = _ref2.isOpen,
toggleMenu = _ref2.toggleMenu,
openMenu = _ref2.openMenu,
closeMenu = _ref2.closeMenu;
var comboboxVariant = 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 = fuzzySearch(itemsToSearch, inputValue || '', {
key: 'searchString',
maxResults
}).map(function (i) {
return i.item;
});
} // We add 2 types of additional data to the input that is going to be renders:
// 1. When the combobox is not searchable, we make the input "behave" like a div. We
// still want an input though for placeholder, spacings, etc.
// 2. When the combobox is searchable, we want it to behave like an empty input when
// focused (showcasing the current value through the placeholder) so that the user can
// see all the options even if he has already selected an option (to do that we need to
// clear the input). If the user blurs, then we revert back to a normal behaviour
var additionalInputProps = _extends({}, rest, !searchable && {
cursor: 'pointer',
onMouseDown: toggleMenu,
onFocus: openMenu,
readOnly: true
}, searchable && {
placeholder: value != undefined ? itemToString(value) : rest.placeholder,
onChange: function onChange(e) {
return setInputValue(e.currentTarget.value);
},
onFocus: function onFocus() {
openMenu();
setInputValue('');
},
onBlur: function onBlur() {
closeMenu();
setInputValue(safeItemToString(value));
}
});
return /*#__PURE__*/React.createElement(Box, _extends({
position: "relative"
}, getRootProps()), /*#__PURE__*/React.createElement(Box, {
position: "relative"
}, /*#__PURE__*/React.createElement(InputControl, {
invalid: invalid,
disabled: disabled,
required: required,
variant: comboboxVariant,
hidden: hidden
}, /*#__PURE__*/React.createElement(InputElement, _extends({
as: "input",
type: "text",
truncated: true,
standalone: hideLabel,
pr: 8
/* account for absolute position of caret */
}, getInputProps(additionalInputProps))), /*#__PURE__*/React.createElement(InputLabel, _extends({
visuallyHidden: hideLabel,
raised: value != null
}, getLabelProps()), label)), /*#__PURE__*/React.createElement(Flex, {
opacity: disabled ? 0.3 : 1,
position: "absolute",
top: 0,
bottom: 0,
right: 2,
align: "center",
justify: "center",
pointerEvents: isOpen ? 'auto' : 'none'
}, /*#__PURE__*/React.createElement(IconButton, {
tabIndex: -1,
icon: isOpen ? 'caret-up' : 'caret-down',
size: "medium",
"aria-label": "Toggle Menu",
onClick: function onClick() {
return closeMenu();
},
variant: "unstyled"
}))), /*#__PURE__*/React.createElement(Menu, _extends({
as: "ul",
maxHeight: maxHeight,
isOpen: isOpen && results.length > 0
}, getMenuProps()), /*#__PURE__*/React.createElement(ComboBoxItems, {
items: results,
disableItem: disableItem,
getItemProps: getItemProps,
itemToString: itemToString,
itemToGroup: itemToGroup,
selectedItems: selectedItem ? [selectedItem] : undefined
})));
});
}
export default typedMemo(Combobox);