@sitecore/sc-contenthub-blok
Version:
A UI library for [Sitecore Content Hub](https://doc.sitecore.com/ch/).
298 lines • 14.4 kB
JavaScript
;
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