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