UNPKG

@chayns-components/core

Version:

A set of beautiful React components for developing your own applications with chayns.

688 lines (674 loc) • 25.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _chaynsApi = require("chayns-api"); var _react = _interopRequireWildcard(require("react")); var _styledComponents = require("styled-components"); var _calculate = require("../../utils/calculate"); var _searchBox = require("../../utils/searchBox"); var _Icon = _interopRequireDefault(require("../icon/Icon")); var _Input = _interopRequireDefault(require("../input/Input")); var _GroupName = _interopRequireDefault(require("./group-name/GroupName")); var _SearchBoxBody = _interopRequireDefault(require("./search-box-body/SearchBoxBody")); var _SearchBoxItem = _interopRequireDefault(require("./search-box-item/SearchBoxItem")); var _SearchBoxItem2 = require("./search-box-item/SearchBoxItem.styles"); var _SearchBox = require("./SearchBox.styles"); var _uuid = require("../../hooks/uuid"); var _DropdownBodyWrapper = _interopRequireDefault(require("../dropdown-body-wrapper/DropdownBodyWrapper")); var _TagInput = _interopRequireDefault(require("../tag-input/TagInput")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } const filterSearchBoxItems = ({ customFilter, items, searchString, shouldUseCustomFilterOnly }) => { if (typeof customFilter !== 'function') { return (0, _searchBox.searchList)({ items, searchString }); } if (shouldUseCustomFilterOnly) { const filteredItems = items.filter(customFilter); return (0, _searchBox.sortSearchBoxItems)({ items: filteredItems, searchString }); } return (0, _searchBox.searchList)({ items, searchString }).filter(customFilter); }; const getDropdownSearchString = ({ selectedId, shouldKeepSelectedItemPosition, value }) => shouldKeepSelectedItemPosition && selectedId ? '' : value; const SearchBox = /*#__PURE__*/(0, _react.forwardRef)(({ container, customFilter, dropdownDirection, inputProps, isInvalid = false, leftIcons, lists, maxHeight = 300, onBlur, onChange, onKeyDown, onSelect, placeholder, presetValue, hintText, selectedId, shouldAddInputToList = true, shouldKeepSelectedItemPosition = false, shouldUseCustomFilterOnly = false, shouldHideFilterButtons = false, shouldShowContentOnEmptyInput = true, shouldShowSmallItems = false, shouldShowRoundImage, shouldShowToggleIcon = false, tagInputSettings, shouldEnableKeyboardHighlighting }, ref) => { const [matchingListsItems, setMatchingListsItems] = (0, _react.useState)(lists); const [selectedImage, setSelectedImage] = (0, _react.useState)(); const [internalValue, setInternalValue] = (0, _react.useState)(typeof presetValue === 'string' && presetValue !== '' ? presetValue : ''); const inputValue = inputProps === null || inputProps === void 0 ? void 0 : inputProps.value; const value = inputValue ?? internalValue; const setValue = (0, _react.useCallback)(nextValue => { if (typeof inputValue !== 'string') { setInternalValue(nextValue); } }, [inputValue]); const [height, setHeight] = (0, _react.useState)(0); const [focusedIndex, setFocusedIndex] = (0, _react.useState)(null); const [hasMultipleGroups, setHasMultipleGroups] = (0, _react.useState)(lists.length > 1); const [filteredChildrenArray, setFilteredChildrenArray] = (0, _react.useState)(); const [inputToListValue, setInputToListValue] = (0, _react.useState)(''); const [groups, setGroups] = (0, _react.useState)(['all']); const [shouldShowBody, setShouldShowBody] = (0, _react.useState)(false); const uuid = (0, _uuid.useUuid)(); const boxRef = (0, _react.useRef)(null); const contentRef = (0, _react.useRef)(null); const inputRef = (0, _react.useRef)(null); const tagInputRef = (0, _react.useRef)(null); const hasFocusRef = (0, _react.useRef)(false); const isAnimatingRef = (0, _react.useRef)(false); const shouldShowPresetValue = (0, _react.useRef)(typeof presetValue === 'string' && presetValue !== ''); const theme = (0, _styledComponents.useTheme)(); const dropdownSearchString = getDropdownSearchString({ selectedId, shouldKeepSelectedItemPosition, value }); const { isTouch } = (0, _chaynsApi.useDevice)(); /** * Checks if there are multiple groups in the lists */ (0, _react.useEffect)(() => { setHasMultipleGroups(lists.length > 1); }, [lists]); const filterButtons = (0, _react.useMemo)(() => { const items = []; if (lists.length <= 1) { return items; } lists.forEach(({ groupName }) => { if (groupName) { items.push({ id: groupName, text: groupName }); } }); return items; }, [lists]); /** * Filters the lists by the FilterButtons */ const activeList = (0, _react.useMemo)(() => { let newLists = []; if (groups[0] === 'all') { newLists = lists; } else { lists.forEach(list => { if (list.groupName && groups.includes(list.groupName)) { newLists.push(list); } }); } const newMatchingItems = []; newLists.forEach(({ list, groupName }) => { const newList = filterSearchBoxItems({ customFilter, items: list, searchString: dropdownSearchString, shouldUseCustomFilterOnly }); if (newList.length > 0) { newMatchingItems.push({ groupName, list: newList }); } }); if (newMatchingItems.length === 0 && shouldAddInputToList) { newMatchingItems.push({ groupName: undefined, list: [] }); } const filteredMatchingListItems = newMatchingItems.map(({ list, groupName }) => ({ groupName, list: list.filter(item => { if (typeof customFilter === 'function' && shouldUseCustomFilterOnly) { return true; } return !(newMatchingItems.length === 1 && item.text === dropdownSearchString); }) })); setMatchingListsItems(filteredMatchingListItems); return newLists; }, [groups, lists, customFilter, dropdownSearchString, shouldAddInputToList, shouldUseCustomFilterOnly]); const handleOpen = (0, _react.useCallback)(() => { setShouldShowBody(true); }, []); const handleClose = (0, _react.useCallback)(() => { setShouldShowBody(false); }, []); const handleFilterButtonsGroupSelect = keys => { setGroups(keys.length === 0 ? ['all'] : keys); }; /** * This hook calculates the height */ (0, _react.useEffect)(() => { const textArray = []; activeList.forEach(({ list, groupName }) => { list.forEach(({ text }) => textArray.push(text)); if (!groupName) { return; } textArray.push(groupName); }); if (shouldAddInputToList && inputToListValue !== '') { textArray.push(inputToListValue); } setHeight((0, _calculate.calculateContentHeight)(textArray)); }, [inputToListValue, activeList, placeholder, shouldAddInputToList]); (0, _react.useEffect)(() => { if (selectedId) { activeList.forEach(({ list }) => { const selectedItem = list.find(({ id }) => id === selectedId); if (selectedItem) { setValue(selectedItem.text); if (selectedItem.imageUrl) { setSelectedImage(/*#__PURE__*/_react.default.createElement(_SearchBoxItem2.StyledSearchBoxItemImage, { src: selectedItem.imageUrl, $shouldShowRoundImage: shouldShowRoundImage })); } } }); } }, [activeList, selectedId, shouldShowRoundImage]); /** * This hook resets the value if the selectedId changes to undefined. This is an own useEffect because the value * should not be reset if the list changes and the selectedId is still undefined. */ (0, _react.useEffect)(() => { if (!selectedId && !shouldShowPresetValue.current) { setValue(''); } }, [selectedId]); (0, _react.useEffect)(() => { isAnimatingRef.current = shouldShowBody; }, [shouldShowBody]); (0, _react.useEffect)(() => { if ((matchingListsItems.length !== 0 || hintText) && !isAnimatingRef.current && hasFocusRef.current) { handleOpen(); } }, [handleOpen, hintText, matchingListsItems.length]); /** * This function handles the focus event of the input and opens the dropdown if the input * should show content on an empty input */ const handleFocus = (0, _react.useCallback)(event => { hasFocusRef.current = true; if (typeof (inputProps === null || inputProps === void 0 ? void 0 : inputProps.onFocus) === 'function') { inputProps.onFocus(event); } if (shouldShowContentOnEmptyInput) { const newMatchingItems = []; activeList.forEach(({ list, groupName }) => { const newList = filterSearchBoxItems({ customFilter, items: list, searchString: dropdownSearchString, shouldUseCustomFilterOnly }); if (newList.length > 0) { newMatchingItems.push({ groupName, list: newList }); } }); if (newMatchingItems.length === 0 && shouldAddInputToList) { newMatchingItems.push({ groupName: undefined, list: [] }); } const filteredMatchingListItems = newMatchingItems.map(({ list, groupName }) => ({ groupName, list: list.filter(item => { if (typeof customFilter === 'function' && shouldUseCustomFilterOnly) { return true; } return !(newMatchingItems.length === 1 && item.text === dropdownSearchString); }) })); setMatchingListsItems(filteredMatchingListItems); if (filteredMatchingListItems.length !== 0 || hintText) { handleOpen(); } } }, [shouldShowContentOnEmptyInput, activeList, shouldAddInputToList, hintText, dropdownSearchString, customFilter, shouldUseCustomFilterOnly, handleOpen, inputProps]); const handleKeyDown = (0, _react.useCallback)(event => { if (typeof onKeyDown === 'function') { onKeyDown(event); } if (typeof (inputProps === null || inputProps === void 0 ? void 0 : inputProps.onKeyDown) === 'function') { inputProps.onKeyDown(event); } }, [inputProps, onKeyDown]); /** * This function filters the lists by input */ (0, _react.useEffect)(() => { const newMatchingItems = []; activeList.forEach(({ list, groupName }) => { const newList = filterSearchBoxItems({ customFilter, items: list, searchString: dropdownSearchString, shouldUseCustomFilterOnly }); if (newList.length > 0) { newMatchingItems.push({ groupName, list: newList }); } }); if (newMatchingItems.length === 0 && shouldAddInputToList) { newMatchingItems.push({ groupName: undefined, list: [] }); } if (shouldAddInputToList && inputToListValue !== '') { newMatchingItems.forEach(({ list }) => { list.forEach(({ text }) => { if (text.toLowerCase() === inputToListValue.toLowerCase()) { setInputToListValue(''); } }); }); } }, [inputToListValue, activeList, shouldAddInputToList, shouldShowContentOnEmptyInput, dropdownSearchString, customFilter, shouldUseCustomFilterOnly]); const handleClick = (0, _react.useCallback)(() => { if (shouldShowBody) { handleClose(); } else { handleOpen(); } }, [handleClose, handleOpen, shouldShowBody]); const rightElement = (0, _react.useMemo)(() => { if (!shouldShowToggleIcon) { return undefined; } return /*#__PURE__*/_react.default.createElement(_SearchBox.StyledSearchBoxIcon, { onClick: handleClick }, /*#__PURE__*/_react.default.createElement(_Icon.default, { icons: ['fa fa-chevron-down'], color: theme['006'] })); }, [handleClick, shouldShowToggleIcon, theme]); const leftElement = (0, _react.useMemo)(() => (leftIcons || selectedImage) && /*#__PURE__*/_react.default.createElement(_SearchBox.StyledSearchBoxLeftWrapper, null, leftIcons && /*#__PURE__*/_react.default.createElement(_Icon.default, { icons: leftIcons }), selectedImage && selectedImage), [leftIcons, selectedImage]); /** * This function handles changes of the input */ const handleChange = (0, _react.useCallback)(event => { const filteredLists = []; shouldShowPresetValue.current = false; activeList.forEach(({ list, groupName }) => { const newList = filterSearchBoxItems({ customFilter, items: list, searchString: event.target.value, shouldUseCustomFilterOnly }); if (newList.length > 0) { filteredLists.push({ groupName, list: newList }); } }); if (filteredLists.length === 0 && shouldAddInputToList) { filteredLists.push({ groupName: undefined, list: [] }); } setSelectedImage(undefined); if (!shouldShowContentOnEmptyInput && !event.target.value) { setMatchingListsItems([]); } else { setMatchingListsItems(filteredLists); } if (filteredLists.length !== 0) { handleOpen(); } setValue(event.target.value); setInputToListValue(event.target.value); if (typeof onChange === 'function') { onChange(event); } if (typeof (inputProps === null || inputProps === void 0 ? void 0 : inputProps.onChange) === 'function') { inputProps.onChange(event); } }, [activeList, customFilter, handleOpen, inputProps, onChange, shouldAddInputToList, shouldShowContentOnEmptyInput, shouldUseCustomFilterOnly]); /** * This function handles the blur event of the input */ const handleBlur = (0, _react.useCallback)(event => { hasFocusRef.current = false; if (typeof onBlur === 'function') { onBlur(event); } if (typeof (inputProps === null || inputProps === void 0 ? void 0 : inputProps.onBlur) === 'function') { inputProps.onBlur(event); } }, [inputProps, onBlur]); const handleDropdownOutsideClick = (0, _react.useCallback)(() => { var _tagInputRef$current; (_tagInputRef$current = tagInputRef.current) === null || _tagInputRef$current === void 0 || _tagInputRef$current.blur(); return hasFocusRef.current && isTouch; }, [isTouch]); const handleContainerBlur = (0, _react.useCallback)(event => { var _contentRef$current; const nextFocusedElement = event.relatedTarget; const currentContainer = event.currentTarget; // Check if focus is moving outside the SearchBox container // Also check the dropdown content (contentRef) since it's rendered in a portal if (!nextFocusedElement || !currentContainer.contains(nextFocusedElement) && !((_contentRef$current = contentRef.current) !== null && _contentRef$current !== void 0 && _contentRef$current.contains(nextFocusedElement))) { handleClose(); } }, [handleClose]); /** * This function handles the item selection */ const handleSelect = (0, _react.useCallback)(item => { const newItem = { ...item, text: item.text.replace('<b>', '').replace('</b>', '').replace('</b', '') }; if (tagInputSettings) { var _tagInputRef$current2; setValue(''); setInputToListValue(''); (_tagInputRef$current2 = tagInputRef.current) === null || _tagInputRef$current2 === void 0 || _tagInputRef$current2.resetValue(); } else { setValue(newItem.text); } handleClose(); setSelectedImage(newItem.imageUrl ? /*#__PURE__*/_react.default.createElement(_SearchBoxItem2.StyledSearchBoxItemImage, { src: newItem.imageUrl, $shouldShowRoundImage: shouldShowRoundImage }) : undefined); setMatchingListsItems([]); if (typeof onSelect === 'function') { onSelect(newItem); } }, [handleClose, onSelect, shouldShowRoundImage, tagInputSettings]); const content = (0, _react.useMemo)(() => { if (hintText && matchingListsItems.length === 0) { return /*#__PURE__*/_react.default.createElement(_SearchBox.StyledSearchBoxHintText, null, hintText.replace('##value##', value)); } const items = []; matchingListsItems.forEach(({ groupName, list }, index) => { if (hasMultipleGroups) { if (list.length <= 0) { return; } if (index !== 0) { items.push(/*#__PURE__*/_react.default.createElement(_GroupName.default, { key: groupName, name: groupName ?? '' })); } } list.forEach(({ id, text, imageUrl }, listIndex) => { items.push(/*#__PURE__*/_react.default.createElement(_SearchBoxItem.default, { key: `${id}_${groupName ?? ''}`, id: id, text: text, imageUrl: imageUrl, shouldShowSmallItems: shouldShowSmallItems, shouldShowRoundImage: shouldShowRoundImage, onSelect: handleSelect, groupName: groupName, tabIndex: index === 0 && listIndex === 0 ? 0 : -1 })); }); }); if (shouldAddInputToList && inputToListValue !== '') { items.push(/*#__PURE__*/_react.default.createElement(_SearchBoxItem.default, { id: "input-value", onSelect: handleSelect, shouldShowSmallItems: shouldShowSmallItems, text: `<b>${inputToListValue}</b`, tabIndex: items.length === 0 ? 0 : -1 })); } return items; }, [hintText, matchingListsItems, shouldAddInputToList, inputToListValue, value, hasMultipleGroups, shouldShowSmallItems, shouldShowRoundImage, handleSelect]); (0, _react.useEffect)(() => { const handleKeyDown = e => { var _contentRef$current2, _childrenArray$find; if (!shouldShowBody || matchingListsItems.length === 0) { return; } const children = (_contentRef$current2 = contentRef.current) === null || _contentRef$current2 === void 0 ? void 0 : _contentRef$current2.children; if (!children) { return; } const childrenArray = Array.from(children); const newChildren = (_childrenArray$find = childrenArray.find(child => child.id.startsWith('searchBoxContent__'))) === null || _childrenArray$find === void 0 ? void 0 : _childrenArray$find.children; if (!(newChildren && newChildren.length > 0)) { return; } const filteredChildren = Array.from(newChildren).filter(child => child.dataset.isgroupname !== 'true'); setFilteredChildrenArray(filteredChildren); if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { e.preventDefault(); if (newChildren && newChildren.length > 0) { const newIndex = focusedIndex !== null ? (focusedIndex + (e.key === 'ArrowUp' ? -1 : 1) + filteredChildren.length) % filteredChildren.length : 0; if (focusedIndex !== null) { const prevElement = filteredChildren[focusedIndex]; prevElement.tabIndex = -1; } setFocusedIndex(newIndex); const newElement = filteredChildren[newIndex]; newElement.tabIndex = 0; } } else if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); if (filteredChildren) { var _element$children$; const element = filteredChildren[focusedIndex ?? 0]; if (!element) { return; } const { id, textContent } = element; let imageUrl; // Just Ignore, it works // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if ((_element$children$ = element.children[0]) !== null && _element$children$ !== void 0 && _element$children$.attributes.src) { var _element$children$2; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access imageUrl = (_element$children$2 = element.children[0]) === null || _element$children$2 === void 0 ? void 0 : _element$children$2.attributes.src.nodeValue; } const newId = id.replace('search-box-item__', ''); handleSelect({ id: newId === 'input-value' ? textContent : newId, text: textContent ?? '', imageUrl }); } } }; document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('keydown', handleKeyDown); }; }, [filteredChildrenArray, focusedIndex, handleSelect, matchingListsItems.length, shouldShowBody]); const handleKeyPress = (0, _react.useCallback)(event => { if (event.keyCode === 27) { setMatchingListsItems([]); } }, []); (0, _react.useImperativeHandle)(ref, () => ({ clear: () => setValue('') }), [setValue]); (0, _react.useEffect)(() => { document.addEventListener('keydown', handleKeyPress); return () => { document.addEventListener('keydown', handleKeyPress); }; }, [handleKeyPress]); /** * Update the value if preset value changes */ (0, _react.useEffect)(() => { if (presetValue) { setValue(presetValue); } }, [presetValue, setValue]); const shouldShowDropdown = shouldShowBody && (matchingListsItems.length !== 0 || !!hintText) && (value.trim() !== '' || shouldShowContentOnEmptyInput); return (0, _react.useMemo)(() => /*#__PURE__*/_react.default.createElement(_SearchBox.StyledSearchBox, { ref: boxRef, key: `search-box-${uuid}`, onBlur: handleContainerBlur }, /*#__PURE__*/_react.default.createElement("div", { id: `search_box_input${uuid}` }, tagInputSettings ? /*#__PURE__*/_react.default.createElement(_TagInput.default, { leftElement: leftElement, onAdd: tagInputSettings.onAdd, onBlur: handleBlur, onChange: handleChange, onFocus: handleFocus, onRemove: tagInputSettings.onRemove, placeholder: placeholder, ref: tagInputRef, shouldAllowMultiple: tagInputSettings.shouldAllowMultiple, shouldEnableKeyboardHighlighting: shouldEnableKeyboardHighlighting, shouldPreventEnter: true, tags: tagInputSettings.tags }) : /*#__PURE__*/_react.default.createElement(_Input.default /* eslint-disable-next-line react/jsx-props-no-spreading */, _extends({}, inputProps, { isInvalid: isInvalid, leftElement: leftElement, onBlur: handleBlur, onChange: handleChange, onFocus: handleFocus, onKeyDown: handleKeyDown, placeholder: placeholder, ref: inputRef, rightElement: rightElement, shouldEnableKeyboardHighlighting: (inputProps === null || inputProps === void 0 ? void 0 : inputProps.shouldEnableKeyboardHighlighting) ?? shouldEnableKeyboardHighlighting, value: value }))), boxRef.current && /*#__PURE__*/_react.default.createElement(_DropdownBodyWrapper.default, { anchorElement: boxRef.current, container: container, direction: dropdownDirection, onClose: handleClose, onOutsideClick: handleDropdownOutsideClick, shouldShowDropdown: shouldShowDropdown }, /*#__PURE__*/_react.default.createElement(_SearchBoxBody.default, { filterButtons: filterButtons, height: height, maxHeight: maxHeight, shouldShow: shouldShowDropdown, key: `search-box-body-${uuid}`, onGroupSelect: handleFilterButtonsGroupSelect, ref: contentRef, selectedGroups: groups, shouldHideFilterButtons: shouldHideFilterButtons }, content))), [container, content, dropdownDirection, filterButtons, groups, handleBlur, handleChange, handleClose, handleDropdownOutsideClick, handleFocus, handleKeyDown, height, inputProps, isInvalid, leftElement, maxHeight, onKeyDown, placeholder, rightElement, shouldHideFilterButtons, shouldShowDropdown, shouldEnableKeyboardHighlighting, tagInputSettings, uuid, value]); }); SearchBox.displayName = 'SearchBox'; var _default = exports.default = SearchBox; //# sourceMappingURL=SearchBox.js.map