UNPKG

@hitachivantara/uikit-react-core

Version:
199 lines (198 loc) 6.74 kB
import { HvButton } from "../../Button/Button.js"; import { HvActionBar } from "../../ActionBar/ActionBar.js"; import { HvCheckBox } from "../../CheckBox/CheckBox.js"; import { CounterLabel } from "../../utils/CounterLabel.js"; import { HvList } from "../../List/List.js"; import { HvInput } from "../../Input/Input.js"; import { getSelected } from "../utils.js"; import { useClasses } from "./List.styles.js"; import { theme } from "@hitachivantara/uikit-styles"; import { mergeStyles, useDefaultProps } from "@hitachivantara/uikit-react-utils"; import { useEffect, useMemo, useState } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; //#region src/Dropdown/List/List.tsx /** * The values property was being deeply cloned. That created a significant performance * hit when the values contained complex properties' values, like React Nodes. * * For minimizing the impact of removing the clone, a shallow clone of the array and its * objects is performed instead. That should have the same effect in the majority of the * cases, where the properties' values are primitive. */ var clone = (values) => values.map((value) => ({ ...value })); /** * Set all hidden's to false. */ var cleanHidden = (lst) => lst.map((item) => ({ ...item, isHidden: false })); var valuesExist = (values) => values != null && values?.length > 0; /** Filter selected ordered element `id`s (or `label`) */ var getSelectedIds = (list) => getSelected(list).map((item) => item.id || item.label); var HvDropdownList = (props) => { const { id, classes: classesProp, values = [], multiSelect = false, showSearch = false, onChange, onCancel, labels, notifyChangesOnFirstRender = false, singleSelectionToggle, height: heightProp, popperStyles, maxHeight: maxHeightProp, virtualized = false, ...others } = useDefaultProps("HvDropdownList", props); const { classes, cx } = useClasses(classesProp); const [searchStr, setSearchStr] = useState(""); const [list, setList] = useState(clone(values)); const [allSelected, setAllSelected] = useState(false); const [anySelected, setAnySelected] = useState(false); const { maxWidth, maxHeight } = popperStyles || {}; const hasChanges = useMemo(() => { return String(getSelectedIds(values)) !== String(getSelectedIds(list)); }, [list, values]); /** * Update states associated with select all. */ const updateSelectAll = (listValues) => { if (!listValues) return; const nbrSelected = getSelected(listValues).length; const hasSelection = nbrSelected > 0; const allSelect = nbrSelected === listValues.length; setAnySelected(hasSelection); setAllSelected(hasSelection && allSelect); }; /** * After the first render, call onChange if notifyChangesOnFirstRender. */ useEffect(() => { if (!valuesExist(values)) return; setList(clone(values)); updateSelectAll(values); if (notifyChangesOnFirstRender) onChange?.(values, false, false, true); }, [ values, notifyChangesOnFirstRender, onChange ]); /** * Sets the filtered values to the state. * * @param {String} str - The value that is being looked. */ const handleSearch = (str) => { const results = list?.filter(({ searchValue, label, value }) => { return (typeof searchValue === "string" && searchValue || typeof label === "string" && label || typeof value === "string" && value || "").toLowerCase().indexOf(str.toLowerCase()) >= 0; }); if (results != null) { setList(list.map((elem) => { const isResult = results.find((result) => result.label === elem.label); return { ...elem, isHidden: !isResult }; })); setSearchStr(str); } return str; }; /** * Create search element. * * @returns {*} */ const renderSearch = () => /* @__PURE__ */ jsx("div", { className: classes.searchContainer, children: /* @__PURE__ */ jsx(HvInput, { type: "search", value: searchStr, placeholder: labels?.searchPlaceholder, "aria-label": labels?.searchPlaceholder, onChange: (event, str) => handleSearch(str) }) }); /** * Select all the values inside the dropdown. * */ const handleSelectAll = () => { const newList = list.map((elem) => { if (elem.disabled) return elem; return { ...elem, selected: !anySelected }; }); setList(newList); updateSelectAll(newList); }; const renderSelectAll = () => { return /* @__PURE__ */ jsx(HvCheckBox, { label: /* @__PURE__ */ jsx(CounterLabel, { selected: getSelected(list).length, total: list.length, conjunctionLabel: labels?.multiSelectionConjunction }), onChange: handleSelectAll, className: classes.selectAll, indeterminate: anySelected && !allSelected, checked: allSelected }); }; /** * When selecting the state list is updated with the corresponding selection. * * @param listValues - elements selected. */ const onSelection = (listValues) => { if (!multiSelect) onChange(cleanHidden(listValues), true, true, true); else { updateSelectAll(listValues); setList(clone(listValues)); } }; /** * Render action buttons. */ const renderActions = () => { return /* @__PURE__ */ jsxs(HvActionBar, { children: [/* @__PURE__ */ jsx(HvButton, { disabled: !hasChanges, onClick: () => onChange(cleanHidden(list), true, true, true), variant: "primaryGhost", children: labels?.applyLabel }), /* @__PURE__ */ jsx(HvButton, { onClick: onCancel, variant: "primaryGhost", children: labels?.cancelLabel })] }); }; const showList = valuesExist(values); /** bottom margin + Panel padding + Search size + SelectAll + ActionBar size */ const elementsSize = theme.spacing(7 + (showSearch ? 5 : 0) + (showList && multiSelect ? 10 : 0)); return /* @__PURE__ */ jsxs("div", { className: classes.rootList, children: [ /* @__PURE__ */ jsx("div", { className: classes.listBorderDown }), /* @__PURE__ */ jsxs("div", { className: classes.listContainer, children: [ showSearch && renderSearch(), showList && multiSelect && renderSelectAll(), showList && /* @__PURE__ */ jsx(HvList, { style: mergeStyles(void 0, { height: heightProp, "--maxW": maxWidth, "--maxH": maxHeightProp ?? `calc(${maxHeight} - ${elementsSize})` }), classes: { root: cx(classes.dropdownListContainer, { [classes.virtualized]: virtualized }) }, values: list, multiSelect, useSelector: multiSelect, showSelectAll: false, onChange: onSelection, labels: { selectionConjunction: labels?.multiSelectionConjunction }, selectable: true, condensed: true, singleSelectionToggle, height: heightProp, virtualized, ...others }) ] }), showList && multiSelect ? renderActions() : null ] }); }; //#endregion export { HvDropdownList };