UNPKG

@carbon/react

Version:

React components for the Carbon Design System

233 lines (231 loc) 10.1 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. */ const require_runtime = require("../../_virtual/_rolldown/runtime.js"); const require_usePrefix = require("../../internal/usePrefix.js"); const require_Text = require("../Text/Text.js"); const require_deprecate = require("../../prop-types/deprecate.js"); const require_utils = require("../../internal/utils.js"); const require_useMergedRefs = require("../../internal/useMergedRefs.js"); const require_useNormalizedInputProps = require("../../internal/useNormalizedInputProps.js"); const require_index = require("../AILabel/index.js"); const require_FormContext = require("../FluidForm/FormContext.js"); const require_util = require("./util.js"); const require_getAnnouncement = require("../../internal/getAnnouncement.js"); let classnames = require("classnames"); classnames = require_runtime.__toESM(classnames); let react = require("react"); react = require_runtime.__toESM(react); let prop_types = require("prop-types"); prop_types = require_runtime.__toESM(prop_types); let react_jsx_runtime = require("react/jsx-runtime"); //#region src/components/TextInput/TextInput.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 TextInput = (0, react.forwardRef)(({ className, decorator, disabled = false, helperText, hideLabel, id, inline = false, invalid = false, invalidText, labelText, light, onChange = () => {}, onClick = () => {}, placeholder, readOnly, size, type = "text", warn = false, warnText, enableCounter = false, maxCount, slug, ...rest }, ref) => { const prefix = require_usePrefix.usePrefix(); const { defaultValue, value } = rest; const inputRef = (0, react.useRef)(null); const mergedRef = require_useMergedRefs.useMergedRefs([ref, inputRef]); function getInitialTextCount() { return (defaultValue || value || inputRef.current?.value || "").toString().length; } const [textCount, setTextCount] = (0, react.useState)(getInitialTextCount()); (0, react.useEffect)(() => { setTextCount(getInitialTextCount()); }, [ value, defaultValue, enableCounter ]); const normalizedProps = require_useNormalizedInputProps.useNormalizedInputProps({ id, readOnly, disabled, invalid, invalidText, warn, warnText }); const sharedTextInputProps = { id, onChange: (evt) => { if (!normalizedProps.disabled) { setTextCount(evt.target.value?.length); onChange(evt); } }, onClick: (evt) => { if (!normalizedProps.disabled) onClick(evt); }, placeholder, type, ref: mergedRef, className: (0, classnames.default)(`${prefix}--text-input`, { [`${prefix}--text-input--light`]: light, [`${prefix}--text-input--invalid`]: normalizedProps.invalid, [`${prefix}--text-input--warning`]: normalizedProps.warn, [`${prefix}--text-input--${size}`]: size, [`${prefix}--layout--size-${size}`]: size }), title: placeholder, disabled: normalizedProps.disabled, readOnly, ["aria-describedby"]: helperText && normalizedProps.helperId, ...rest }; if (enableCounter) sharedTextInputProps.maxLength = maxCount; const inputWrapperClasses = (0, classnames.default)([(0, classnames.default)(`${prefix}--form-item`, className)], `${prefix}--text-input-wrapper`, { [`${prefix}--text-input-wrapper--readonly`]: readOnly, [`${prefix}--text-input-wrapper--light`]: light, [`${prefix}--text-input-wrapper--inline`]: inline, [`${prefix}--text-input-wrapper--inline--invalid`]: inline && normalizedProps.invalid }); const labelClasses = (0, classnames.default)(`${prefix}--label`, { [`${prefix}--visually-hidden`]: hideLabel, [`${prefix}--label--disabled`]: normalizedProps.disabled, [`${prefix}--label--inline`]: inline, [`${prefix}--label--inline--${size}`]: inline && !!size }); const helperTextClasses = (0, classnames.default)(`${prefix}--form__helper-text`, { [`${prefix}--form__helper-text--disabled`]: normalizedProps.disabled, [`${prefix}--form__helper-text--inline`]: inline }); const fieldOuterWrapperClasses = (0, classnames.default)(`${prefix}--text-input__field-outer-wrapper`, { [`${prefix}--text-input__field-outer-wrapper--inline`]: inline }); const fieldWrapperClasses = (0, classnames.default)(`${prefix}--text-input__field-wrapper`, { [`${prefix}--text-input__field-wrapper--warning`]: normalizedProps.warn, [`${prefix}--text-input__field-wrapper--slug`]: slug, [`${prefix}--text-input__field-wrapper--decorator`]: decorator }); const iconClasses = (0, classnames.default)({ [`${prefix}--text-input__invalid-icon`]: normalizedProps.invalid || normalizedProps.warn, [`${prefix}--text-input__invalid-icon--warning`]: normalizedProps.warn }); const counterClasses = (0, classnames.default)(`${prefix}--label`, { [`${prefix}--label--disabled`]: disabled, [`${prefix}--text-input__label-counter`]: true }); const counter = enableCounter && maxCount ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Text.Text, { as: "div", className: counterClasses, children: `${textCount}/${maxCount}` }) : null; const label = typeof labelText !== "undefined" && labelText !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Text.Text, { as: "label", htmlFor: id, className: labelClasses, children: labelText }); const labelWrapper = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: `${prefix}--text-input__label-wrapper`, children: [label, counter] }); const helper = typeof helperText !== "undefined" && helperText !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Text.Text, { as: "div", id: normalizedProps.helperId, className: helperTextClasses, children: helperText }); const input = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { ...require_util.getTextInputProps({ sharedTextInputProps, invalid: normalizedProps.invalid, invalidId: normalizedProps.invalidId, warn: normalizedProps.warn, warnId: normalizedProps.warnId }) }); const { isFluid } = (0, react.useContext)(require_FormContext.FormContext); const announcerRef = (0, react.useRef)(null); const [prevAnnouncement, setPrevAnnouncement] = (0, react.useState)(""); const ariaAnnouncement = require_getAnnouncement.getAnnouncement(textCount, maxCount); (0, react.useEffect)(() => { if (ariaAnnouncement && ariaAnnouncement !== prevAnnouncement) { const announcer = announcerRef.current; if (announcer) { announcer.textContent = ""; const timeoutId = setTimeout(() => { if (announcer) { announcer.textContent = ariaAnnouncement; setPrevAnnouncement(ariaAnnouncement); } }, 1e3); return () => { if (timeoutId) clearTimeout(timeoutId); }; } } }, [ariaAnnouncement, prevAnnouncement]); const Icon = normalizedProps.icon; const candidate = slug ?? decorator; const normalizedDecorator = require_utils.isComponentElement(candidate, require_index.AILabel) ? (0, react.cloneElement)(candidate, { size: "mini" }) : candidate; return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: inputWrapperClasses, children: [!inline ? labelWrapper : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: `${prefix}--text-input__label-helper-wrapper`, children: [labelWrapper, !isFluid && (normalizedProps.validation || helper)] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: fieldOuterWrapperClasses, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: fieldWrapperClasses, "data-invalid": normalizedProps.invalid || null, children: [ Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: iconClasses }), input, slug ? normalizedDecorator : decorator ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `${prefix}--text-input__field-inner-wrapper--decorator`, children: normalizedDecorator }) : "", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `${prefix}--text-input__counter-alert`, role: "alert", "aria-live": "assertive", "aria-atomic": "true", ref: announcerRef, children: ariaAnnouncement }), isFluid && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", { className: `${prefix}--text-input__divider` }), isFluid && !inline && normalizedProps.validation ] }), !isFluid && !inline && (normalizedProps.validation || helper)] })] }); }); TextInput.displayName = "TextInput"; TextInput.propTypes = { className: prop_types.default.string, decorator: prop_types.default.node, defaultValue: prop_types.default.oneOfType([prop_types.default.string, prop_types.default.number]), disabled: prop_types.default.bool, enableCounter: prop_types.default.bool, helperText: prop_types.default.node, hideLabel: prop_types.default.bool, id: prop_types.default.string.isRequired, inline: prop_types.default.bool, invalid: prop_types.default.bool, invalidText: prop_types.default.node, labelText: prop_types.default.node.isRequired, light: require_deprecate.deprecate(prop_types.default.bool, "The `light` prop for `TextInput` has been deprecated in favor of the new `Layer` component. It will be removed in the next major release."), maxCount: prop_types.default.number, onChange: prop_types.default.func, onClick: prop_types.default.func, placeholder: prop_types.default.string, readOnly: prop_types.default.bool, size: prop_types.default.oneOf([ "sm", "md", "lg" ]), slug: require_deprecate.deprecate(prop_types.default.node, "The `slug` prop for `TextInput` has been deprecated in favor of the new `decorator` prop. It will be removed in the next major release."), type: prop_types.default.string, value: prop_types.default.oneOfType([prop_types.default.string, prop_types.default.number]), warn: prop_types.default.bool, warnText: prop_types.default.node }; //#endregion exports.default = TextInput;