// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file InputGroup.tsx * @input Uses React, StyleX, theme tokens, InputGroupContext, Field * @output Exports InputGroup component with group label/description ARIA wiring * @position Groups input with prefix/suffix addons; consumed by index.ts * * Children (TextInput, NumberInput, TimeInput, DateInput, Typeahead, * Selector, MultiSelector) consume the InputGroup context * to remove their own border/radius so the group container provides * the unified border treatment. * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/InputGroup/InputGroup.doc.mjs * - /packages/core/src/InputGroup/InputGroup.test.tsx * - /packages/core/src/InputGroup/index.ts * - /apps/storybook/stories/InputGroup.stories.tsx * - /packages/cli/assets/templates/blocks/components/InputGroup/ */ import { useId, useMemo, type ReactNode } from 'react'; import * as stylex from "../style-runtime.js"; import "../theme/tokens.stylex.js"; import { sizeVars } from "../theme/tokens.stylex.js"; import { Field, type InputStatus } from "../Field/index.js"; import { SizeProvider, useSize } from "../SizeContext/SizeContext.js"; import { mergeProps } from "../utils/index.js"; import type { BaseProps } from "../BaseProps.js"; import { InputGroupContext } from "./InputGroupContext.js"; import { themeProps } from "../utils/themeProps.js"; export type InputGroupSize = 'sm' | 'md' | 'lg'; const styles = { group: { k1xSpc: "x3nfvp2", kGNEyG: "x1qjc9v5", kWkggS: "xjbqb8w", $$css: true as const }, disabled: { kkrTdU: "xt0e3qv", kSiTet: "xbyyjgo", $$css: true as const } }; const sizeStyles = { sm: { kZKoxP: "x6k0iem", $$css: true as const }, md: { kZKoxP: "x1ueg155", $$css: true as const }, lg: { kZKoxP: "xssyfek", $$css: true as const } }; export interface InputGroupProps extends Omit, 'children'> { /** Ref forwarded to the group container element */ ref?: React.Ref; /** * Input and addon children. */ children: ReactNode; /** * Label text for the group (required for accessibility). */ label: string; /** * Whether to visually hide the label. * @default false */ isLabelHidden?: boolean; /** * Description text displayed between the label and input group. */ description?: string; /** * Whether the group is disabled. * @default false */ isDisabled?: boolean; /** * Whether the field is optional. * @default false */ isOptional?: boolean; /** * Whether the field is required. * @default false */ isRequired?: boolean; /** * Default size for inputs in the group. * @default 'md' */ size?: InputGroupSize; /** * Status indicator applied to the group border. */ status?: InputStatus; /** * Tooltip text at the end of the label. */ labelTooltip?: string; /** * Test ID for testing frameworks. */ 'data-testid'?: string; } /** * Groups an input with prefix/suffix addons in a visually connected * container with shared border and focus ring. * * @example * ``` * * $ * * * ``` */ export function InputGroup({ children, label, isLabelHidden = false, description, isDisabled = false, isOptional = false, isRequired = false, size: sizeProp, status, labelTooltip, xstyle, className, style, ref, 'data-testid': testId, ...rest }: InputGroupProps) { const size = useSize(sizeProp, 'md'); const inputId = useId(); const labelID = useId(); const descriptionID = useId(); const statusMessageId = useId(); const describedByIDs = [description ? descriptionID : null, status?.message ? statusMessageId : null].filter(Boolean).join(' ') || undefined; const contextValue = useMemo(() => ({ isInGroup: true as const, labelID, describedByIDs }), [labelID, describedByIDs]); return
{children}
; } InputGroup.displayName = 'InputGroup';