@hitachivantara/uikit-react-core
Version:
UI Kit Core React components.
229 lines (228 loc) • 8.78 kB
JavaScript
import { fixedForwardRef } from "../types/generic.js";
import { HvTypography } from "../Typography/Typography.js";
import { setId } from "../utils/setId.js";
import { useUniqueId } from "../hooks/useUniqueId.js";
import { isInvalid } from "../FormElement/utils.js";
import { HvFormElement } from "../FormElement/FormElement.js";
import { HvWarningText } from "../FormElement/WarningText/WarningText.js";
import { useControlled as useControlled$1 } from "../hooks/useControlled.js";
import { HvBaseDropdown } from "../BaseDropdown/BaseDropdown.js";
import { HvLabelContainer } from "../FormElement/LabelContainer.js";
import { useLabels } from "../hooks/useLabels.js";
import { CounterLabel } from "../utils/CounterLabel.js";
import { useClasses } from "./Dropdown.styles.js";
import { getSelected, getSelectionLabel } from "./utils.js";
import { HvDropdownList } from "./List/List.js";
import { useDefaultProps } from "@hitachivantara/uikit-react-utils";
import { useEffect, useRef, useState } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
import { useForkRef } from "@mui/material/utils";
//#region src/Dropdown/Dropdown.tsx
var DEFAULT_LABELS = {
/** Label for overwrite the default header behavior. */
select: void 0,
/** Cancel button label. */
cancelLabel: "Cancel",
/** Apply button label. */
applyLabel: "Apply",
/** The label used in the middle of the multiSelection count. */
searchPlaceholder: "Search",
/** The label used in search. */
multiSelectionConjunction: "/"
};
/**
* A dropdown list is a graphical control element, similar to a list box, that allows the user to choose one value from a list.
*/
var HvDropdown = fixedForwardRef(function HvDropdown(props, ref) {
const { classes: classesProp, className, id, name, required, disabled, readOnly, label, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, description, "aria-describedby": ariaDescribedBy, placeholder = "Select...", onChange, status, statusMessage, "aria-errormessage": ariaErrorMessage, onCancel, onToggle, onClickOutside, onFocus, onBlur, values, multiSelect = false, showSearch, expanded, defaultExpanded, notifyChangesOnFirstRender, labels: labelsProp, disablePortal, singleSelectionToggle = true, placement, variableWidth, popperProps = {}, height, maxHeight, virtualized, baseDropdownProps = {}, listProps = {}, ...others } = useDefaultProps("HvDropdown", props);
const { classes, cx } = useClasses(classesProp);
const labels = useLabels(DEFAULT_LABELS, labelsProp);
const elementId = useUniqueId(id);
const [popperStyles, setPopperStyles] = useState();
const [validationState, setValidationState] = useControlled$1(status, "standBy");
const [validationMessage] = useControlled$1(statusMessage, "Required");
const [isOpen, setIsOpen] = useControlled$1(expanded, Boolean(defaultExpanded));
const [selectionLabel, setSelectionLabel] = useState(getSelectionLabel(labels, placeholder, multiSelect, values));
const [internalValues, setInternalValues] = useState(values);
const internalValuesRef = useRef(values);
useEffect(() => {
setInternalValues(values);
internalValuesRef.current = values;
}, [values]);
useEffect(() => {
setSelectionLabel(getSelectionLabel(labels, placeholder, multiSelect, values));
}, [
labels,
multiSelect,
placeholder,
values
]);
const dropdownHeaderRef = useRef(void 0);
const { ref: refProp, dropdownHeaderRef: dropdownHeaderRefProp, ...otherBaseDropdownProps } = baseDropdownProps;
const headerForkedRef = useForkRef(dropdownHeaderRefProp, dropdownHeaderRef);
const dropdownForkedRef = useForkRef(ref, refProp);
const handleToggle = (event, open) => {
onToggle?.(event, open);
setIsOpen(open);
if (!open) setValidationState(() => {
if (required) {
if (!(getSelected(internalValuesRef.current).length > 0)) return "invalid";
}
return "valid";
});
};
/** Applies the selected values to the state */
const handleSelection = (listValues, commitChanges, toggle, notifyChanges = true) => {
const selected = getSelected(listValues);
if (commitChanges) {
setInternalValues(listValues);
internalValuesRef.current = listValues;
setSelectionLabel(getSelectionLabel(labels, placeholder, multiSelect, listValues));
setValidationState(() => {
if (required && selected.length === 0) return "invalid";
return "valid";
});
}
if (notifyChanges) onChange?.(multiSelect ? selected : selected[0]);
if (toggle) {
handleToggle(void 0, false);
dropdownHeaderRef.current?.focus({ preventScroll: true });
}
};
/**
* Handles the `Cancel` action. Both single and ranged modes are handled here.
*/
const handleCancel = (evt) => {
onCancel?.(evt);
handleToggle(evt, false);
dropdownHeaderRef.current?.focus({ preventScroll: true });
};
const handleClickOutside = (evt) => {
onClickOutside?.(evt);
onCancel?.(evt);
};
const setFocusToContent = (containerRef) => {
const inputs = containerRef?.getElementsByTagName("input");
if (inputs && inputs.length > 0) {
inputs[0].focus();
return;
}
(containerRef != null ? [...containerRef.getElementsByTagName("li")] : []).every((listItem) => {
if (listItem.tabIndex >= 0) {
listItem.focus();
return false;
}
return true;
});
};
const buildHeaderLabel = () => {
const hasSelection = getSelected(internalValues).length > 0;
return labels?.select || !multiSelect ? /* @__PURE__ */ jsx(HvTypography, {
component: "div",
variant: "body",
className: cx(classes.placeholder, {
[classes.selectionDisabled]: disabled,
[classes.placeholderClosed]: !(isOpen || hasSelection)
}),
children: selectionLabel.selected
}) : /* @__PURE__ */ jsx(CounterLabel, {
selected: selectionLabel.selected,
total: selectionLabel.total,
conjunctionLabel: labels.multiSelectionConjunction,
className: cx(classes.placeholder, { [classes.selectionDisabled]: disabled })
});
};
const canShowError = ariaErrorMessage == null && (status !== void 0 && statusMessage !== void 0 || status === void 0 && required);
const isStateInvalid = isInvalid(validationState);
let errorMessageId;
if (isStateInvalid) errorMessageId = canShowError ? setId(elementId, "error") : ariaErrorMessage;
return /* @__PURE__ */ jsxs(HvFormElement, {
id,
name,
status: validationState,
disabled,
readOnly,
required,
className: cx(classes.root, { [classes.disabled]: disabled }, className),
...others,
children: [
/* @__PURE__ */ jsx(HvLabelContainer, {
label,
description,
labelId: setId(elementId, "label"),
descriptionId: setId(elementId, "description"),
classes: {
root: classes.labelContainer,
label: classes.label,
description: classes.description
}
}),
/* @__PURE__ */ jsx(HvBaseDropdown, {
ref: dropdownForkedRef,
id: setId(id, "dropdown"),
classes: {
root: cx(classes.dropdown, { [classes.readOnly]: readOnly }),
arrow: classes.arrow,
header: cx(classes.dropdownHeader, { [classes.dropdownHeaderInvalid]: isStateInvalid }),
headerOpen: classes.dropdownHeaderOpen
},
expanded: isOpen,
disabled,
readOnly,
required,
disablePortal,
placement,
popperProps,
placeholder: buildHeaderLabel(),
onToggle: handleToggle,
onClickOutside: handleClickOutside,
onContainerCreation: (el, state) => {
setFocusToContent(el, state);
setPopperStyles(state.elements?.popper.style);
},
role: "combobox",
variableWidth,
"aria-label": ariaLabel,
"aria-labelledby": [label && setId(elementId, "label"), ariaLabelledBy].join(" ").trim() || void 0,
"aria-invalid": isStateInvalid ? true : void 0,
"aria-errormessage": errorMessageId,
"aria-describedby": [description && setId(elementId, "description"), ariaDescribedBy].join(" ").trim() || void 0,
onFocus,
onBlur,
dropdownHeaderRef: headerForkedRef,
...otherBaseDropdownProps,
children: /* @__PURE__ */ jsx(HvDropdownList, {
id: setId(elementId, "values"),
classes: {
rootList: classes.rootList,
dropdownListContainer: classes.dropdownListContainer
},
values: internalValues,
multiSelect,
popperStyles,
showSearch,
onChange: handleSelection,
onCancel: handleCancel,
labels,
notifyChangesOnFirstRender,
singleSelectionToggle,
"aria-label": ariaLabel,
"aria-labelledby": label ? setId(elementId, "label") : void 0,
height,
maxHeight,
virtualized,
"data-is-dropdown": true,
...listProps
})
}),
canShowError && /* @__PURE__ */ jsx(HvWarningText, {
id: setId(elementId, "error"),
disableBorder: true,
className: classes.error,
children: validationMessage
})
]
});
});
//#endregion
export { HvDropdown };