UNPKG

@hitachivantara/uikit-react-core

Version:
173 lines (172 loc) 6.32 kB
import { fixedForwardRef } from "../types/generic.js"; import { HvListContainer } from "../ListContainer/ListContainer.js"; import { setId } from "../utils/setId.js"; import { useUniqueId } from "../hooks/useUniqueId.js"; import { HvFormElement } from "../FormElement/FormElement.js"; import { HvWarningText } from "../FormElement/WarningText/WarningText.js"; import { HvDropdownPanel } from "../BaseDropdown/BaseDropdownPanel.js"; import { HvDropdownButton } from "../DropdownButton/DropdownButton.js"; import { HvLabelContainer } from "../FormElement/LabelContainer.js"; import { HvOption } from "./Option.js"; import { useClasses } from "./Select.styles.js"; import { useDefaultProps } from "@hitachivantara/uikit-react-utils"; import { useRef, useState } from "react"; import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime"; import { SelectProvider, useSelect } from "@mui/base"; import { useControlled, useForkRef } from "@mui/material/utils"; import { clsx } from "clsx"; //#region src/Select/Select.tsx var noop = () => {}; function defaultRenderValue(options) { if (Array.isArray(options)) return /* @__PURE__ */ jsx(Fragment$1, { children: options.map((o) => o.label).join(", ") }); return options?.label ?? null; } var mergeIds = (...ids) => clsx(ids) || void 0; function renderOptions(options) { return options?.map((option) => /* @__PURE__ */ jsx(HvOption, { ...option, children: option.label }, option.value)); } /** * The `HvSelect` component is a form control for choosing an option from a list. * * It aligns with the native `<select>` and `<option>` APIs, making it easy to integrate into forms. * * @example * <HvSelect name="pets"> * <HvOption value="dog">Dog</HvOption> * <HvOption value="cat">Cat</HvOption> * </HvSelect> * */ var HvSelect = fixedForwardRef(function HvSelect(props, ref) { const { children: childrenProp, classes: classesProp, className, id: idProp, size, variant = "secondarySubtle", name, required, disabled: disabledProp, readOnly, label, open: openProp, defaultOpen, multiple, autoComplete, renderValue: renderValueProp, 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 [placement, setPlacement] = useState("bottom-start"); const buttonRef = useRef(null); 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: useForkRef(ref, buttonRef), getSerializedValue, onChange, onOpenChange: handleOpenChange }); const renderValue = renderValueProp ?? defaultRenderValue; 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.readOnly]: readOnly, [classes.disabled]: disabled }), ...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" }), "data-popper-placement": 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: renderValue(actualValue) ?? placeholder }), /* @__PURE__ */ jsx(HvDropdownPanel, { role: "none", open: isOpen, keepMounted: true, variableWidth, disablePortal: !enablePortal, anchorEl: buttonRef.current, classes: { container: classes.popper, panel: classes.panel }, placement, onFirstUpdate: (state) => setPlacement(state.placement), onClickAway: noop, onToggle: noop, children: /* @__PURE__ */ jsx(HvListContainer, { condensed: true, selectable: true, "data-is-dropdown": true, ...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 }) ] }); }); //#endregion export { HvSelect };