{
  "name": "apple-style-dock",
  "type": "registry:ui",
  "componentName": "dock",
  "description": "macOS-style dock with scaling and movement effects.",
  "files": [
    {
      "path": "apple-style-dock.tsx",
      "content": "import {\n  Activity,\n  Component,\n  HomeIcon,\n  Mail,\n  Package,\n  ScrollText,\n  SunMoon,\n} from 'lucide-react';\n\nimport { Dock, DockIcon, DockItem, DockLabel } from '@/components/core/dock';\n\nconst data = [\n  {\n    title: 'Home',\n    icon: (\n      <HomeIcon className='h-full w-full text-neutral-600 dark:text-neutral-300' />\n    ),\n    href: '#',\n  },\n  {\n    title: 'Products',\n    icon: (\n      <Package className='h-full w-full text-neutral-600 dark:text-neutral-300' />\n    ),\n    href: '#',\n  },\n  {\n    title: 'Components',\n    icon: (\n      <Component className='h-full w-full text-neutral-600 dark:text-neutral-300' />\n    ),\n    href: '#',\n  },\n  {\n    title: 'Activity',\n    icon: (\n      <Activity className='h-full w-full text-neutral-600 dark:text-neutral-300' />\n    ),\n    href: '#',\n  },\n  {\n    title: 'Change Log',\n    icon: (\n      <ScrollText className='h-full w-full text-neutral-600 dark:text-neutral-300' />\n    ),\n    href: '#',\n  },\n  {\n    title: 'Email',\n    icon: (\n      <Mail className='h-full w-full text-neutral-600 dark:text-neutral-300' />\n    ),\n    href: '#',\n  },\n  {\n    title: 'Theme',\n    icon: (\n      <SunMoon className='h-full w-full text-neutral-600 dark:text-neutral-300' />\n    ),\n    href: '#',\n  },\n];\n\nexport function AppleStyleDock() {\n  return (\n    <div className='absolute bottom-2 left-1/2 max-w-full -translate-x-1/2'>\n      <Dock className='items-end pb-3'>\n        {data.map((item, idx) => (\n          <DockItem\n            key={idx}\n            className='aspect-square rounded-full bg-gray-200 dark:bg-neutral-800'\n          >\n            <DockLabel>{item.title}</DockLabel>\n            <DockIcon>{item.icon}</DockIcon>\n          </DockItem>\n        ))}\n      </Dock>\n    </div>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "components/core/dock.tsx",
      "content": "'use client';\n\nimport {\n  motion,\n  MotionValue,\n  useMotionValue,\n  useSpring,\n  useTransform,\n  type SpringOptions,\n  AnimatePresence,\n} from 'motion/react';\nimport {\n  Children,\n  cloneElement,\n  createContext,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { cn } from '@/lib/utils';\n\nconst DOCK_HEIGHT = 128;\nconst DEFAULT_MAGNIFICATION = 80;\nconst DEFAULT_DISTANCE = 150;\nconst DEFAULT_PANEL_HEIGHT = 64;\n\nexport type DockProps = {\n  children: React.ReactNode;\n  className?: string;\n  distance?: number;\n  panelHeight?: number;\n  magnification?: number;\n  spring?: SpringOptions;\n};\n\nexport type DockItemProps = {\n  className?: string;\n  children: React.ReactNode;\n  onClick?: () => void;\n};\n\nexport type DockLabelProps = {\n  className?: string;\n  children: React.ReactNode;\n};\n\nexport type DockIconProps = {\n  className?: string;\n  children: React.ReactNode;\n};\n\nexport type DocContextType = {\n  mouseX: MotionValue;\n  spring: SpringOptions;\n  magnification: number;\n  distance: number;\n};\n\nexport type DockProviderProps = {\n  children: React.ReactNode;\n  value: DocContextType;\n};\n\nconst DockContext = createContext<DocContextType | undefined>(undefined);\n\nfunction DockProvider({ children, value }: DockProviderProps) {\n  return <DockContext.Provider value={value}>{children}</DockContext.Provider>;\n}\n\nfunction useDock() {\n  const context = useContext(DockContext);\n  if (!context) {\n    throw new Error('useDock must be used within an DockProvider');\n  }\n  return context;\n}\n\nfunction Dock({\n  children,\n  className,\n  spring = { mass: 0.1, stiffness: 150, damping: 12 },\n  magnification = DEFAULT_MAGNIFICATION,\n  distance = DEFAULT_DISTANCE,\n  panelHeight = DEFAULT_PANEL_HEIGHT,\n}: DockProps) {\n  const mouseX = useMotionValue(Infinity);\n  const isHovered = useMotionValue(0);\n\n  const maxHeight = useMemo(() => {\n    return Math.max(DOCK_HEIGHT, magnification + magnification / 2 + 4);\n  }, [magnification]);\n\n  const heightRow = useTransform(isHovered, [0, 1], [panelHeight, maxHeight]);\n  const height = useSpring(heightRow, spring);\n\n  return (\n    <motion.div\n      style={{\n        height: height,\n        scrollbarWidth: 'none',\n      }}\n      className='mx-2 flex max-w-full items-end overflow-x-auto'\n    >\n      <motion.div\n        onMouseMove={({ pageX }) => {\n          isHovered.set(1);\n          mouseX.set(pageX);\n        }}\n        onMouseLeave={() => {\n          isHovered.set(0);\n          mouseX.set(Infinity);\n        }}\n        className={cn(\n          'mx-auto flex w-fit gap-4 rounded-2xl bg-gray-50 px-4 dark:bg-neutral-900',\n          className\n        )}\n        style={{ height: panelHeight }}\n        role='toolbar'\n        aria-label='Application dock'\n      >\n        <DockProvider value={{ mouseX, spring, distance, magnification }}>\n          {children}\n        </DockProvider>\n      </motion.div>\n    </motion.div>\n  );\n}\n\nfunction DockItem({ children, className, onClick }: DockItemProps) {\n  const ref = useRef<HTMLDivElement>(null);\n\n  const { distance, magnification, mouseX, spring } = useDock();\n\n  const isHovered = useMotionValue(0);\n\n  const mouseDistance = useTransform(mouseX, (val) => {\n    const domRect = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 };\n    return val - domRect.x - domRect.width / 2;\n  });\n\n  const widthTransform = useTransform(\n    mouseDistance,\n    [-distance, 0, distance],\n    [40, magnification, 40]\n  );\n\n  const width = useSpring(widthTransform, spring);\n\n  return (\n    <motion.div\n      ref={ref}\n      style={{ width }}\n      onHoverStart={() => isHovered.set(1)}\n      onHoverEnd={() => isHovered.set(0)}\n      onFocus={() => isHovered.set(1)}\n      onBlur={() => isHovered.set(0)}\n      className={cn(\n        'relative inline-flex items-center justify-center',\n        className\n      )}\n      tabIndex={0}\n      role='button'\n      aria-haspopup='true'\n      onClick={onClick}\n    >\n      {Children.map(children, (child) =>\n        cloneElement(\n          child as React.ReactElement<{\n            width?: MotionValue<number>;\n            isHovered?: MotionValue<number>;\n          }>,\n          { width, isHovered }\n        )\n      )}\n    </motion.div>\n  );\n}\n\nfunction DockLabel({ children, className, ...rest }: DockLabelProps) {\n  const restProps = rest as Record<string, unknown>;\n  const isHovered = restProps['isHovered'] as MotionValue<number>;\n  const [isVisible, setIsVisible] = useState(false);\n\n  useEffect(() => {\n    const unsubscribe = isHovered.on('change', (latest) => {\n      setIsVisible(latest === 1);\n    });\n\n    return () => unsubscribe();\n  }, [isHovered]);\n\n  return (\n    <AnimatePresence>\n      {isVisible && (\n        <motion.div\n          initial={{ opacity: 0, y: 0 }}\n          animate={{ opacity: 1, y: -10 }}\n          exit={{ opacity: 0, y: 0 }}\n          transition={{ duration: 0.2 }}\n          className={cn(\n            'absolute -top-6 left-1/2 w-fit whitespace-pre rounded-md border border-gray-200 bg-gray-100 px-2 py-0.5 text-xs text-neutral-700 dark:border-neutral-900 dark:bg-neutral-800 dark:text-white',\n            className\n          )}\n          role='tooltip'\n          style={{ x: '-50%' }}\n        >\n          {children}\n        </motion.div>\n      )}\n    </AnimatePresence>\n  );\n}\n\nfunction DockIcon({ children, className, ...rest }: DockIconProps) {\n  const restProps = rest as Record<string, unknown>;\n  const width = restProps['width'] as MotionValue<number>;\n\n  const widthTransform = useTransform(width, (val) => val / 2);\n\n  return (\n    <motion.div\n      style={{ width: widthTransform }}\n      className={cn('flex items-center justify-center', className)}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nexport { Dock, DockIcon, DockItem, DockLabel };\n",
      "type": "registry:ui"
    }
  ]
}