'use client'; import React, { Attributes, Children, ReactNode, cloneElement, isValidElement, useCallback, useId, useMemo, } from 'react'; import { useBreakpoint } from '../../hook/breakpoints.hook.js'; import { resolveResponsiveVariant } from '../../utils/breakpoint.util.js'; import { ErrorMessage, Hint, Label } from '../index.js'; import { InputGroupSupportingText } from './components/index.js'; import { InputGroupAddOn } from './components/input-group-add-ons/input-group-add-ons.component.js'; import { styles as inputGroupStyles } from './input-group.styles.js'; import { type InputGroupProps } from './input-group.types.js'; export function InputGroup({ label, hideLabel, size = 'medium', hint, errorMessage, supportingText, instanceId, after, before, children, tag: Tag = 'div', className, width = 'full', id: propID, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, 'aria-label': ariaLabel, ...props }: InputGroupProps) { const _id = useId(); const id = useMemo(() => instanceId || `gel-field-${_id}`, [_id, instanceId]); const breakpoint = useBreakpoint(); const resolvedWidth = resolveResponsiveVariant(width, breakpoint); const resolvedSize = resolveResponsiveVariant(size, breakpoint); const ariaDescribedByValue = useMemo(() => { const arr = [ ...(errorMessage ? [`${id}-error`] : []), ...(hint ? [`${id}-hint`] : []), ...(before ? [`${id}-text-before`] : []), ...(after ? [`${id}-text-after`] : []), ...(supportingText ? [`${id}-supporting-text`] : []), ]; return arr.join(' '); }, [errorMessage, id, hint, before, after, supportingText]); const { element: beforeElement, icon: beforeIcon, inset: beforeInset, } = useMemo(() => { if ( before && typeof before !== 'boolean' && typeof before !== 'string' && typeof before !== 'number' && ('element' in before || 'icon' in before || 'inset' in before) ) { return { ...before, inset: before.inset || !!before.icon }; } return { element: before as ReactNode, icon: undefined, inset: false }; }, [before]); const { element: afterElement, icon: afterIcon, inset: afterInset, } = useMemo(() => { if ( after && typeof after !== 'boolean' && typeof after !== 'string' && typeof after !== 'number' && ('element' in after || 'icon' in after || 'inset' in after) ) { return { ...after, inset: after.inset || !!after.icon }; } return { element: after as ReactNode, icon: undefined, inset: false }; }, [after]); const renderChildren = useCallback(() => { return Children.map(children, child => { if (isValidElement(child)) { return cloneElement(child, { size: resolvedSize, id: propID || id, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy || ariaDescribedByValue, 'aria-label': ariaLabel, className: 'focus:z-10', // for focus ring visibility ...(resolvedWidth !== 'full' ? { width: resolvedWidth } : {}), } as Partial & Attributes); } }); }, [ children, resolvedSize, propID, id, ariaLabelledBy, ariaDescribedBy, ariaDescribedByValue, ariaLabel, resolvedWidth, ]); const isFieldset = useMemo(() => Tag === 'fieldset', [Tag]); const styles = inputGroupStyles({ before: !!before, after: !!after, afterInset, beforeInset, width: resolvedWidth, }); return ( {label && ( )} {hint && {hint}} {errorMessage && }
{before && ( {beforeElement} )} {renderChildren()} {after && ( {afterElement} )}
{supportingText && ( {supportingText} )}
); }