{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ai-action-bar",
  "title": "AI action bar",
  "description": "A toolbar that morphs into a one-line prompt for the agent, and back.",
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/manniche/ai-action-bar/ai-action-bar.tsx",
      "content": "// Based on Watermelon UI's “Contextual AI bar” (MIT, © 2026 Watermelon Platform Contributors,\n// github.com/WatermelonCorp/watermelon-platform). Rewritten with named tools and a working submit.\nimport { ArrowUp, Sparkles, Wrench } from 'lucide-react'\nimport { AnimatePresence, LayoutGroup, MotionConfig, motion } from 'motion/react'\nimport { useId, useState, type ComponentType, type SubmitEvent } from 'react'\nimport { cn } from '@/lib/utils'\n\nexport type AiBarTool = {\n  id: string\n  /** Read aloud and shown as a tooltip. */\n  label: string\n  icon: ComponentType<{ className?: string }>\n  onSelect?: () => void\n}\n\nexport type AiActionBarProps = {\n  tools: AiBarTool[]\n  /** Runs with the instruction when the person sends it. */\n  onAsk: (prompt: string) => void\n  placeholder?: string\n  labels?: { tools?: string; ask?: string; send?: string }\n  defaultMode?: 'tools' | 'ask'\n  className?: string\n}\n\n/** A toolbar that morphs into a one-line prompt for the AI, and back. */\nexport function AiActionBar({ tools, onAsk, placeholder = 'Ask AI to change it…', labels = {}, defaultMode = 'tools', className }: AiActionBarProps) {\n  const [mode, setMode] = useState(defaultMode)\n  const [text, setText] = useState('')\n  const group = useId()\n  const { tools: toolsLabel = 'Tools', ask = 'Ask AI', send = 'Send' } = labels\n  const spring = { type: 'spring', stiffness: 260, damping: 32 } as const\n\n  const submit = (e: SubmitEvent<HTMLFormElement>) => {\n    e.preventDefault()\n    const p = text.trim()\n    if (!p) return\n    onAsk(p)\n    setText('')\n  }\n\n  const modes = [\n    { id: 'tools', label: toolsLabel, Icon: Wrench },\n    { id: 'ask', label: ask, Icon: Sparkles },\n  ] as const\n\n  return (\n    <MotionConfig reducedMotion=\"user\" transition={spring}>\n      <LayoutGroup id={group}>\n        <motion.div layout style={{ borderRadius: 28 }} className={cn('flex w-full max-w-md items-center gap-1 border bg-card p-1 shadow-sm', className)}>\n          <div role=\"radiogroup\" aria-label=\"Mode\" className=\"flex shrink-0 items-center gap-0.5 rounded-full bg-background p-1 shadow-sm\">\n            {modes.map(({ id, label, Icon }) => (\n              <button\n                key={id}\n                type=\"button\"\n                role=\"radio\"\n                aria-checked={mode === id}\n                aria-label={label}\n                title={label}\n                onClick={() => setMode(id)}\n                className=\"relative grid size-10 place-items-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n              >\n                {mode === id && <motion.span layoutId=\"mode\" className=\"absolute inset-0 rounded-full bg-muted\" />}\n                <Icon className=\"relative size-[18px]\" aria-hidden />\n              </button>\n            ))}\n          </div>\n\n          <AnimatePresence mode=\"popLayout\" initial={false}>\n            {mode === 'tools' ? (\n              <motion.div\n                key=\"tools\"\n                initial={{ opacity: 0, x: 24, filter: 'blur(4px)' }}\n                animate={{ opacity: 1, x: 0, filter: 'blur(0px)' }}\n                exit={{ opacity: 0, x: 24, filter: 'blur(4px)' }}\n                className=\"flex flex-1 items-center justify-end gap-1 pr-1\"\n              >\n                {tools.map(({ id, label, icon: Icon, onSelect }) => (\n                  <button\n                    key={id}\n                    type=\"button\"\n                    onClick={onSelect}\n                    aria-label={label}\n                    title={label}\n                    className=\"grid size-10 place-items-center rounded-full text-foreground transition-[background-color,transform] duration-150 hover:bg-muted active:scale-[0.92]\"\n                  >\n                    <Icon className=\"size-[18px]\" />\n                  </button>\n                ))}\n              </motion.div>\n            ) : (\n              <motion.form\n                key=\"ask\"\n                onSubmit={submit}\n                initial={{ opacity: 0 }}\n                animate={{ opacity: 1 }}\n                exit={{ opacity: 0 }}\n                className=\"flex min-w-0 flex-1 items-center gap-1 pl-2\"\n              >\n                <input\n                  autoFocus\n                  value={text}\n                  onChange={(e) => setText(e.target.value)}\n                  placeholder={placeholder}\n                  aria-label={ask}\n                  className=\"min-w-0 flex-1 bg-transparent text-base outline-none placeholder:text-muted-foreground\"\n                />\n                <button\n                  type=\"submit\"\n                  aria-label={send}\n                  disabled={!text.trim()}\n                  className=\"grid size-10 shrink-0 place-items-center rounded-full bg-foreground text-background transition-[opacity,transform] duration-150 active:scale-[0.92] disabled:opacity-30\"\n                >\n                  <ArrowUp className=\"size-[18px]\" aria-hidden />\n                </button>\n              </motion.form>\n            )}\n          </AnimatePresence>\n        </motion.div>\n      </LayoutGroup>\n    </MotionConfig>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "meta": {
    "credit": "Adapted from Watermelon UI (MIT)."
  },
  "categories": [
    "agent"
  ],
  "type": "registry:component"
}