UNPKG

@hitachivantara/uikit-react-core

Version:

Core React components for the NEXT Design System.

238 lines (237 loc) 7.46 kB
import { jsxs, jsx, Fragment } from "react/jsx-runtime"; import { useState, useRef } from "react"; import { Popper } from "@mui/base/Popper"; import { useSelect, SelectProvider } from "@mui/base/useSelect"; import { useForkRef, useControlled } from "@mui/material/utils"; import { clsx } from "clsx"; import { useDefaultProps, useTheme } from "@hitachivantara/uikit-react-utils"; import { HvLabelContainer } from "../FormElement/LabelContainer.js"; import { useUniqueId } from "../hooks/useUniqueId.js"; import { fixedForwardRef } from "../types/generic.js"; import { getContainerElement } from "../utils/document.js"; import { setId } from "../utils/setId.js"; import { HvOption } from "./Option.js"; import { useClasses } from "./Select.styles.js"; import { staticClasses } from "./Select.styles.js"; import { HvFormElement } from "../FormElement/FormElement.js"; import { HvDropdownButton } from "../DropdownButton/DropdownButton.js"; import { HvListContainer } from "../ListContainer/ListContainer.js"; import { HvWarningText } from "../FormElement/WarningText/WarningText.js"; function defaultRenderValue(options) { if (Array.isArray(options)) { if (options.length === 0) return null; return /* @__PURE__ */ jsx(Fragment, { children: options.map((o) => o.label).join(", ") }); } return options?.label ?? null; } const mergeIds = (...ids) => clsx(ids) || void 0; function renderOptions(options) { return options?.map((option) => /* @__PURE__ */ jsx(HvOption, { ...option, children: option.label }, option.value)); } const HvSelect = fixedForwardRef(function HvSelect2(props, ref) { const { children: childrenProp, classes: classesProp, className, id: idProp, size, variant = "secondarySubtle", name, required, disabled: disabledProp, readOnly, label, open: openProp, defaultOpen, multiple, autoComplete, options: optionsProp, variableWidth, value: valueProp, defaultValue, placeholder, inputProps, enablePortal, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, description, "aria-describedby": ariaDescribedBy, status, statusMessage, "aria-errormessage": ariaErrorMessage, getSerializedValue, onClick, onChange, onOpenChange, ...others } = useDefaultProps("HvSelect", props); const { classes, cx } = useClasses(classesProp); const { rootId } = useTheme(); const [placement, setPlacement] = useState("bottom-start"); const buttonRef = useRef(null); const handleButtonRef = useForkRef(ref, buttonRef); const { contextValue, disabled, getButtonProps, getListboxProps, getHiddenInputProps, getOptionMetadata, value, open } = useSelect({ componentName: "HvSelect", name, required, disabled: disabledProp, multiple, open: openProp, defaultOpen, value: valueProp, defaultValue, options: optionsProp, buttonRef: handleButtonRef, getSerializedValue, onChange, onOpenChange: handleOpenChange }); const id = useUniqueId(idProp); const labelId = useUniqueId(setId(idProp, "label")); const descriptionId = useUniqueId(setId(idProp, "description")); const errorMessageId = useUniqueId(setId(idProp, "error")); const [validationMessage] = useControlled({ name: "HvSelect.statusMessage", controlled: statusMessage, default: "Required" }); const [validationState, setValidationState] = useControlled({ name: "HvSelect.status", controlled: status, default: "standBy" }); function handleOpenChange(newOpen) { if (!newOpen) { const hasValue = multiple ? value.length > 0 : !!value; setValidationState(required && !hasValue ? "invalid" : "valid"); } onOpenChange?.(newOpen); } const canShowError = ariaErrorMessage == null && (status !== void 0 && statusMessage !== void 0 || status === void 0 && required); const isInvalid = validationState === "invalid"; const actualValue = multiple ? value.map((v) => getOptionMetadata(v)).filter((v) => v !== void 0) : getOptionMetadata(value) ?? null; const children = childrenProp ?? renderOptions(optionsProp); const isOpen = open && !!children; return /* @__PURE__ */ jsxs( HvFormElement, { name, required, disabled, readOnly, status: validationState, className: cx(classes.root, className, { [classes.disabled]: disabled, [classes.readOnly]: readOnly }), ...others, children: [ /* @__PURE__ */ jsx( HvLabelContainer, { label, description, inputId: id, labelId, descriptionId, classes: { root: classes.labelContainer, label: classes.label, description: classes.description } } ), /* @__PURE__ */ jsx( HvDropdownButton, { id, open: isOpen, disabled, readOnly, className: cx(classes.select, { [classes.invalid]: validationState === "invalid" }), placement, size, variant, "aria-label": ariaLabel, "aria-labelledby": mergeIds(ariaLabelledBy, { [labelId]: label }), "aria-invalid": isInvalid ? true : void 0, "aria-errormessage": errorMessageId, "aria-describedby": mergeIds(ariaDescribedBy, { [descriptionId]: description }), ...getButtonProps(), children: defaultRenderValue(actualValue) ?? placeholder } ), /* @__PURE__ */ jsx( Popper, { role: "none", open: isOpen, keepMounted: true, disablePortal: !enablePortal, container: enablePortal ? getContainerElement(rootId) : void 0, anchorEl: buttonRef.current, className: classes.popper, placement, modifiers: [ { enabled: true, phase: "main", fn: ({ state }) => setPlacement(state.placement) } ], children: /* @__PURE__ */ jsx( HvListContainer, { condensed: true, selectable: true, style: { width: variableWidth ? "auto" : (buttonRef.current?.clientWidth || 0) + 2 }, className: cx(classes.panel, { [classes.panelOpenedUp]: placement.includes("top"), [classes.panelOpenedDown]: placement.includes("bottom") }), ...getListboxProps(), children: /* @__PURE__ */ jsx(SelectProvider, { value: contextValue, children }) } ) } ), /* @__PURE__ */ jsx( "input", { ...getHiddenInputProps(), autoComplete, ...inputProps } ), canShowError && /* @__PURE__ */ jsx( HvWarningText, { id: errorMessageId, disableBorder: true, className: classes.error, children: validationMessage } ) ] } ); }); export { HvSelect, staticClasses as selectClasses };