UNPKG

@sitecore/sc-contenthub-blok

Version:

A UI library for [Sitecore Content Hub](https://doc.sitecore.com/ch/).

298 lines 14.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BlokSelect = BlokSelect; const jsx_runtime_1 = require("react/jsx-runtime"); const css_1 = require("@emotion/css"); const react_1 = require("react"); const useIsMounted_1 = __importDefault(require("../../customHooks/useIsMounted")); const Foundations_1 = require("../../Foundations"); const utils_1 = require("../../Foundations/utils"); const utils_2 = require("../../Foundations/utils"); const useIsDarkTheme_1 = require("../../useIsDarkTheme"); const BlokButtonSelect_1 = require("../BlokButtonSelect/BlokButtonSelect"); const BlokIcon_1 = require("../BlokIcon/BlokIcon"); const BlokSearchBox_1 = require("../BlokSearchBox/BlokSearchBox"); const CoreComponents_1 = require("../CoreComponents"); const classes = { dropDownAutoWidth: (0, css_1.css)({ width: "auto", }), dropdownFullWidth: (0, css_1.css)({ width: `${Foundations_1.inputMaxWidth}px`, display: "block", maxWidth: "100%", overflow: "hidden", }), select: (0, css_1.css)({ backgroundColor: "transparent !important ", maxWidth: "calc(100% - 40px)", display: "flex", alignItems: "center", }), blockSelect: (0, css_1.css)({ backgroundColor: "transparent !important ", maxWidth: "calc(100% - 40px)", overflow: "hidden", textOverflow: "ellipsis", lineHeight: 1.5, display: "block !important", }), selectIcon: (0, css_1.css)({ color: Foundations_1.blackAlpha[900], top: "50%", transform: "translateY(-50%)", right: utils_1.isLTR ? "7px !important" : "auto", left: utils_1.isRTL ? "7px !important" : "auto", position: "absolute", pointerEvents: "none", cursor: "pointer", svg: (0, css_1.css)({ width: "22px !important", height: "22px !important", verticalAlign: "middle", marginInlineStart: "calc(0.375rem * -1)!important", marginInlineEnd: "calc(0.375rem * -1)!important", padding: "0.125rem !important", }), }), selectIconOnDark: (0, css_1.css)({ color: Foundations_1.whiteAlpha[900], }), checkBox: (0, css_1.css)({ marginLeft: -"8px", }), noneLabel: (0, css_1.css)({ color: Foundations_1.blackAlpha["800"], }), noneLabelOnDark: (0, css_1.css)({ color: Foundations_1.whiteAlpha["800"], }), transparentDropdownButton: (0, css_1.css)({ backgroundColor: "transparent", }), searchBoxWithButton: (0, css_1.css)({ margin: "0 0.5rem 0.5rem 0.5rem" }), dropdownItemIcon: (0, css_1.css)({ color: Foundations_1.blackAlpha[600], }), hidden: (0, css_1.css)({ display: "none", }), lightText: (0, css_1.css)({ color: Foundations_1.blackAlpha["500"] }), iconLabel: (0, css_1.css)({ marginLeft: "8px", verticalAlign: "middle", maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", lineHeight: 1.5, }), }; function BlokSelect({ id, options = (0, utils_2.getEmptyImmutableArray)(), onChange: onChange_, value, labelMapper = (item) => item, valueMapper = (item) => item, returnValue, placeholder = "Select an option", emptyValueSelectable = false, className, isReadOnly, searchBoxMinAmountOfItems = 8, tooltip = "", showIconAndText = false, hideItemIcons = false, isFullWidth = false, accessibilityId, maxHeight = 250, itemsToDisable = (0, utils_2.getEmptyImmutableArray)(), openMenuByDefault = false, showEmptyDropdown = false, noneLabel = "None", noResultsText = "No results found", clearMessage = "Clear", searchMessage = "Search", }) { const [filteredOptions, setFilteredOptions] = (0, react_1.useState)(options); const [searchFilter, setSearchFilter] = (0, react_1.useState)(""); const selectRef = (0, react_1.useRef)(null); const dropdownClasses = (0, react_1.useMemo)(() => ({ select: showIconAndText ? classes.select : classes.blockSelect, }), [showIconAndText]); const searchBoxRef = (0, react_1.useCallback)((node) => { if (node) { node.focus(); } }, []); const [isMenuVisible, setIsMenuVisible] = (0, react_1.useState)(openMenuByDefault); const [toolTipIsShown, setToolTipIsShown] = (0, react_1.useState)(false); const isMountedRef = (0, useIsMounted_1.default)(); const isDarkTheme = (0, useIsDarkTheme_1.useIsDarkTheme)(); const customDropdownIcon = (0, react_1.useCallback)(() => { return ((0, jsx_runtime_1.jsx)(BlokIcon_1.BlokIcon, { icon: "chevron-down", classes: [ classes.selectIcon, isDarkTheme ? classes.selectIconOnDark : null, ] })); }, [isDarkTheme]); // When clicking the clear button on the searchbox clear search value and set the list back to the original values. const onClearSearch = (0, react_1.useCallback)(() => { setFilteredOptions(options); setSearchFilter(""); }, [options]); // When selecting a value in the dropdown. const onChange = (0, react_1.useCallback)(async (event) => { onClearSearch(); const key = event.target.value; const newValue = options?.find((item) => valueMapper(item) === key); if ((0, utils_2.isFunction)(onChange_)) { if (newValue) { if ((0, utils_2.isFunction)(returnValue)) { await onChange_(returnValue(newValue)); } else { await onChange_(newValue); } } if (newValue == null) { if (emptyValueSelectable) { await onChange_(newValue); } } } if (isMountedRef.current) { // Hide the menu. setIsMenuVisible(false); } }, [ emptyValueSelectable, isMountedRef, valueMapper, onChange_, onClearSearch, options, returnValue, ]); // When clicking the enter key in the search box select the first value in the list and close the menu. const onEnterPress = (0, react_1.useCallback)(async () => { if (filteredOptions.length) { // Grab the first value in the filtered options list. const valueToSelect = filteredOptions[0]; if ((0, utils_2.isFunction)(returnValue)) { await onChange_(returnValue(valueToSelect)); } else { await onChange_(valueToSelect); } if (isMountedRef.current) { // Restore the filtered options to the options. setFilteredOptions(options); // Hide the menu. setIsMenuVisible(false); } } }, [filteredOptions, isMountedRef, onChange_, options, returnValue]); (0, react_1.useEffect)(() => { if (!options.length) { return; } if (searchFilter !== "") { setFilteredOptions(options.filter((option) => labelMapper(option) .toLocaleLowerCase() .includes(searchFilter.toLocaleLowerCase()))); } else { setFilteredOptions(options); } }, [labelMapper, options, searchFilter]); const hideToolTip = (0, react_1.useCallback)(() => setToolTipIsShown(false), []); const showToolTip = (0, react_1.useCallback)(() => { if (!isMenuVisible) { setToolTipIsShown(true); } }, [isMenuVisible]); const renderValue = (0, react_1.useCallback)((selected) => { const selectedItem = options.find((item) => valueMapper(item) === selected); if (!selectedItem) { return (0, jsx_runtime_1.jsx)("span", { children: placeholder ?? noneLabel }); } /** This can maybe be refined in the future this way we show the selected icon on the search template dropdown. */ if (Object.prototype.hasOwnProperty.call(selectedItem, "icon")) { return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(BlokIcon_1.BlokIcon, { icon: selectedItem.icon, classes: [classes.dropdownItemIcon] }), showIconAndText && ((0, jsx_runtime_1.jsx)("span", { className: classes.iconLabel, children: labelMapper(selectedItem) }))] })); } return labelMapper(selectedItem); }, [ options, labelMapper, valueMapper, placeholder, noneLabel, showIconAndText, ]); const onOpen = (0, react_1.useCallback)(() => { setIsMenuVisible(true); hideToolTip(); }, [hideToolTip]); const filteredItems = (0, react_1.useMemo)(() => { return filteredOptions?.map((option, index) => { const mappedValue = valueMapper(option); return (option && ((0, jsx_runtime_1.jsxs)(CoreComponents_1.MenuItem, { value: mappedValue, onKeyDown: (e) => { if (e.key === "Escape") { setIsMenuVisible(false); setSearchFilter(""); } }, disabled: itemsToDisable.some((item) => mappedValue === valueMapper(item)), onClick: async () => { // When there is only one option in the list and label // and value of the option are the same, clicking on the item will // not be detected, hence we need this custom click logic. if (filteredOptions.length !== 1 || typeof valueMapper !== "function" || typeof labelMapper !== "function" || mappedValue !== labelMapper(option)) { return; } if ((0, utils_2.isFunction)(returnValue)) { await onChange_(returnValue(option)); } else { await onChange_(option); } if (isMountedRef.current) { // Restore the filtered options to the options. setFilteredOptions(options); // Hide the menu. setIsMenuVisible(false); } }, children: [Object.prototype.hasOwnProperty.call(option, "icon") && !hideItemIcons && ((0, jsx_runtime_1.jsx)(CoreComponents_1.ListItemIcon, { children: (0, jsx_runtime_1.jsx)(BlokIcon_1.BlokIcon, { icon: option.icon }) })), (0, jsx_runtime_1.jsx)(CoreComponents_1.ListItemText, { primary: labelMapper(option) })] }, `${mappedValue}-${index}`))); }); }, [ filteredOptions, valueMapper, itemsToDisable, hideItemIcons, labelMapper, returnValue, isMountedRef, onChange_, options, ]); const searchBox = (0, react_1.useMemo)(() => options.length > searchBoxMinAmountOfItems && ((0, jsx_runtime_1.jsx)(BlokSearchBox_1.BlokSearchBox, { onSearch: setSearchFilter, onClearSearch: onClearSearch, value: searchFilter, ref: searchBoxRef, onEnterPress: onEnterPress, isInsideDropdown: true, hasGutterBottom: false, clearMessage: clearMessage, searchMessage: searchMessage })), [ clearMessage, onClearSearch, onEnterPress, options.length, searchBoxMinAmountOfItems, searchBoxRef, searchFilter, searchMessage, ]); const menuCloseHandler = (0, react_1.useCallback)(() => { setIsMenuVisible(false); setSearchFilter(""); }, []); if (!options.length && !showEmptyDropdown) { return null; } const selectedValue = (value && valueMapper(value)) || ""; return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: isReadOnly ? ((0, jsx_runtime_1.jsx)("div", { "data-testid": id, children: (0, jsx_runtime_1.jsx)(CoreComponents_1.Typography, { variant: "body1", children: typeof value === "object" ? labelMapper(value) : (value ?? "") }) })) : ((0, jsx_runtime_1.jsx)(CoreComponents_1.Tooltip, { title: toolTipIsShown ? tooltip : "", children: (0, jsx_runtime_1.jsxs)(CoreComponents_1.Select, { ref: selectRef, "data-testid": id, onChange: onChange, className: (0, css_1.cx)(className, isFullWidth ? classes.dropdownFullWidth : classes.dropDownAutoWidth), classes: dropdownClasses, value: filteredOptions.some((option) => valueMapper(option) === selectedValue) ? selectedValue : searchFilter, open: isMenuVisible, displayEmpty: true, onMouseEnter: showToolTip, onMouseLeave: hideToolTip, onOpen: onOpen, onClose: menuCloseHandler, renderValue: renderValue, labelId: `label-${accessibilityId}`, onKeyDown: (e) => { e.stopPropagation(); }, variant: "filled", MenuProps: { anchorOrigin: (0, BlokButtonSelect_1.getPopoverOrigin)(selectRef.current, "anchor"), transformOrigin: (0, BlokButtonSelect_1.getPopoverOrigin)(selectRef.current, "transform"), slotProps: { paper: { style: { maxHeight, }, }, }, autoFocus: options.length < searchBoxMinAmountOfItems, }, IconComponent: customDropdownIcon, children: [searchBox, value === null && ((0, jsx_runtime_1.jsx)(CoreComponents_1.MenuItem, { value: undefined, className: classes.hidden, children: (0, jsx_runtime_1.jsx)("em", { className: classes.lightText, children: placeholder }) })), emptyValueSelectable && searchFilter === "" && ( // We need to set the value to null so that it is picked up correctly. (0, jsx_runtime_1.jsx)(CoreComponents_1.MenuItem, { value: null, children: (0, jsx_runtime_1.jsx)("em", { children: noneLabel }) })), !filteredOptions.length && noResultsText, filteredItems] }) })) })); } //# sourceMappingURL=BlokSelect.js.map