@hitachivantara/uikit-react-core
Version:
Core React components for the NEXT Design System.
316 lines (315 loc) • 10 kB
JavaScript
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
};