{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "suggestion-chips",
  "title": "Suggestion Chips",
  "description": "Starter or follow-up prompts shown as wrapping pills, a single scrolling row, or cards with descriptions, with loading and refresh states.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "files": [
    {
      "path": "registry/tealmac/suggestion-chips.tsx",
      "content": "// Adapted from Ballmac UI (https://github.com/vamsiy78/ballmac-ui), MIT License, Copyright (c) 2026 Ballmac.\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowUpRight, RefreshCw } from \"lucide-react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype Suggestion = {\n  /** Text on the chip. */\n  label: string\n  /** Text sent when chosen. Defaults to `label`. */\n  prompt?: string\n  /** Second line, used by the \"cards\" layout. */\n  description?: string\n  /** Leading icon. */\n  icon?: React.ReactNode\n}\n\ntype SuggestionChipsProps = Omit<React.ComponentProps<\"div\">, \"onSelect\"> & {\n  /** The prompts to offer. */\n  suggestions: Suggestion[]\n  /** Called with the prompt text and the suggestion that was chosen. */\n  onSelect?: (prompt: string, suggestion: Suggestion) => void\n  /** \"pills\" wraps onto several lines, \"scroll\" keeps one swipeable row with faded edges, \"cards\" is a grid with descriptions. */\n  variant?: \"pills\" | \"scroll\" | \"cards\"\n  /** Accessible name of the group. */\n  label?: string\n  /** When set, a refresh button appears after the chips. */\n  onRefresh?: () => void\n  /** Accessible name of the refresh button. */\n  refreshLabel?: string\n  /** Disables every chip, for example while a reply is streaming. */\n  disabled?: boolean\n  /** Shows placeholder chips while suggestions load. */\n  loading?: boolean\n}\n\nconst chipBase =\n  \"inline-flex shrink-0 items-center gap-2 rounded-full border bg-background text-sm font-medium text-foreground shadow-xs outline-none transition-[color,background-color,border-color,box-shadow,transform] duration-150 select-none hover:border-foreground/25 hover:bg-accent focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50 motion-reduce:transition-none motion-reduce:active:scale-100 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground\"\n\nfunction SuggestionChips({\n  suggestions,\n  onSelect,\n  variant = \"pills\",\n  label,\n  onRefresh,\n  refreshLabel,\n  disabled = false,\n  loading = false,\n  className,\n  ...props\n}: SuggestionChipsProps) {\n  label ??= \"Suggested prompts\"\n  refreshLabel ??= \"Show other suggestions\"\n  const reduce = useReducedMotion()\n  const [spins, setSpins] = React.useState(0)\n\n  function choose(s: Suggestion) {\n    onSelect?.(s.prompt ?? s.label, s)\n  }\n\n  const rootClass =\n    variant === \"cards\"\n      ? \"grid w-full gap-2 sm:grid-cols-2\"\n      : variant === \"scroll\"\n        ? \"-mx-1 flex w-full snap-x gap-2 overflow-x-auto px-1 py-1 [-ms-overflow-style:none] [scrollbar-width:none] [mask-image:linear-gradient(to_right,transparent,black_1.25rem,black_calc(100%-1.25rem),transparent)] [&::-webkit-scrollbar]:hidden\"\n        : \"flex w-full flex-wrap gap-2\"\n\n  const item = (i: number) => ({\n    initial: reduce ? { opacity: 0 } : { opacity: 0, y: 8 },\n    animate: { opacity: 1, y: 0 },\n    transition: {\n      duration: 0.28,\n      delay: reduce ? 0 : Math.min(i, 8) * 0.04,\n      ease: [0.22, 1, 0.36, 1] as const,\n    },\n  })\n\n  return (\n    <div\n      data-slot=\"suggestion-chips\"\n      data-variant={variant}\n      role=\"group\"\n      aria-label={label}\n      aria-busy={loading || undefined}\n      className={cn(rootClass, className)}\n      {...props}\n    >\n      {loading\n        ? Array.from({ length: 4 }, (_, i) => (\n            <span\n              key={i}\n              aria-hidden=\"true\"\n              className={cn(\n                \"bg-muted animate-pulse motion-reduce:animate-none\",\n                variant === \"cards\"\n                  ? \"h-16 rounded-xl\"\n                  : \"h-9 shrink-0 rounded-full\",\n                variant !== \"cards\" && [\"w-32\", \"w-44\", \"w-28\", \"w-40\"][i]\n              )}\n            />\n          ))\n        : suggestions.map((s, i) =>\n            variant === \"cards\" ? (\n              <motion.button\n                key={s.label}\n                type=\"button\"\n                disabled={disabled}\n                onClick={() => choose(s)}\n                {...item(i)}\n                className=\"group/chip bg-card hover:border-foreground/25 hover:bg-accent/60 focus-visible:border-ring focus-visible:ring-ring/50 flex min-w-0 items-start gap-3 rounded-xl border p-3.5 text-start shadow-xs transition-[border-color,background-color,box-shadow] duration-150 outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 motion-reduce:transition-none\"\n              >\n                {s.icon && (\n                  <span\n                    aria-hidden=\"true\"\n                    className=\"bg-muted text-foreground mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg border [&_svg]:size-4\"\n                  >\n                    {s.icon}\n                  </span>\n                )}\n                <span className=\"grid min-w-0 flex-1 gap-0.5\">\n                  <span className=\"text-foreground text-sm font-medium\">\n                    {s.label}\n                  </span>\n                  {s.description && (\n                    <span className=\"text-muted-foreground line-clamp-2 text-[13px] leading-5\">\n                      {s.description}\n                    </span>\n                  )}\n                </span>\n                <ArrowUpRight\n                  aria-hidden=\"true\"\n                  className=\"text-muted-foreground mt-1 size-4 shrink-0 opacity-0 transition-[opacity,transform] duration-150 group-hover/chip:translate-x-0.5 group-hover/chip:-translate-y-0.5 group-hover/chip:opacity-100 group-focus-visible/chip:opacity-100 motion-reduce:transition-none rtl:-scale-x-100\"\n                />\n              </motion.button>\n            ) : (\n              <motion.button\n                key={s.label}\n                type=\"button\"\n                disabled={disabled}\n                onClick={() => choose(s)}\n                {...item(i)}\n                className={cn(\n                  chipBase,\n                  \"h-9 px-3.5\",\n                  variant === \"scroll\" && \"snap-start\",\n                  s.icon && \"ps-3\"\n                )}\n              >\n                {s.icon}\n                <span className=\"whitespace-nowrap\">{s.label}</span>\n              </motion.button>\n            )\n          )}\n      {onRefresh && !loading && (\n        <button\n          type=\"button\"\n          disabled={disabled}\n          aria-label={refreshLabel}\n          title={refreshLabel}\n          onClick={() => {\n            setSpins((n) => n + 1)\n            onRefresh()\n          }}\n          className={cn(\n            \"text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-ring/50 inline-flex size-9 shrink-0 items-center justify-center rounded-full transition-colors outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50\",\n            variant === \"cards\" && \"justify-self-start sm:col-span-2\"\n          )}\n        >\n          <motion.span\n            aria-hidden=\"true\"\n            animate={{ rotate: reduce ? 0 : spins * 180 }}\n            transition={{ type: \"spring\", stiffness: 240, damping: 20 }}\n            className=\"flex\"\n          >\n            <RefreshCw className=\"size-4\" />\n          </motion.span>\n        </button>\n      )}\n    </div>\n  )\n}\n\nexport { SuggestionChips, type SuggestionChipsProps, type Suggestion }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}