{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dock",
  "title": "Dock",
  "description": "A toolbar of app icons that bounce when picked, with labels on hover and arrow-key navigation.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/manniche/dock/dock.tsx",
      "content": "// Based on Watermelon UI's “Dock” (MIT, © 2026 Watermelon Platform Contributors,\n// github.com/WatermelonCorp/watermelon-platform). Rewritten as a real toolbar with labels, arrow keys and a controlled value.\nimport { MotionConfig, motion } from 'motion/react'\nimport { useRef, useState, type KeyboardEvent, type ReactNode } from 'react'\nimport { cn } from '@/lib/utils'\n\nexport type DockItem = { id: string; label: string; icon: ReactNode }\n\nexport type DockProps = {\n  items: DockItem[]\n  value: string | null\n  onValueChange: (id: string) => void\n  /** Accessible name for the toolbar. */\n  label?: string\n  className?: string\n}\n\n/** A row of app icons that lift on hover and bounce when picked. The open one gets a dot. */\nexport function Dock({ items, value, onValueChange, label = 'Dock', className }: DockProps) {\n  const [bounce, setBounce] = useState<{ id: string; n: number } | null>(null)\n  const refs = useRef<(HTMLButtonElement | null)[]>([])\n\n  const pick = (id: string) => {\n    onValueChange(id)\n    setBounce((b) => ({ id, n: (b?.n ?? 0) + 1 }))\n  }\n\n  // One tab stop for the whole dock; the arrow keys move between icons.\n  const keys = (e: KeyboardEvent, i: number) => {\n    const step = { ArrowRight: 1, ArrowLeft: -1 }[e.key]\n    const to = step ? (i + step + items.length) % items.length : e.key === 'Home' ? 0 : e.key === 'End' ? items.length - 1 : -1\n    if (to < 0) return\n    e.preventDefault()\n    refs.current[to]?.focus()\n  }\n\n  const focusable = Math.max(0, items.findIndex((it) => it.id === value))\n\n  return (\n    <MotionConfig reducedMotion=\"user\">\n      <div\n        role=\"toolbar\"\n        aria-label={label}\n        className={cn('inline-flex items-end gap-2 rounded-3xl border bg-card px-2.5 pt-2.5 pb-1.5 shadow-sm', className)}\n      >\n        {items.map((it, i) => {\n          const active = it.id === value\n          return (\n            <div key={it.id} className=\"group relative flex flex-col items-center\">\n              <span className=\"pointer-events-none absolute -top-9 rounded-lg bg-foreground px-2 py-1 text-xs whitespace-nowrap text-background opacity-0 transition-opacity duration-150 group-hover:opacity-100 group-has-focus-visible:opacity-100\">\n                {it.label}\n              </span>\n              <motion.button\n                ref={(el) => {\n                  refs.current[i] = el\n                }}\n                type=\"button\"\n                aria-label={it.label}\n                aria-pressed={active}\n                tabIndex={i === focusable ? 0 : -1}\n                onClick={() => pick(it.id)}\n                onKeyDown={(e) => keys(e, i)}\n                whileHover={{ y: -4 }}\n                transition={{ type: 'spring', stiffness: 550, damping: 15, mass: 1.1 }}\n                className=\"rounded-xl\"\n              >\n                {/* A new key replays the bounce without remounting the button, so focus stays put. */}\n                <motion.span\n                  key={bounce?.id === it.id ? bounce.n : 0}\n                  style={{ transformOrigin: 'bottom' }}\n                  initial={bounce?.id === it.id ? { scale: 1.3, y: -8 } : false}\n                  animate={{ scale: 1, y: 0 }}\n                  transition={{ type: 'spring', stiffness: 550, damping: 15, mass: 1.1 }}\n                  className={cn(\n                    'grid size-11 place-items-center rounded-xl bg-muted transition-colors duration-200 [&_svg]:size-5',\n                    active ? 'text-foreground' : 'text-muted-foreground hover:text-foreground',\n                  )}\n                >\n                  {it.icon}\n                </motion.span>\n              </motion.button>\n              <span\n                aria-hidden\n                className={cn('mt-1 size-1 rounded-full bg-foreground/50 transition-opacity duration-300', active ? 'opacity-100' : 'opacity-0')}\n              />\n            </div>\n          )\n        })}\n      </div>\n    </MotionConfig>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "meta": {
    "credit": "Adapted from Watermelon UI (MIT)."
  },
  "categories": [
    "navigation"
  ],
  "type": "registry:component"
}