UNPKG

@hitachivantara/uikit-react-core

Version:

Core React components for the NEXT Design System.

316 lines (315 loc) 10 kB
import { jsxs, jsx } from "react/jsx-runtime"; import { useState, useRef, useEffect } from "react"; import { useForkRef } from "@mui/material/utils"; import { useDefaultProps } from "@hitachivantara/uikit-react-utils"; import { HvLabelContainer } from "../FormElement/LabelContainer.js"; import { useControlled } from "../hooks/useControlled.js"; import { useLabels } from "../hooks/useLabels.js"; import { useUniqueId } from "../hooks/useUniqueId.js"; import { fixedForwardRef } from "../types/generic.js"; import { CounterLabel } from "../utils/CounterLabel.js"; import { setId } from "../utils/setId.js"; import { useClasses } from "./Dropdown.styles.js"; import { staticClasses } from "./Dropdown.styles.js"; import { getSelectionLabel, getSelected } from "./utils.js"; import { HvDropdownList } from "./List/List.js"; import { HvFormElement } from "../FormElement/FormElement.js"; import { HvBaseDropdown } from "../BaseDropdown/BaseDropdown.js"; import { isInvalid } from "../FormElement/utils.js"; import { HvWarningText } from "../FormElement/WarningText/WarningText.js"; import { HvTypography } from "../Typography/Typography.js"; const DEFAULT_LABELS = { /** Label for overwrite the default header behavior. */ select: void 0, /** Label used for the All checkbox action. @deprecated unused */ selectAll: "All", /** 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: "/" }; const HvDropdown = fixedForwardRef(function HvDropdown2(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, hasTooltips, 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 [validationState, setValidationState] = useControlled( status, "standBy" ); const [validationMessage] = useControlled(statusMessage, "Required"); const [isOpen, setIsOpen] = useControlled(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) { const hasSelection = getSelected(internalValuesRef.current).length > 0; if (!hasSelection) { return "invalid"; } } return "valid"; }); } }; 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 }); } }; 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; } const listItems = containerRef != null ? [...containerRef.getElementsByTagName("li")] : []; listItems.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: setFocusToContent, 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, showSearch, onChange: handleSelection, onCancel: handleCancel, labels, notifyChangesOnFirstRender, hasTooltips, singleSelectionToggle, "aria-label": ariaLabel, "aria-labelledby": label ? setId(elementId, "label") : void 0, height, maxHeight, virtualized, ...listProps } ) } ), canShowError && /* @__PURE__ */ jsx( HvWarningText, { id: setId(elementId, "error"), disableBorder: true, className: classes.error, children: validationMessage } ) ] } ); }); export { HvDropdown, staticClasses as dropdownClasses };