@chayns-components/core
Version:
A set of beautiful React components for developing your own applications with chayns.
688 lines (674 loc) • 25.3 kB
JavaScript
"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