{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "field-group",
  "title": "Group field",
  "description": "Styled field: the Group set. 5 decorative field variations (RowField, SplitField, GridField, SectionField, FieldsetField) on the ai2 token system, driven by CSS token transitions and sized sm to xl. Part of the free styled layer.",
  "dependencies": [],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/field-group.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Group field family: 5 multi-field form layouts. Not a single control but blocks\n   where related controls stand together (a side-by-side row, an asymmetric split, a\n   grid, a titled section, a real fieldset/legend). Every control is matched to its\n   own label through htmlFor/id, the description is bound with aria-describedby, and\n   invalidity falls back to the standard danger appearance ON THE CONTROL via\n   aria-invalid. All ids come from useId, so dozens of examples on the same page do\n   not collide. Color comes ONLY from tokens. Static - no animation. Renders with no\n   props too. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst gap: Record<StyledSize, string> = {\n  sm: \"gap-1\",\n  md: \"gap-1.5\",\n  lg: \"gap-2\",\n  xl: \"gap-2.5\",\n}\n\nconst blockGap: Record<StyledSize, string> = {\n  sm: \"gap-2.5\",\n  md: \"gap-3\",\n  lg: \"gap-4\",\n  xl: \"gap-5\",\n}\n\nconst labelText: Record<StyledSize, string> = {\n  sm: \"text-xs\",\n  md: \"text-sm\",\n  lg: \"text-sm\",\n  xl: \"text-base\",\n}\n\nconst helpText: Record<StyledSize, string> = {\n  sm: \"text-xs\",\n  md: \"text-xs\",\n  lg: \"text-sm\",\n  xl: \"text-sm\",\n}\n\nconst controlHeight: Record<StyledSize, string> = {\n  sm: \"h-8 text-sm\",\n  md: \"h-9 text-sm\",\n  lg: \"h-10 text-base\",\n  xl: \"h-12 text-base\",\n}\n\nconst controlBase =\n  \"w-full rounded-md border border-field-border bg-transparent px-3 py-1 text-foreground outline-none transition-colors placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-danger aria-invalid:ring-danger/20 dark:aria-invalid:ring-danger/40\"\n\ninterface FieldProps {\n  className?: string\n  size?: StyledSize\n  label?: React.ReactNode\n  description?: React.ReactNode\n}\n\n/* Grup icindeki tek bir alan: etiket + kontrol, id eslesmesi burada. */\nfunction SubField({\n  id,\n  size,\n  label,\n  placeholder,\n  describedBy,\n  className,\n}: {\n  id: string\n  size: StyledSize\n  label: React.ReactNode\n  placeholder: string\n  describedBy?: string\n  className?: string\n}) {\n  return (\n    <div className={cn(\"flex min-w-0 flex-1 flex-col\", gap[size], className)}>\n      <label\n        htmlFor={id}\n        data-slot=\"styled-field-label\"\n        className={cn(\"w-fit font-medium leading-none text-foreground select-none\", labelText[size])}\n      >\n        {label}\n      </label>\n      <input id={id} placeholder={placeholder} aria-describedby={describedBy} className={cn(controlBase, controlHeight[size])} />\n    </div>\n  )\n}\n\nfunction GroupDescription({ id, size, children }: { id?: string; size: StyledSize; children: React.ReactNode }) {\n  if (children == null || children === false) return null\n  return (\n    <p id={id} data-slot=\"styled-field-description\" className={cn(\"leading-snug text-muted-foreground\", helpText[size])}>\n      {children}\n    </p>\n  )\n}\n\n/* Row: iki alan yan yana, esit genislikte; ortak aciklama altta. */\nexport function RowField({\n  className,\n  size = \"md\",\n  label = \"Full name\",\n  description = \"As it appears on your ID.\",\n}: FieldProps) {\n  const base = React.useId()\n  const firstId = `${base}-first`\n  const lastId = `${base}-last`\n  const descId = description != null && description !== false ? `${base}-desc` : undefined\n\n  return (\n    <div data-slot=\"styled-field\" className={cn(\"flex w-full max-w-md flex-col\", blockGap[size], className)}>\n      <span className={cn(\"font-medium leading-none text-foreground\", labelText[size])}>{label}</span>\n      <div className={cn(\"flex flex-row\", blockGap[size])}>\n        <SubField id={firstId} size={size} label=\"First\" placeholder=\"Ada\" describedBy={descId} />\n        <SubField id={lastId} size={size} label=\"Last\" placeholder=\"Lovelace\" describedBy={descId} />\n      </div>\n      <GroupDescription id={descId} size={size}>\n        {description}\n      </GroupDescription>\n    </div>\n  )\n}\n\n/* Split: asimetrik bolme - genis birincil alan + dar ikincil alan. */\nexport function SplitField({\n  className,\n  size = \"md\",\n  label = \"Amount\",\n  description = \"Enter the amount and pick a currency.\",\n}: FieldProps) {\n  const base = React.useId()\n  const amountId = `${base}-amount`\n  const currencyId = `${base}-currency`\n  const descId = description != null && description !== false ? `${base}-desc` : undefined\n\n  return (\n    <div data-slot=\"styled-field\" className={cn(\"flex w-full max-w-md flex-col\", blockGap[size], className)}>\n      <span className={cn(\"font-medium leading-none text-foreground\", labelText[size])}>{label}</span>\n      <div className={cn(\"flex flex-row\", blockGap[size])}>\n        <SubField id={amountId} size={size} label=\"Value\" placeholder=\"1,250.00\" describedBy={descId} className=\"flex-[2]\" />\n        <div className={cn(\"flex w-24 shrink-0 flex-col\", gap[size])}>\n          <label\n            htmlFor={currencyId}\n            data-slot=\"styled-field-label\"\n            className={cn(\"w-fit font-medium leading-none text-foreground select-none\", labelText[size])}\n          >\n            Currency\n          </label>\n          <select\n            id={currencyId}\n            aria-describedby={descId}\n            defaultValue=\"USD\"\n            className={cn(controlBase, controlHeight[size], \"cursor-pointer\")}\n          >\n            <option value=\"USD\">USD</option>\n            <option value=\"EUR\">EUR</option>\n            <option value=\"TRY\">TRY</option>\n          </select>\n        </div>\n      </div>\n      <GroupDescription id={descId} size={size}>\n        {description}\n      </GroupDescription>\n    </div>\n  )\n}\n\n/* Grid: iki sutunlu izgara, dort alan; dar ekranda tek sutuna duser. */\nexport function GridField({\n  className,\n  size = \"md\",\n  label = \"Shipping address\",\n  description = \"We ship to these details exactly as written.\",\n}: FieldProps) {\n  const base = React.useId()\n  const descId = description != null && description !== false ? `${base}-desc` : undefined\n  const cells = [\n    { key: \"street\", label: \"Street\", placeholder: \"12 Baker St\" },\n    { key: \"city\", label: \"City\", placeholder: \"London\" },\n    { key: \"zip\", label: \"Postal code\", placeholder: \"NW1 6XE\" },\n    { key: \"country\", label: \"Country\", placeholder: \"United Kingdom\" },\n  ]\n\n  return (\n    <div data-slot=\"styled-field\" className={cn(\"flex w-full max-w-md flex-col\", blockGap[size], className)}>\n      <span className={cn(\"font-medium leading-none text-foreground\", labelText[size])}>{label}</span>\n      <div className={cn(\"grid grid-cols-1 sm:grid-cols-2\", blockGap[size])}>\n        {cells.map((c) => (\n          <SubField\n            key={c.key}\n            id={`${base}-${c.key}`}\n            size={size}\n            label={c.label}\n            placeholder={c.placeholder}\n            describedBy={descId}\n          />\n        ))}\n      </div>\n      <GroupDescription id={descId} size={size}>\n        {description}\n      </GroupDescription>\n    </div>\n  )\n}\n\n/* Section: a titled section - a separator line plus the fields under the title. */\nexport function SectionField({\n  className,\n  size = \"md\",\n  label = \"Account\",\n  description = \"These details appear on your invoices.\",\n}: FieldProps) {\n  const base = React.useId()\n  const descId = description != null && description !== false ? `${base}-desc` : undefined\n\n  return (\n    <div\n      data-slot=\"styled-field\"\n      className={cn(\"flex w-full max-w-md flex-col rounded-xl border border-border bg-card p-4 text-card-foreground\", blockGap[size], className)}\n    >\n      <div className={cn(\"flex flex-col\", gap[size])}>\n        <h4 className={cn(\"font-semibold leading-none text-foreground\", labelText[size])}>{label}</h4>\n        <GroupDescription id={descId} size={size}>\n          {description}\n        </GroupDescription>\n      </div>\n      <div className=\"h-px w-full bg-border\" />\n      <div className={cn(\"flex flex-col\", blockGap[size])}>\n        <SubField id={`${base}-company`} size={size} label=\"Company\" placeholder=\"Acme Inc\" describedBy={descId} />\n        <SubField id={`${base}-vat`} size={size} label=\"VAT number\" placeholder=\"GB123456789\" describedBy={descId} />\n      </div>\n    </div>\n  )\n}\n\n/* Fieldset: gercek <fieldset> + <legend>, ilgili kontrolleri semantik olarak grupla. */\nexport function FieldsetField({\n  className,\n  size = \"md\",\n  label = \"Contact preferences\",\n  description = \"Pick how we should reach you.\",\n}: FieldProps) {\n  const base = React.useId()\n  const descId = description != null && description !== false ? `${base}-desc` : undefined\n  const options = [\n    { key: \"email\", label: \"Email\" },\n    { key: \"sms\", label: \"SMS\" },\n    { key: \"none\", label: \"Do not contact me\" },\n  ]\n\n  return (\n    <fieldset\n      data-slot=\"styled-field\"\n      aria-describedby={descId}\n      className={cn(\"flex w-full max-w-md flex-col rounded-xl border border-border p-4\", blockGap[size], className)}\n    >\n      <legend className={cn(\"px-1 font-medium leading-none text-foreground\", labelText[size])}>{label}</legend>\n      <GroupDescription id={descId} size={size}>\n        {description}\n      </GroupDescription>\n      <div className={cn(\"flex flex-col\", gap[size])}>\n        {options.map((o) => (\n          /* min-h-6: because a native <input> is a replaced element, the ::after hit-area recipe does NOT work THERE (the pseudo-element is not rendered). The fix is to seat the row on a 24px base: the input stays 16x16 but neighbouring input centres are >=24px apart, which satisfies WCAG 2.5.8's spacing exception. The label is already bound with htmlFor, so the clickable area covers the text as well. */\n          <div key={o.key} className=\"flex min-h-6 items-center gap-2\">\n            <input\n              type=\"radio\"\n              id={`${base}-${o.key}`}\n              name={`${base}-contact`}\n              defaultChecked={o.key === \"email\"}\n              className=\"size-4 shrink-0 accent-primary outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n            />\n            <label htmlFor={`${base}-${o.key}`} className={cn(\"leading-none text-foreground select-none\", helpText[size])}>\n              {o.label}\n            </label>\n          </div>\n        ))}\n      </div>\n    </fieldset>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/field-group.tsx"
    }
  ],
  "categories": [
    "styled",
    "field"
  ],
  "type": "registry:component"
}