{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dock",
  "type": "registry:ui",
  "title": "Dock",
  "description": "macOS-style dock bar where icons magnify on cursor proximity. Playful navigation accent.",
  "dependencies": [
    "@saasflare/ui",
    "motion"
  ],
  "files": [
    {
      "path": "components/ui/dock.tsx",
      "type": "registry:ui",
      "content": "\"use client\"\n\n/**\n * @fileoverview macOS-style dock with magnification effect.\n * @author Saasflare™\n * Renders a horizontal dock bar where items magnify on mouse proximity.\n * Uses Motion springs for smooth scaling with natural physics.\n * @module packages/ui/components/ui/dock\n * @package ui\n *\n * @component\n * @example\n * import { Dock, DockItem } from '@saasflare/ui';\n * <Dock>\n *   <DockItem label=\"Home\"><HomeIcon className=\"size-6\" /></DockItem>\n *   <DockItem label=\"Search\"><SearchIcon className=\"size-6\" /></DockItem>\n *   <DockItem label=\"Settings\"><SettingsIcon className=\"size-6\" /></DockItem>\n * </Dock>\n *\n * @example\n * // Custom magnification range\n * <Dock magnification={1.8} distance={120}>\n *   {navItems.map(item => (\n *     <DockItem key={item.id} label={item.name} onClick={item.action}>\n *       {item.icon}\n *     </DockItem>\n *   ))}\n * </Dock>\n */\n\nimport {\n  createContext,\n  useContext,\n  useEffect,\n  useRef,\n  type ReactNode,\n  type MouseEvent as ReactMouseEvent,\n} from \"react\"\nimport {\n  m,\n  useMotionValue,\n  useSpring,\n  useTransform,\n  type MotionValue,\n} from \"motion/react\"\nimport { cn } from \"@saasflare/ui\"\nimport { useSaasflareMotion } from \"@saasflare/ui\"\nimport { useSaasflareProps, type SaasflareComponentProps } from \"@saasflare/ui\"\n\n/* ── Dock context ── */\n\ninterface DockContextValue {\n  mouseX: MotionValue<number>\n  magnification: number\n  distance: number\n  reduced: boolean\n}\n\nconst DockContext = createContext<DockContextValue | null>(null)\n\nfunction useDock(): DockContextValue {\n  const ctx = useContext(DockContext)\n  if (!ctx) throw new Error(\"DockItem must be used within a Dock\")\n  return ctx\n}\n\n/* ── Dock ── */\n\n/** Props for the Dock container. */\nexport interface DockProps extends SaasflareComponentProps {\n  /** Dock items. */\n  children: ReactNode\n  /** Maximum scale factor for magnified items. Default: `1.5` */\n  magnification?: number\n  /** Mouse proximity distance in pixels that triggers magnification. Default: `100` */\n  distance?: number\n  /** Additional class names. */\n  className?: string\n}\n\n/**\n * Horizontal dock bar with proximity-based item magnification.\n *\n * - Items scale up as the mouse approaches them\n * - Uses spring physics for smooth, elastic scaling\n * - Falls back to a static icon bar when reduced motion is preferred\n * - Tracks mouse X position across the entire dock\n *\n * @component\n * @package ui\n */\nexport function Dock({\n  children,\n  magnification = 1.5,\n  distance = 100,\n  className,\n  surface,\n  radius,\n  animated,\n  iconWeight,\n}: DockProps) {\n  const sf = useSaasflareProps({ surface, radius, animated, iconWeight })\n  const motion = useSaasflareMotion(sf.animated)\n  const mouseX = useMotionValue(Infinity)\n\n  return (\n    <DockContext.Provider\n      value={{ mouseX, magnification, distance, reduced: motion.disabled }}\n    >\n      <m.nav\n        onMouseMove={(e: ReactMouseEvent) => mouseX.set(e.clientX)}\n        onMouseLeave={() => mouseX.set(Infinity)}\n        data-slot=\"dock\"\n        data-surface={sf.surface}\n        data-radius={sf.radius}\n        data-animated={String(sf.animated)}\n        className={cn(\n          \"mx-auto flex h-14 items-end gap-2 rounded-2xl border surface-card px-3 pb-2\",\n          className,\n        )}\n        role=\"toolbar\"\n        aria-label=\"Dock\"\n      >\n        {children}\n      </m.nav>\n    </DockContext.Provider>\n  )\n}\n\n/* ── DockItem ── */\n\n/**\n * Props for a DockItem. Intentionally does NOT carry the Saasflare axes —\n * a DockItem's motion follows the parent {@link Dock} via context, and it has\n * no surface/radius/iconWeight of its own.\n */\nexport interface DockItemProps {\n  /** Icon or content inside the dock item. */\n  children: ReactNode\n  /** Tooltip label for the item. */\n  label: string\n  /** Click handler. */\n  onClick?: () => void\n  /** Additional class names. */\n  className?: string\n}\n\n/** Base icon size in pixels. */\nconst BASE_SIZE = 40\n\n/**\n * Individual item within a Dock.\n *\n * - Magnifies based on mouse proximity using spring interpolation\n * - Shows a tooltip label on hover\n * - Renders at static base size when reduced motion is preferred\n *\n * @component\n * @package ui\n */\nexport function DockItem({\n  children,\n  label,\n  onClick,\n  className,\n}: DockItemProps) {\n  const { mouseX, magnification, distance, reduced } = useDock()\n  const ref = useRef<HTMLButtonElement>(null)\n\n  /* Reading gBCR for every item on every mouseX update — while the width\n   * spring is writing layout properties each frame — is the classic\n   * read/write layout-thrash interleave. Cache the item's rest center and\n   * invalidate on scroll/resize; the drift while neighbors magnify is\n   * bounded and visually negligible at dock distances. */\n  const centerRef = useRef<number | null>(null)\n\n  useEffect(() => {\n    const invalidate = () => {\n      centerRef.current = null\n    }\n    window.addEventListener(\"resize\", invalidate, { passive: true })\n    window.addEventListener(\"scroll\", invalidate, { passive: true, capture: true })\n    return () => {\n      window.removeEventListener(\"resize\", invalidate)\n      window.removeEventListener(\"scroll\", invalidate, { capture: true })\n    }\n  }, [])\n\n  const distanceFromMouse = useTransform(mouseX, (val: number) => {\n    if (centerRef.current === null) {\n      const bounds = ref.current?.getBoundingClientRect()\n      if (!bounds) return distance + 1\n      centerRef.current = bounds.x + bounds.width / 2\n    }\n    return val - centerRef.current\n  })\n\n  const maxSize = BASE_SIZE * magnification\n\n  const sizeTransform = useTransform(\n    distanceFromMouse,\n    [-distance, 0, distance],\n    [BASE_SIZE, maxSize, BASE_SIZE],\n  )\n\n  const size = useSpring(sizeTransform, { stiffness: 300, damping: 25 })\n\n  return (\n    <m.button\n      ref={ref}\n      onClick={onClick}\n      style={reduced ? { width: BASE_SIZE, height: BASE_SIZE } : { width: size, height: size }}\n      className={cn(\n        \"relative flex items-center justify-center rounded-xl bg-muted transition-colors hover:bg-muted/80\",\n        \"group\",\n        className,\n      )}\n      aria-label={label}\n      data-slot=\"dock-item\"\n    >\n      {children}\n\n      {/* Tooltip */}\n      <span\n        className=\"pointer-events-none absolute -top-9 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-md bg-popover px-2 py-1 text-xs text-popover-foreground shadow-md opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100\"\n        role=\"tooltip\"\n      >\n        {label}\n      </span>\n    </m.button>\n  )\n}\n",
      "target": "components/ui/dock.tsx"
    }
  ],
  "$meta": {
    "source": "https://ui.saasflare.io/r/dock.json"
  }
}
