{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "input-group-action",
  "title": "Group-action input",
  "description": "Styled input: the Group-action set. 5 decorative input variations (CopyGroup, PasteGroup, RevealGroup, GenerateGroup, LoadingGroup) on the ai2 token system, driven by CSS token transitions and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/input-group-action.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, ClipboardPaste, Copy, Eye, EyeOff, Loader2, RefreshCw } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Action input group family: 5 trailing action buttons, with REAL behavior.\n   copy (navigator.clipboard.writeText; wrapped in try/catch so it cannot throw, and\n   the copied state is announced with aria-live), paste (clipboard.readText, the same\n   protection), reveal (a real password show/hide, with the state announced through\n   aria-pressed), generate (a DETERMINISTIC generator: NO Math.random, a token is\n   produced from an incrementing index counter over a fixed alphabet - the same index\n   always gives the same value), loading (a fixed-duration loading state, with the\n   timeout cleared in the effect cleanup).\n   All useState lives at the root level, never in a subtree that unmounts.\n   Color comes ONLY from tokens, via alpha color-mix. No motion (the loader's\n   animate-spin deliberately keeps spinning so it never reads as a hang). */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst height: 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 rootBase =\n  \"group inline-flex w-64 max-w-full items-center overflow-hidden rounded-lg border border-field-border bg-transparent transition-colors duration-(--motion-base) focus-within:border-primary focus-within:ring-[3px] focus-within:ring-ring/50 [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\nconst fieldBase =\n  \"h-full w-full min-w-0 bg-transparent px-3 text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\"\n\nconst actionBtn =\n  \"flex h-full shrink-0 select-none items-center gap-1.5 border-l border-field-border bg-secondary px-3 text-sm font-medium text-secondary-foreground outline-none transition-colors hover:bg-surface-3 focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50\"\n\nconst iconActionBtn =\n  \"mr-1.5 inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)] hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\ntype Props = Omit<React.ComponentProps<\"input\">, \"size\"> & { size?: StyledSize }\n\n/* Copy: gercek pano kopyalama. clipboard reddedebilir (izin/guvensiz baglam),\n   bu yuzden try/catch; hata durumunda buton sessizce eski haline doner. */\nexport function CopyGroup({ className, size = \"md\", ...props }: Props) {\n  const id = React.useId()\n  const [value, setValue] = React.useState(\"https://ai2.dev/r/button.json\")\n  const [copied, setCopied] = React.useState(false)\n\n  React.useEffect(() => {\n    if (!copied) return\n    const t = window.setTimeout(() => setCopied(false), 1500)\n    return () => window.clearTimeout(t)\n  }, [copied])\n\n  return (\n    <div data-slot=\"styled-input-group\" className={cn(rootBase, height[size], className)}>\n      <label htmlFor={id} className=\"sr-only\">\n        Copyable value\n      </label>\n      <input\n        id={id}\n        {...props}\n        value={value}\n        onChange={(e) => setValue(e.target.value)}\n        className={fieldBase}\n      />\n      <span aria-live=\"polite\" className=\"sr-only\">\n        {copied ? \"Copied to clipboard\" : \"\"}\n      </span>\n      <button\n        type=\"button\"\n        aria-label={copied ? \"Copied\" : \"Copy value\"}\n        className={actionBtn}\n        onClick={async () => {\n          try {\n            await navigator.clipboard.writeText(value)\n            setCopied(true)\n          } catch {\n            setCopied(false)\n          }\n        }}\n      >\n        {copied ? <Check className=\"text-success\" /> : <Copy />}\n        <span>{copied ? \"Copied\" : \"Copy\"}</span>\n      </button>\n    </div>\n  )\n}\n\n/* Paste: panodan okur. Okuma izni reddedilebilir; try/catch ile alan degismez. */\nexport function PasteGroup({ className, size = \"md\", ...props }: Props) {\n  const id = React.useId()\n  const [value, setValue] = React.useState(\"\")\n\n  return (\n    <div data-slot=\"styled-input-group\" className={cn(rootBase, height[size], className)}>\n      <label htmlFor={id} className=\"sr-only\">\n        Pasteable value\n      </label>\n      <input\n        id={id}\n        placeholder=\"Paste a value\"\n        {...props}\n        value={value}\n        onChange={(e) => setValue(e.target.value)}\n        className={fieldBase}\n      />\n      <button\n        type=\"button\"\n        aria-label=\"Paste from clipboard\"\n        className={actionBtn}\n        onClick={async () => {\n          try {\n            const text = await navigator.clipboard.readText()\n            if (text) setValue(text)\n          } catch {\n            /* Without clipboard read permission the field is left as it is. */\n          }\n        }}\n      >\n        <ClipboardPaste />\n        <span>Paste</span>\n      </button>\n    </div>\n  )\n}\n\n/* Reveal: gercek sifre goster/gizle. Durum aria-pressed + degisen aria-label\n   ile duyurulur. */\nexport function RevealGroup({ className, size = \"md\", ...props }: Props) {\n  const id = React.useId()\n  const [shown, setShown] = React.useState(false)\n\n  return (\n    <div data-slot=\"styled-input-group\" className={cn(rootBase, height[size], className)}>\n      <label htmlFor={id} className=\"sr-only\">\n        Password\n      </label>\n      <input\n        id={id}\n        placeholder=\"Password\"\n        autoComplete=\"current-password\"\n        {...props}\n        type={shown ? \"text\" : \"password\"}\n        className={fieldBase}\n      />\n      <button\n        type=\"button\"\n        aria-pressed={shown}\n        aria-label={shown ? \"Hide password\" : \"Show password\"}\n        className={iconActionBtn}\n        onClick={() => setShown((v) => !v)}\n      >\n        {shown ? <EyeOff /> : <Eye />}\n      </button>\n    </div>\n  )\n}\n\n/* Deterministic token generator: NO Math.random. The given index is mapped to a fixed alphabet through a plain multiply and modulo, so the same index always produces the same token (SSR and the client agree). */\nconst ALPHABET = \"abcdefghijkmnpqrstuvwxyz23456789\"\n\nfunction tokenAt(index: number) {\n  let out = \"\"\n  for (let i = 0; i < 12; i++) {\n    const step = (index + 1) * 7 + i * 13\n    out += ALPHABET[step % ALPHABET.length]\n  }\n  return out\n}\n\n/* Generate: increments the counter on every click and writes the deterministic token into the field. */\nexport function GenerateGroup({ className, size = \"md\", ...props }: Props) {\n  const id = React.useId()\n  const [index, setIndex] = React.useState(0)\n  const [value, setValue] = React.useState(() => tokenAt(0))\n\n  return (\n    <div data-slot=\"styled-input-group\" className={cn(rootBase, height[size], className)}>\n      <label htmlFor={id} className=\"sr-only\">\n        Generated token\n      </label>\n      <input\n        id={id}\n        {...props}\n        value={value}\n        onChange={(e) => setValue(e.target.value)}\n        className={cn(fieldBase, \"font-mono\")}\n      />\n      <button\n        type=\"button\"\n        aria-label=\"Generate a new token\"\n        className={actionBtn}\n        onClick={() => {\n          const next = index + 1\n          setIndex(next)\n          setValue(tokenAt(next))\n        }}\n      >\n        <RefreshCw />\n        <span>New</span>\n      </button>\n    </div>\n  )\n}\n\n/* Loading: sabit sureli (1200ms) yukleme durumu; timeout effect cleanup'inda\n   temizlenir, bu yuzden unmount sonrasi setState olmaz. */\nexport function LoadingGroup({ className, size = \"md\", ...props }: Props) {\n  const id = React.useId()\n  const [loading, setLoading] = React.useState(false)\n\n  React.useEffect(() => {\n    if (!loading) return\n    const t = window.setTimeout(() => setLoading(false), 1200)\n    return () => window.clearTimeout(t)\n  }, [loading])\n\n  return (\n    <div data-slot=\"styled-input-group\" className={cn(rootBase, height[size], className)}>\n      <label htmlFor={id} className=\"sr-only\">\n        Value to check\n      </label>\n      <input id={id} placeholder=\"Value\" {...props} className={fieldBase} />\n      <span aria-live=\"polite\" className=\"sr-only\">\n        {loading ? \"Checking\" : \"\"}\n      </span>\n      <button\n        type=\"button\"\n        aria-label={loading ? \"Checking\" : \"Check value\"}\n        disabled={loading}\n        className={actionBtn}\n        onClick={() => setLoading(true)}\n      >\n        {loading ? <Loader2 className=\"animate-spin\" /> : null}\n        <span>{loading ? \"Checking\" : \"Check\"}</span>\n      </button>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/input-group-action.tsx"
    }
  ],
  "categories": [
    "styled",
    "input"
  ],
  "type": "registry:component"
}