{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "field",
  "title": "Field",
  "description": "Label, description and error wiring for a single control.",
  "dependencies": [],
  "registryDependencies": [
    "@hjconsult/lib"
  ],
  "files": [
    {
      "path": "src/components/field/index.tsx",
      "content": "'use client'\n\nimport { type ComponentPropsWithoutRef, type ReactNode, type Ref, useId } from 'react'\nimport { cn } from '../../lib'\nimport { FieldContext, type FieldContextValue } from './context'\n\nexport { FieldContext, type FieldContextValue, useField, useFieldControl } from './context'\n\nexport interface FieldProps extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {\n  label: ReactNode\n  description?: ReactNode\n  /** Validation message. When set the control gets `aria-invalid` and is described by it. */\n  error?: ReactNode\n  required?: boolean\n  disabled?: boolean\n  /** Control id; generated when omitted. */\n  id?: string\n  /** Keep the label for assistive technology only. */\n  hideLabel?: boolean\n  /** Visible hint next to the label, e.g. \"Optional\". */\n  hint?: ReactNode\n  /** The control: `Input`, `Textarea`, or anything using `useFieldControl`. */\n  children: ReactNode\n  ref?: Ref<HTMLDivElement>\n}\n\n/**\n * Label + description + error wiring for a single control. The control inside reads the ids\n * through context, so `aria-describedby` and `aria-invalid` are always correct.\n */\nexport function Field({\n  label,\n  description,\n  error,\n  required = false,\n  disabled = false,\n  id: idProp,\n  hideLabel = false,\n  hint,\n  className,\n  children,\n  ...props\n}: FieldProps) {\n  const generated = useId()\n  const id = idProp ?? `hj-field-${generated}`\n  const descriptionId = description ? `${id}-description` : undefined\n  const errorId = error ? `${id}-error` : undefined\n  const value: FieldContextValue = {\n    id,\n    describedBy: [descriptionId, errorId].filter(Boolean).join(' ') || undefined,\n    invalid: Boolean(error),\n    required,\n    disabled,\n  }\n  return (\n    <FieldContext.Provider value={value}>\n      <div\n        data-invalid={error ? '' : undefined}\n        data-disabled={disabled ? '' : undefined}\n        className={cn('group/field flex flex-col gap-2', disabled && 'opacity-60', className)}\n        {...props}\n      >\n        <div className=\"flex items-baseline justify-between gap-3\">\n          <label\n            htmlFor={id}\n            className={cn('font-body text-small font-medium text-fg', hideLabel && 'sr-only')}\n          >\n            {label}\n            {required ? (\n              <span aria-hidden=\"true\" className=\"ms-1 text-danger\">\n                *\n              </span>\n            ) : null}\n          </label>\n          {hint ? <span className=\"font-body text-caption text-fg-subtle\">{hint}</span> : null}\n        </div>\n        {description ? (\n          <p id={descriptionId} className=\"font-body text-small text-fg-muted\">\n            {description}\n          </p>\n        ) : null}\n        {children}\n        <p\n          id={errorId}\n          aria-live=\"polite\"\n          className={cn('font-body text-small text-danger', !error && 'sr-only')}\n        >\n          {error}\n        </p>\n      </div>\n    </FieldContext.Provider>\n  )\n}\n",
      "type": "registry:ui",
      "target": "hj/components/field/index.tsx"
    },
    {
      "path": "src/components/field/context.ts",
      "content": "'use client'\n\nimport { createContext, useContext } from 'react'\n\nexport interface FieldContextValue {\n  /** Id for the control; the label's `htmlFor` points here. */\n  id: string\n  /** Space-separated ids of the description and error paragraphs (when rendered). */\n  describedBy?: string\n  invalid: boolean\n  required: boolean\n  disabled: boolean\n}\n\nexport const FieldContext = createContext<FieldContextValue | null>(null)\n\n/** The enclosing `Field`'s wiring, or `null` outside a Field. */\nexport function useField(): FieldContextValue | null {\n  return useContext(FieldContext)\n}\n\nexport interface FieldControlAttributes {\n  id?: string\n  'aria-describedby'?: string\n  'aria-invalid'?: boolean | 'true' | 'false' | 'grammar' | 'spelling'\n  required?: boolean\n  disabled?: boolean\n}\n\nfunction joinIds(...ids: Array<string | undefined>): string | undefined {\n  const value = ids.filter(Boolean).join(' ')\n  return value || undefined\n}\n\n/**\n * Merge a control's own attributes with the enclosing Field's wiring. Explicit props win; ids\n * in `aria-describedby` are concatenated. Use this in custom controls (selects, comboboxes).\n */\nexport function useFieldControl<T extends FieldControlAttributes>(props: T): T {\n  const field = useField()\n  if (!field) return props\n  return {\n    ...props,\n    id: props.id ?? field.id,\n    'aria-describedby': joinIds(field.describedBy, props['aria-describedby']),\n    'aria-invalid': props['aria-invalid'] ?? (field.invalid || undefined),\n    required: props.required ?? (field.required || undefined),\n    disabled: props.disabled ?? (field.disabled || undefined),\n  }\n}\n",
      "type": "registry:ui",
      "target": "hj/components/field/context.ts"
    }
  ],
  "cssVars": {},
  "type": "registry:ui"
}