{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button-group-icon",
  "title": "Group-icon button",
  "description": "Styled button: the Group-icon set. 5 decorative button variations (IconButtonGroup, LabelButtonGroup, MixedButtonGroup, CompactIconButtonGroup, TooltipButtonGroup) 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/button-group-icon.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Bold, Italic, Underline } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Icon button-group family: five icon-first action groups. The difference is how\n   much of the label is visible: icon only, icon + label, mixed, a compressed icon\n   rail, and icons with a keyboard-reachable tooltip. An aria-label is MANDATORY\n   on every icon-only button: with no visible text it is the only source of an\n   accessible name. The tooltip also opens on focus (not hover-only) and closes\n   with Escape. Color comes ONLY from semantic tokens, via alpha color-mix.\n   Deterministic. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\n/* Base Button olcegiyle hizali: sm=h-8, md=h-9, lg=h-10, xl=h-12. */\nconst sizes: Record<StyledSize, string> = {\n  sm: \"h-8 px-3 text-sm\",\n  md: \"h-9 px-4 text-sm\",\n  lg: \"h-10 px-5 text-sm\",\n  xl: \"h-12 px-6 text-base\",\n}\n\nconst iconSizes: Record<StyledSize, string> = {\n  sm: \"size-8\",\n  md: \"size-9\",\n  lg: \"size-10\",\n  xl: \"size-12\",\n}\n\ntype Action = {\n  label?: string\n  icon?: React.ReactNode\n  onClick?: () => void\n}\n\ntype GroupProps = {\n  className?: string\n  size?: StyledSize\n  actions?: Action[]\n}\n\nconst defaultActions: Action[] = [\n  { label: \"Bold\", icon: <Bold /> },\n  { label: \"Italic\", icon: <Italic /> },\n  { label: \"Underline\", icon: <Underline /> },\n]\n\nconst item =\n  \"relative inline-flex shrink-0 select-none items-center justify-center gap-2 font-medium whitespace-nowrap outline-none transition-colors focus-visible:z-10 focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\nconst outline =\n  \"border border-field-border bg-background text-foreground hover:bg-secondary hover:text-secondary-foreground\"\n\n/* Icon: icon only, an attached row sharing its edges. The label is an aria-label. */\nexport function IconButtonGroup({ className, size = \"md\", actions = defaultActions }: GroupProps) {\n  return (\n    <div\n      data-slot=\"styled-button-group\"\n      role=\"group\"\n      aria-label=\"Text formatting\"\n      className={cn(\"inline-flex isolate\", className)}\n    >\n      {actions.map((action, i) => (\n        <button\n          key={i}\n          type=\"button\"\n          onClick={action.onClick}\n          aria-label={action.label}\n          data-slot=\"styled-button-group-item\"\n          className={cn(\n            item,\n            outline,\n            iconSizes[size],\n            i > 0 && \"-ml-px\",\n            i === 0 && \"rounded-l-lg\",\n            i === actions.length - 1 && \"rounded-r-lg\"\n          )}\n        >\n          {action.icon}\n        </button>\n      ))}\n    </div>\n  )\n}\n\n/* Label: ikon + gorunur etiket. Erisilebilir ad metinden gelir. */\nexport function LabelButtonGroup({ className, size = \"md\", actions = defaultActions }: GroupProps) {\n  return (\n    <div\n      data-slot=\"styled-button-group\"\n      role=\"group\"\n      aria-label=\"Text formatting\"\n      className={cn(\"inline-flex isolate\", className)}\n    >\n      {actions.map((action, i) => (\n        <button\n          key={i}\n          type=\"button\"\n          onClick={action.onClick}\n          data-slot=\"styled-button-group-item\"\n          className={cn(\n            item,\n            outline,\n            sizes[size],\n            i > 0 && \"-ml-px\",\n            i === 0 && \"rounded-l-lg\",\n            i === actions.length - 1 && \"rounded-r-lg\"\n          )}\n        >\n          {action.icon}\n          {action.label}\n        </button>\n      ))}\n    </div>\n  )\n}\n\n/* Mixed: ilk aksiyon etiketli birincil, kalanlar ikon-only. */\nexport function MixedButtonGroup({ className, size = \"md\", actions = defaultActions }: GroupProps) {\n  const [primary, ...rest] = actions\n\n  return (\n    <div\n      data-slot=\"styled-button-group\"\n      role=\"group\"\n      aria-label=\"Text formatting\"\n      className={cn(\"inline-flex isolate\", className)}\n    >\n      {primary ? (\n        <button\n          type=\"button\"\n          onClick={primary.onClick}\n          data-slot=\"styled-button-group-item\"\n          className={cn(\n            item,\n            \"rounded-l-lg bg-primary text-primary-foreground hover:bg-primary/90\",\n            sizes[size]\n          )}\n        >\n          {primary.icon}\n          {primary.label}\n        </button>\n      ) : null}\n      {rest.map((action, i) => (\n        <button\n          key={i}\n          type=\"button\"\n          onClick={action.onClick}\n          aria-label={action.label}\n          data-slot=\"styled-button-group-item\"\n          className={cn(\n            item,\n            outline,\n            iconSizes[size],\n            \"-ml-px\",\n            i === rest.length - 1 && \"rounded-r-lg\"\n          )}\n        >\n          {action.icon}\n        </button>\n      ))}\n    </div>\n  )\n}\n\n/* CompactIcon: sikistirilmis ikon rayi, tek kap icinde kucuk radiuslu ikonlar. */\nexport function CompactIconButtonGroup({\n  className,\n  size = \"md\",\n  actions = defaultActions,\n}: GroupProps) {\n  return (\n    <div\n      data-slot=\"styled-button-group\"\n      role=\"group\"\n      aria-label=\"Text formatting\"\n      className={cn(\n        \"inline-flex items-center gap-0.5 rounded-lg border border-field-border bg-background p-0.5\",\n        className\n      )}\n    >\n      {actions.map((action, i) => (\n        <button\n          key={i}\n          type=\"button\"\n          onClick={action.onClick}\n          aria-label={action.label}\n          data-slot=\"styled-button-group-item\"\n          className={cn(\n            item,\n            \"rounded-md text-muted-foreground hover:bg-secondary hover:text-secondary-foreground\",\n            iconSizes[size]\n          )}\n        >\n          {action.icon}\n        </button>\n      ))}\n    </div>\n  )\n}\n\n/* Tooltip: icon-only buttons plus a real tooltip. It opens on focus AND hover and closes on Escape; the hint is bound to the button with aria-describedby, so the accessible name still comes from aria-label. The open index is held in the root component: because no subtree unmounts, the state is never lost. */\nexport function TooltipButtonGroup({ className, size = \"md\", actions = defaultActions }: GroupProps) {\n  const [open, setOpen] = React.useState<number | null>(null)\n  const uid = React.useId()\n\n  React.useEffect(() => {\n    if (open === null) return\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") setOpen(null)\n    }\n    window.addEventListener(\"keydown\", onKey)\n    return () => window.removeEventListener(\"keydown\", onKey)\n  }, [open])\n\n  return (\n    <div\n      data-slot=\"styled-button-group\"\n      role=\"group\"\n      aria-label=\"Text formatting\"\n      className={cn(\"inline-flex isolate\", className)}\n    >\n      {actions.map((action, i) => {\n        const tipId = `${uid}-tip-${i}`\n        const isOpen = open === i\n\n        return (\n          <span key={i} className=\"relative inline-flex\">\n            <button\n              type=\"button\"\n              onClick={action.onClick}\n              aria-label={action.label}\n              aria-describedby={isOpen ? tipId : undefined}\n              onFocus={() => setOpen(i)}\n              onBlur={() => setOpen((v) => (v === i ? null : v))}\n              onPointerEnter={() => setOpen(i)}\n              onPointerLeave={() => setOpen((v) => (v === i ? null : v))}\n              data-slot=\"styled-button-group-item\"\n              className={cn(\n                item,\n                outline,\n                iconSizes[size],\n                i > 0 && \"-ml-px\",\n                i === 0 && \"rounded-l-lg\",\n                i === actions.length - 1 && \"rounded-r-lg\"\n              )}\n            >\n              {action.icon}\n            </button>\n            {isOpen && action.label ? (\n              <span\n                id={tipId}\n                role=\"tooltip\"\n                data-slot=\"styled-button-group-tooltip\"\n                className=\"pointer-events-none absolute bottom-full left-1/2 z-30 mb-2 -translate-x-1/2 rounded-md border border-border bg-popover px-2 py-1 text-xs font-medium whitespace-nowrap text-popover-foreground shadow-md\"\n              >\n                {action.label}\n              </span>\n            ) : null}\n          </span>\n        )\n      })}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/button-group-icon.tsx"
    }
  ],
  "categories": [
    "styled",
    "button"
  ],
  "type": "registry:component"
}