UNPKG

@hitachivantara/uikit-react-core

Version:
229 lines (228 loc) 8.78 kB
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 };