pouncejs
Version:
A collection of UI components from Panther labs
399 lines (347 loc) • 14.5 kB
JavaScript
"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;