UNPKG

@carbon/react

Version:

React components for the Carbon Design System

188 lines (186 loc) 7.31 kB
/** * Copyright IBM Corp. 2016, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import { usePrefix } from "../../internal/usePrefix.js"; import { Text } from "../Text/Text.js"; import { deprecate } from "../../prop-types/deprecate.js"; import { isComponentElement } from "../../internal/utils.js"; import { useNormalizedInputProps } from "../../internal/useNormalizedInputProps.js"; import { AILabel } from "../AILabel/index.js"; import { composeEventHandlers } from "../../tools/events.js"; import { FormContext } from "../FluidForm/FormContext.js"; import classNames from "classnames"; import React, { cloneElement, forwardRef, useContext, useState } from "react"; import PropTypes from "prop-types"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; import { ChevronDown, WarningAltFilled, WarningFilled } from "@carbon/icons-react"; //#region src/components/Select/Select.tsx /** * Copyright IBM Corp. 2016, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const Select = forwardRef(({ className, decorator, id, inline = false, labelText = "Select", disabled = false, children, noLabel = false, hideLabel = false, invalid = false, invalidText = "", helperText, light = false, readOnly, size, warn = false, warnText, onChange, slug, ...other }, ref) => { const prefix = usePrefix(); const { isFluid } = useContext(FormContext); const [isFocused, setIsFocused] = useState(false); const validChildren = React.Children.toArray(children).filter((child) => React.isValidElement(child)); const selectedValue = other?.value || other?.defaultValue; const selectedOption = validChildren.find((child) => child.props?.value === selectedValue); const [title, setTitle] = useState(other?.title || selectedOption?.props?.text || validChildren[0]?.props?.text || ""); const normalizedProps = useNormalizedInputProps({ id, disabled, readOnly, invalid, invalidText, warn, warnText }); const selectClasses = classNames({ [`${prefix}--select`]: true, [`${prefix}--select--inline`]: inline, [`${prefix}--select--light`]: light, [`${prefix}--select--invalid`]: normalizedProps.invalid, [`${prefix}--select--disabled`]: normalizedProps.disabled, [`${prefix}--select--readonly`]: readOnly, [`${prefix}--select--warning`]: normalizedProps.warn, [`${prefix}--select--fluid--invalid`]: isFluid && normalizedProps.invalid, [`${prefix}--select--fluid--focus`]: isFluid && isFocused, [`${prefix}--select--slug`]: slug, [`${prefix}--select--decorator`]: decorator }); const labelClasses = classNames(`${prefix}--label`, { [`${prefix}--visually-hidden`]: hideLabel, [`${prefix}--label--disabled`]: normalizedProps.disabled }); const inputClasses = classNames({ [`${prefix}--select-input`]: true, [`${prefix}--select-input--${size}`]: size }); const error = normalizedProps.validation; const helperTextClasses = classNames(`${prefix}--form__helper-text`, { [`${prefix}--form__helper-text--disabled`]: normalizedProps.disabled }); const helper = typeof helperText !== "undefined" && helperText !== null ? /* @__PURE__ */ jsx(Text, { as: "div", id: normalizedProps.helperId, className: helperTextClasses, children: helperText }) : null; const ariaProps = {}; if (normalizedProps.invalid) ariaProps["aria-describedby"] = normalizedProps.invalidId; else if (!inline && !isFluid) ariaProps["aria-describedby"] = helper ? normalizedProps.helperId : void 0; const handleFocus = (evt) => { setIsFocused(evt.type === "focus"); }; const handleChange = (evt) => { const selectedOption = evt?.target?.options[evt.target.selectedIndex]; setTitle(selectedOption?.text); }; const readOnlyEventHandlers = { onMouseDown: (evt) => { if (readOnly) { evt.preventDefault(); evt.target.focus(); } }, onKeyDown: (evt) => { if (readOnly && [ "ArrowDown", "ArrowUp", " " ].includes(evt.key)) evt.preventDefault(); } }; const candidate = slug ?? decorator; const normalizedDecorator = isComponentElement(candidate, AILabel) ? cloneElement(candidate, { size: "mini" }) : candidate; const input = /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx("select", { ...other, ...ariaProps, id, className: inputClasses, disabled: normalizedProps.disabled || void 0, "aria-invalid": normalizedProps.invalid || void 0, "aria-readonly": readOnly || void 0, title, onChange: composeEventHandlers([onChange, handleChange]), ...readOnlyEventHandlers, ref, children }), /* @__PURE__ */ jsx(ChevronDown, { className: `${prefix}--select__arrow` }), normalizedProps.invalid && /* @__PURE__ */ jsx(WarningFilled, { className: `${prefix}--select__invalid-icon` }), !normalizedProps.invalid && normalizedProps.warn && /* @__PURE__ */ jsx(WarningAltFilled, { className: `${prefix}--select__invalid-icon ${prefix}--select__invalid-icon--warning` }) ] }); return /* @__PURE__ */ jsx("div", { className: classNames(`${prefix}--form-item`, className), children: /* @__PURE__ */ jsxs("div", { className: selectClasses, children: [ !noLabel && /* @__PURE__ */ jsx(Text, { as: "label", htmlFor: id, className: labelClasses, children: labelText }), inline && /* @__PURE__ */ jsxs("div", { className: `${prefix}--select-input--inline__wrapper`, children: [/* @__PURE__ */ jsx("div", { className: `${prefix}--select-input__wrapper`, "data-invalid": normalizedProps.invalid || null, children: input }), error] }), !inline && /* @__PURE__ */ jsxs("div", { className: `${prefix}--select-input__wrapper`, "data-invalid": normalizedProps.invalid || null, onFocus: handleFocus, onBlur: handleFocus, children: [ input, slug ? normalizedDecorator : decorator ? /* @__PURE__ */ jsx("div", { className: `${prefix}--select__inner-wrapper--decorator`, children: normalizedDecorator }) : "", isFluid && /* @__PURE__ */ jsx("hr", { className: `${prefix}--select__divider` }), isFluid && error ? error : null ] }), !inline && !isFluid && error ? error : helper ] }) }); }); Select.displayName = "Select"; Select.propTypes = { children: PropTypes.node, className: PropTypes.string, decorator: PropTypes.node, defaultValue: PropTypes.any, disabled: PropTypes.bool, helperText: PropTypes.node, hideLabel: PropTypes.bool, id: PropTypes.string.isRequired, inline: PropTypes.bool, invalid: PropTypes.bool, invalidText: PropTypes.node, labelText: PropTypes.node, light: deprecate(PropTypes.bool, "The `light` prop for `Select` is no longer needed and has been deprecated in v11 in favor of the new `Layer` component. It will be moved in the next major release."), noLabel: PropTypes.bool, onChange: PropTypes.func, readOnly: PropTypes.bool, size: PropTypes.oneOf([ "sm", "md", "lg" ]), slug: deprecate(PropTypes.node, "The `slug` prop has been deprecated and will be removed in the next major version. Use the decorator prop instead."), warn: PropTypes.bool, warnText: PropTypes.node }; //#endregion export { Select as default };