{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "form",
  "type": "registry:ui",
  "title": "Form",
  "description": "Field, label, control, description and error wiring, fieldset and settings section.",
  "dependencies": [
    "radix-ui"
  ],
  "registryDependencies": [
    "@wibo/utils",
    "@wibo/label"
  ],
  "files": [
    {
      "path": "src/components/form.tsx",
      "type": "registry:ui",
      "content": "import {\n  createContext,\n  useContext,\n  useEffect,\n  useId,\n  useMemo,\n  useState,\n  type ComponentProps,\n  type ReactNode,\n} from \"react\";\nimport { Slot } from \"radix-ui\";\nimport { cn } from \"@/lib/utils\";\nimport { Label } from \"@/components/ui/label\";\n\ninterface FieldContextValue {\n  controlId: string;\n  descriptionId: string;\n  errorId: string;\n  invalid: boolean;\n  required: boolean;\n  disabled: boolean;\n  hasDescription: boolean;\n  hasError: boolean;\n  setHasDescription: (value: boolean) => void;\n  setHasError: (value: boolean) => void;\n}\n\nconst FieldContext = createContext<FieldContextValue | null>(null);\n\nfunction useField(component: string): FieldContextValue {\n  const ctx = useContext(FieldContext);\n  if (!ctx) throw new Error(`<${component}> must be used inside <Field>.`);\n  return ctx;\n}\n\ninterface FieldProps extends ComponentProps<\"div\"> {\n  /** Id for the control; generated when omitted. */\n  controlId?: string;\n  /** Marks the control invalid. Defaults to true while a `FieldError` has content. */\n  invalid?: boolean;\n  required?: boolean;\n  disabled?: boolean;\n  /** `horizontal`: label and description on the left, control on the right (settings rows). */\n  orientation?: \"vertical\" | \"horizontal\";\n}\n\n/**\n * One form field: wires `FieldLabel`, `FieldControl`, `FieldDescription` and\n * `FieldError` together (ids, `aria-describedby`, `aria-invalid`).\n */\nfunction Field({\n  controlId,\n  invalid,\n  required = false,\n  disabled = false,\n  orientation = \"vertical\",\n  className,\n  ...props\n}: FieldProps) {\n  const generated = useId();\n  const base = controlId ?? `field-${generated}`;\n  const [hasDescription, setHasDescription] = useState(false);\n  const [hasError, setHasError] = useState(false);\n  const value = useMemo<FieldContextValue>(\n    () => ({\n      controlId: base,\n      descriptionId: `${base}-description`,\n      errorId: `${base}-error`,\n      invalid: invalid ?? hasError,\n      required,\n      disabled,\n      hasDescription,\n      hasError,\n      setHasDescription,\n      setHasError,\n    }),\n    [base, invalid, hasError, required, disabled, hasDescription],\n  );\n  return (\n    <FieldContext.Provider value={value}>\n      <div\n        data-slot=\"field\"\n        data-orientation={orientation}\n        data-invalid={value.invalid || undefined}\n        data-disabled={disabled || undefined}\n        className={cn(\n          \"group grid gap-1.5\",\n          orientation === \"horizontal\" &&\n            \"grid-cols-[minmax(0,1fr)_auto] items-center gap-x-6 [&>[data-slot=field-description]]:col-start-1 [&>[data-slot=field-error]]:col-span-2\",\n          className,\n        )}\n        {...props}\n      />\n    </FieldContext.Provider>\n  );\n}\n\nfunction FieldLabel({ className, children, ...props }: ComponentProps<typeof Label>) {\n  const field = useField(\"FieldLabel\");\n  return (\n    <Label\n      data-slot=\"field-label\"\n      htmlFor={field.controlId}\n      className={cn(\"group-data-[invalid=true]:text-danger\", className)}\n      {...props}\n    >\n      {children}\n      {field.required ? (\n        <span aria-hidden=\"true\" className=\"text-muted-foreground\">\n          *\n        </span>\n      ) : null}\n    </Label>\n  );\n}\n\n/** Wraps exactly one control (Input, SelectTrigger, Switch...) and passes it the field's ids and state. */\nfunction FieldControl({ children, ...props }: ComponentProps<typeof Slot.Root>) {\n  const field = useField(\"FieldControl\");\n  const describedBy =\n    [field.hasDescription ? field.descriptionId : null, field.hasError ? field.errorId : null]\n      .filter(Boolean)\n      .join(\" \") || undefined;\n  return (\n    <Slot.Root\n      data-slot=\"field-control\"\n      id={field.controlId}\n      aria-describedby={describedBy}\n      aria-invalid={field.invalid || undefined}\n      aria-required={field.required || undefined}\n      {...(field.disabled ? { disabled: true } : {})}\n      {...props}\n    >\n      {children}\n    </Slot.Root>\n  );\n}\n\nfunction FieldDescription({ className, ...props }: ComponentProps<\"p\">) {\n  const field = useField(\"FieldDescription\");\n  const { setHasDescription } = field;\n  useEffect(() => {\n    setHasDescription(true);\n    return () => setHasDescription(false);\n  }, [setHasDescription]);\n  return (\n    <p\n      data-slot=\"field-description\"\n      id={field.descriptionId}\n      className={cn(\"text-xs text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\n/** Inline validation message. Renders nothing without children. */\nfunction FieldError({ className, children, ...props }: ComponentProps<\"p\">) {\n  const field = useField(\"FieldError\");\n  const { setHasError } = field;\n  const present = children !== undefined && children !== null && children !== false && children !== \"\";\n  useEffect(() => {\n    setHasError(present);\n    return () => setHasError(false);\n  }, [present, setHasError]);\n  if (!present) return null;\n  return (\n    <p\n      data-slot=\"field-error\"\n      id={field.errorId}\n      className={cn(\"text-xs font-medium text-danger\", className)}\n      {...props}\n    >\n      {children}\n    </p>\n  );\n}\n\n/** Groups related controls (radio group, checkboxes) under a legend. */\nfunction Fieldset({ className, ...props }: ComponentProps<\"fieldset\">) {\n  return <fieldset data-slot=\"fieldset\" className={cn(\"grid gap-3\", className)} {...props} />;\n}\n\nfunction FieldLegend({ className, ...props }: ComponentProps<\"legend\">) {\n  return (\n    <legend\n      data-slot=\"field-legend\"\n      className={cn(\"mb-1 text-sm font-medium text-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\ninterface SettingsSectionProps extends Omit<ComponentProps<\"section\">, \"title\"> {\n  title: ReactNode;\n  description?: ReactNode;\n  /** Actions at the bottom of the card, usually a save button. */\n  footer?: ReactNode;\n}\n\n/** Settings page block: title and description on the left, a card of fields on the right. */\nfunction SettingsSection({ title, description, footer, className, children, ...props }: SettingsSectionProps) {\n  const headingId = useId();\n  return (\n    <section\n      data-slot=\"settings-section\"\n      aria-labelledby={headingId}\n      className={cn(\"grid grid-cols-1 gap-4 py-6 lg:grid-cols-[minmax(0,18rem)_minmax(0,1fr)] lg:gap-10\", className)}\n      {...props}\n    >\n      <div className=\"grid content-start gap-1\">\n        <h2 id={headingId} className=\"text-sm font-medium text-foreground\">\n          {title}\n        </h2>\n        {description ? <p className=\"text-sm text-muted-foreground\">{description}</p> : null}\n      </div>\n      <div className=\"overflow-hidden rounded-lg border border-border bg-card shadow-card\">\n        <div className=\"grid gap-5 p-4 sm:p-5\">{children}</div>\n        {footer ? (\n          <div className=\"flex items-center justify-end gap-2 border-t border-border bg-muted/40 px-4 py-3 sm:px-5\">\n            {footer}\n          </div>\n        ) : null}\n      </div>\n    </section>\n  );\n}\n\nexport {\n  Field,\n  FieldControl,\n  FieldDescription,\n  FieldError,\n  FieldLabel,\n  FieldLegend,\n  Fieldset,\n  SettingsSection,\n};\nexport type { FieldProps, SettingsSectionProps };\n"
    }
  ]
}
