UNPKG

@chayns-components/core

Version:

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

645 lines (629 loc) • 21.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _chaynsApi = require("chayns-api"); var _react = require("motion/react"); var _react2 = _interopRequireWildcard(require("react")); var _reactDom = require("react-dom"); 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"); 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); } const SearchBox = /*#__PURE__*/(0, _react2.forwardRef)(({ isInvalid = false, placeholder, leftIcons, lists, onChange, onBlur, onSelect, onKeyDown, selectedId, container, shouldHideFilterButtons = false, shouldShowRoundImage, shouldShowContentOnEmptyInput = true, shouldAddInputToList = true, shouldShowToggleIcon = false, customFilter, presetValue }, ref) => { const [matchingListsItems, setMatchingListsItems] = (0, _react2.useState)(lists); const [selectedImage, setSelectedImage] = (0, _react2.useState)(); const [value, setValue] = (0, _react2.useState)(typeof presetValue === 'string' && presetValue !== '' ? presetValue : ''); const [isAnimating, setIsAnimating] = (0, _react2.useState)(false); const [height, setHeight] = (0, _react2.useState)(0); const [width, setWidth] = (0, _react2.useState)(0); const [focusedIndex, setFocusedIndex] = (0, _react2.useState)(null); const [hasMultipleGroups, setHasMultipleGroups] = (0, _react2.useState)(lists.length > 1); const [filteredChildrenArray, setFilteredChildrenArray] = (0, _react2.useState)(); const [inputToListValue, setInputToListValue] = (0, _react2.useState)(''); const [groups, setGroups] = (0, _react2.useState)(['all']); const [portal, setPortal] = (0, _react2.useState)(); const [internalCoordinates, setInternalCoordinates] = (0, _react2.useState)({ x: 0, y: 0 }); const [newContainer, setNewContainer] = (0, _react2.useState)(container ?? null); const uuid = (0, _uuid.useUuid)(); const boxRef = (0, _react2.useRef)(null); const contentRef = (0, _react2.useRef)(null); const inputRef = (0, _react2.useRef)(null); const hasFocusRef = (0, _react2.useRef)(false); const isAnimatingRef = (0, _react2.useRef)(false); const shouldShowPresetValue = (0, _react2.useRef)(typeof presetValue === 'string' && presetValue !== ''); const theme = (0, _styledComponents.useTheme)(); const { browser } = (0, _chaynsApi.getDevice)(); (0, _react2.useEffect)(() => { if (boxRef.current && !container) { const el = boxRef.current; const element = el.closest('.dialog-inner') || el.closest('body'); setNewContainer(element); } }, [container]); (0, _react2.useEffect)(() => { if (container instanceof Element) { setNewContainer(container); } }, [container]); (0, _react2.useEffect)(() => { if (boxRef.current) { const { x, y } = boxRef.current.getBoundingClientRect(); setInternalCoordinates({ x, y }); } }, []); /** * Checks if Lists are smaller then 1 */ (0, _react2.useEffect)(() => { setHasMultipleGroups(lists.length > 1); }, [lists]); const filterButtons = (0, _react2.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, _react2.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 = (0, _searchBox.searchList)({ items: list, searchString: value }); 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') { return customFilter(item); } return !(newMatchingItems.length === 1 && item.text === value); }) })); setMatchingListsItems(filteredMatchingListItems); return newLists; }, [groups, lists, customFilter, shouldAddInputToList, value]); const handleOpen = (0, _react2.useCallback)(() => { if (boxRef.current && newContainer) { const { left: comboBoxLeft, top: comboBoxTop, height: bodyHeight } = boxRef.current.getBoundingClientRect(); const { left, top } = newContainer.getBoundingClientRect(); const x = comboBoxLeft - left + newContainer.scrollLeft; const y = comboBoxTop - top + newContainer.scrollTop; setInternalCoordinates({ x, y: y + bodyHeight }); setIsAnimating(true); } }, [newContainer]); const handleClose = (0, _react2.useCallback)(() => { setIsAnimating(false); }, []); const handleFilterButtonsGroupSelect = keys => { setGroups(keys.length === 0 ? ['all'] : keys); }; /** * This function closes the list of items */ const handleOutsideClick = (0, _react2.useCallback)(event => { if (boxRef.current && !boxRef.current.contains(event.target) && contentRef.current && !contentRef.current.contains(event.target)) { handleClose(); } }, [handleClose]); /** * This hook listens for clicks */ (0, _react2.useEffect)(() => { document.addEventListener('click', handleOutsideClick); window.addEventListener('blur', () => handleClose()); return () => { document.removeEventListener('click', handleOutsideClick); window.addEventListener('blur', () => handleClose()); }; }, [handleOutsideClick, boxRef, handleClose]); /** * This hook calculates the height */ (0, _react2.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]); /** * This hook calculates the width */ (0, _react2.useEffect)(() => { const input = document.getElementById(`search_box_input${uuid}`); const getInputWidth = () => { if (input) { setWidth(input.offsetWidth); } }; if (input) { new ResizeObserver(getInputWidth).observe(input); } }, [uuid]); (0, _react2.useEffect)(() => { if (selectedId) { activeList.forEach(({ list }) => { const selectedItem = list.find(({ id }) => id === selectedId); if (selectedItem) { setValue(selectedItem.text); if (selectedItem.imageUrl) { setSelectedImage(/*#__PURE__*/_react2.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, _react2.useEffect)(() => { if (!selectedId && !shouldShowPresetValue.current) { setValue(''); } }, [selectedId]); (0, _react2.useEffect)(() => { isAnimatingRef.current = isAnimating; }, [isAnimating]); (0, _react2.useEffect)(() => { if (matchingListsItems.length !== 0 && !isAnimatingRef.current && hasFocusRef.current) { handleOpen(); } }, [handleOpen, matchingListsItems.length]); /** * This function sets the items on focus if shouldShowContentOnEmptyInput */ const handleFocus = (0, _react2.useCallback)(() => { hasFocusRef.current = true; if (shouldShowContentOnEmptyInput) { const newMatchingItems = []; activeList.forEach(({ list, groupName }) => { const newList = (0, _searchBox.searchList)({ items: list, searchString: value }); 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') { return customFilter(item); } return !(newMatchingItems.length === 1 && item.text === value); }) })); setMatchingListsItems(filteredMatchingListItems); if (filteredMatchingListItems.length !== 0) { handleOpen(); } } }, [activeList, handleOpen, customFilter, shouldAddInputToList, shouldShowContentOnEmptyInput, value]); /** * This function filters the lists by input */ (0, _react2.useEffect)(() => { const newMatchingItems = []; activeList.forEach(({ list, groupName }) => { const newList = (0, _searchBox.searchList)({ items: list, searchString: value }); 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, value]); const handleClick = (0, _react2.useCallback)(() => { if (isAnimating) { handleClose(); } else { handleOpen(); } }, [handleClose, handleOpen, isAnimating]); const rightElement = (0, _react2.useMemo)(() => { if (!shouldShowToggleIcon) { return undefined; } return /*#__PURE__*/_react2.default.createElement(_SearchBox.StyledSearchBoxIcon, { onClick: handleClick }, /*#__PURE__*/_react2.default.createElement(_Icon.default, { icons: ['fa fa-chevron-down'], color: theme['006'] })); }, [handleClick, shouldShowToggleIcon, theme]); const leftElement = (0, _react2.useMemo)(() => /*#__PURE__*/_react2.default.createElement(_SearchBox.StyledSearchBoxLeftWrapper, null, leftIcons && /*#__PURE__*/_react2.default.createElement(_Icon.default, { icons: leftIcons }), selectedImage && selectedImage), [leftIcons, selectedImage]); /** * This function handles changes of the input */ const handleChange = (0, _react2.useCallback)(event => { const filteredLists = []; shouldShowPresetValue.current = false; activeList.forEach(({ list, groupName }) => { const newList = (0, _searchBox.searchList)({ items: list, searchString: event.target.value }); 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); } }, [activeList, handleOpen, onChange, shouldAddInputToList, shouldShowContentOnEmptyInput]); /** * This function handles the blur event of the input */ const handleBlur = (0, _react2.useCallback)(event => { hasFocusRef.current = false; if (typeof onBlur === 'function') { onBlur(event); } }, [onBlur]); /** * This function handles the item selection */ const handleSelect = (0, _react2.useCallback)(item => { const newItem = { ...item, text: item.text.replace('<b>', '').replace('</b>', '').replace('</b', '') }; setValue(newItem.text); handleClose(); setSelectedImage(newItem.imageUrl ? /*#__PURE__*/_react2.default.createElement(_SearchBoxItem2.StyledSearchBoxItemImage, { src: newItem.imageUrl, $shouldShowRoundImage: shouldShowRoundImage }) : undefined); setMatchingListsItems([]); if (typeof onSelect === 'function') { onSelect(newItem); } }, [handleClose, onSelect, shouldShowRoundImage]); const content = (0, _react2.useMemo)(() => { const items = []; matchingListsItems.forEach(({ groupName, list }, index) => { if (hasMultipleGroups) { if (list.length <= 0) { return; } if (index !== 0) { items.push(/*#__PURE__*/_react2.default.createElement(_GroupName.default, { key: groupName, name: groupName ?? '' })); } } list.forEach(({ id, text, imageUrl }) => { items.push(/*#__PURE__*/_react2.default.createElement(_SearchBoxItem.default, { key: `${id}_${groupName ?? ''}`, id: id, text: text, imageUrl: imageUrl, shouldShowRoundImage: shouldShowRoundImage, onSelect: handleSelect, groupName: groupName })); }); }); if (shouldAddInputToList && inputToListValue !== '') { items.push(/*#__PURE__*/_react2.default.createElement(_SearchBoxItem.default, { id: "input-value", onSelect: handleSelect, text: `<b>${inputToListValue}</b` })); } return items; }, [matchingListsItems, shouldAddInputToList, inputToListValue, hasMultipleGroups, shouldShowRoundImage, handleSelect]); (0, _react2.useEffect)(() => { const handleKeyDown = e => { if (!isAnimating || matchingListsItems.length === 0) { return; } if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { var _contentRef$current, _childrenArray$find; e.preventDefault(); const children = (_contentRef$current = contentRef.current) === null || _contentRef$current === void 0 ? void 0 : _contentRef$current.children; if (!children) { return; } const childrenArray = Array.from(children); const newChildren = (_childrenArray$find = childrenArray.find(child => child.id.startsWith('searchbox-content__'))) === null || _childrenArray$find === void 0 ? void 0 : _childrenArray$find.children; if (newChildren && newChildren.length > 0) { const filteredChildren = Array.from(newChildren).filter(child => child.dataset.isgroupname !== 'true'); setFilteredChildrenArray(filteredChildren); 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; newElement.focus(); } } else if (e.key === 'Enter' && focusedIndex !== null) { if (filteredChildrenArray) { var _element$children$; const element = filteredChildrenArray[focusedIndex]; 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; } handleSelect({ id: id.replace('search-box-item__', ''), text: textContent ?? '', imageUrl }); } } }; document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('keydown', handleKeyDown); }; }, [filteredChildrenArray, focusedIndex, handleSelect, isAnimating, matchingListsItems.length]); const handleKeyPress = (0, _react2.useCallback)(event => { if (event.keyCode === 27) { setMatchingListsItems([]); } }, []); (0, _react2.useImperativeHandle)(ref, () => ({ clear: () => setValue('') }), []); (0, _react2.useEffect)(() => { document.addEventListener('keydown', handleKeyPress); return () => { document.addEventListener('keydown', handleKeyPress); }; }, [handleKeyPress]); /** * Update the value if preset value changes */ (0, _react2.useEffect)(() => { if (presetValue) { setValue(presetValue); } }, [presetValue]); (0, _react2.useEffect)(() => { if (!newContainer) { return; } setPortal(() => /*#__PURE__*/(0, _reactDom.createPortal)(/*#__PURE__*/_react2.default.createElement(_react.AnimatePresence, { initial: false, key: `search-box-body-animation-wrapper-${uuid}` }, isAnimating && matchingListsItems.length !== 0 && (value.trim() !== '' || shouldShowContentOnEmptyInput) && /*#__PURE__*/_react2.default.createElement(_SearchBoxBody.default, { key: `search-box-body-${uuid}`, filterButtons: filterButtons, selectedGroups: groups, width: width, coordinates: internalCoordinates, browser: browser === null || browser === void 0 ? void 0 : browser.name, height: height, ref: contentRef, onGroupSelect: handleFilterButtonsGroupSelect, shouldHideFilterButtons: shouldHideFilterButtons }, content)), newContainer)); }, [browser === null || browser === void 0 ? void 0 : browser.name, newContainer, content, filterButtons, groups, height, internalCoordinates, isAnimating, width, shouldHideFilterButtons, matchingListsItems.length, value, shouldShowContentOnEmptyInput, uuid]); return (0, _react2.useMemo)(() => /*#__PURE__*/_react2.default.createElement(_SearchBox.StyledSearchBox, { ref: boxRef, key: `search-box-${uuid}` }, /*#__PURE__*/_react2.default.createElement("div", { id: `search_box_input${uuid}` }, /*#__PURE__*/_react2.default.createElement(_Input.default, { isInvalid: isInvalid, ref: inputRef, onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, placeholder: placeholder, onKeyDown: onKeyDown, leftElement: leftElement, rightElement: rightElement, value: value })), portal), [handleBlur, handleChange, handleFocus, isInvalid, leftElement, onKeyDown, placeholder, portal, rightElement, uuid, value]); }); SearchBox.displayName = 'SearchBox'; var _default = exports.default = SearchBox; //# sourceMappingURL=SearchBox.js.map