@hitachivantara/uikit-react-core
Version:
UI Kit Core React components.
199 lines (198 loc) • 6.74 kB
JavaScript
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 };