'use client'; import React, { forwardRef } from 'react'; import { Surface, cn, sizeHeight, surfaceClasses, useEffectiveSurface, } from '../common'; export interface PixelInputGroupProps extends React.HTMLAttributes { size?: 'sm' | 'md' | 'lg'; surface?: Surface; /** * Accessible name for the group. Strongly recommended since this primitive * visually joins multiple form controls (e.g. country code + phone) — without * an accessible name a screen reader user has no idea what the group represents. * In dev, a missing name on a group of >1 child logs a warning. */ 'aria-label'?: string; 'aria-labelledby'?: string; children: React.ReactNode; } export const PixelInputGroup = forwardRef( function PixelInputGroup( { size = 'md', surface: surfaceProp, className, children, role, ...rest }, ref, ) { const ariaLabel = (rest as { 'aria-label'?: string })['aria-label']; const ariaLabelledBy = (rest as { 'aria-labelledby'?: string })['aria-labelledby']; const hasName = !!(ariaLabel || ariaLabelledBy); const childCount = React.Children.count(children); if (process.env.NODE_ENV !== 'production' && childCount > 1 && !hasName) { // eslint-disable-next-line no-console console.warn( '[PixelInputGroup] missing aria-label / aria-labelledby. A group of joined ' + 'controls is unintelligible to SR users without an accessible name.', ); } const surface = useEffectiveSurface(surfaceProp); const s = surfaceClasses(surface); const items = React.Children.toArray(children).filter(React.isValidElement); const last = items.length - 1; const joined = items.map((child, i) => { const el = child as React.ReactElement<{ className?: string; style?: React.CSSProperties; }>; const isFirst = i === 0; const isLast = i === last; // Strip inner border + radius so children look like one shell, then // add a right divider between segments (except on the last child). const joinedClass = cn( 'min-w-0 border-0 rounded-none focus:z-10 focus-visible:z-10 relative', !isLast && cn('border-r', s.border, 'border-retro-border/60'), // Keep the original child className last so consumer styles win where needed. el.props.className, ); void isFirst; return React.cloneElement(el, { ...(el.props as object), className: joinedClass, }); }); // Only set role=group when the group has an accessible name; otherwise drop // it so SR users don't hear an unlabeled "group" landmark. const effectiveRole = role ?? (hasName ? 'group' : undefined); return (
{joined}
); }, ); PixelInputGroup.displayName = 'PixelInputGroup';