{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "dock", "title": "Dock", "description": "A dock component", "files": [ { "path": "registry/pioneerui/dock.tsx", "content": "\"use client\"\n\nimport {\n motion,\n type MotionValue,\n useMotionValue,\n useSpring,\n useTransform,\n type SpringOptions,\n AnimatePresence,\n} from \"framer-motion\"\nimport {\n Children,\n cloneElement,\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n type ReactElement,\n} from \"react\"\nimport { cn } from \"@/lib/utils\"\n\n// Constants\nconst DOCK_CONFIG = {\n HEIGHT: 128,\n DEFAULT_MAGNIFICATION: 80,\n DEFAULT_DISTANCE: 150,\n DEFAULT_PANEL_HEIGHT: 64,\n DEFAULT_SPRING: {\n mass: 0.1,\n stiffness: 150,\n damping: 12,\n },\n} as const\n\n// Types\ninterface DockContextType {\n mouseX: MotionValue\n spring: SpringOptions\n magnification: number\n distance: number\n isHovered: MotionValue\n}\n\ninterface DockProps {\n children: ReactNode\n className?: string\n distance?: number\n panelHeight?: number\n magnification?: number\n spring?: SpringOptions\n}\n\ninterface DockItemProps {\n className?: string\n children: ReactNode\n onClick?: () => void\n href?: string\n target?: string\n rel?: string\n}\n\ninterface DockLabelProps {\n className?: string\n children: ReactNode\n}\n\ninterface DockIconProps {\n className?: string\n children: ReactNode\n}\n\n// Context\nconst DockContext = createContext(undefined)\n\nfunction useDock() {\n const context = useContext(DockContext)\n if (!context) {\n throw new Error(\"useDock must be used within a DockProvider\")\n }\n return context\n}\n\n// Components\nexport function Dock({\n children,\n className,\n spring = DOCK_CONFIG.DEFAULT_SPRING,\n magnification = DOCK_CONFIG.DEFAULT_MAGNIFICATION,\n distance = DOCK_CONFIG.DEFAULT_DISTANCE,\n panelHeight = DOCK_CONFIG.DEFAULT_PANEL_HEIGHT,\n}: DockProps) {\n const mouseX = useMotionValue(Number.POSITIVE_INFINITY)\n const isHovered = useMotionValue(0)\n\n const maxHeight = useMemo(() => {\n return Math.max(DOCK_CONFIG.HEIGHT, magnification + magnification / 2 + 4)\n }, [magnification])\n\n const heightTransform = useTransform(isHovered, [0, 1], [panelHeight, maxHeight])\n const height = useSpring(heightTransform, spring)\n\n return (\n \n {\n isHovered.set(1)\n mouseX.set(event.pageX)\n }}\n onMouseLeave={() => {\n isHovered.set(0)\n mouseX.set(Number.POSITIVE_INFINITY)\n }}\n className={cn(\n \"mx-auto flex w-fit gap-4 rounded-2xl bg-background/80 px-4 backdrop-blur-md dark:bg-neutral-900/80\",\n className,\n )}\n style={{ height: panelHeight }}\n role=\"toolbar\"\n aria-label=\"Application dock\"\n >\n {children}\n {children}\n \n \n )\n}\n\nexport function DockItem({ children, className, onClick, href, target, rel, ...props }: DockItemProps) {\n const ref = useRef(null)\n const { distance, magnification, mouseX, spring } = useDock()\n const isHovered = useMotionValue(0)\n\n const mouseDistance = useMotionValue(0)\n\n useEffect(() => {\n if (!ref.current) return\n\n const updateMouseDistance = () => {\n if (!ref.current) {\n mouseDistance.set(0)\n return\n }\n const rect = ref.current.getBoundingClientRect()\n mouseDistance.set(mouseX.get() - rect.x - rect.width / 2)\n }\n\n const unsubscribeMouseX = mouseX.on(\"change\", updateMouseDistance)\n\n updateMouseDistance()\n\n return () => {\n unsubscribeMouseX()\n }\n }, [mouseX])\n\n const widthTransform = useTransform(mouseDistance, [-distance, 0, distance], [40, magnification, 40])\n const width = useSpring(widthTransform, spring)\n\n const Component = href ? motion.a : motion.button\n\n return (\n 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 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-primary\",\n className,\n )}\n onClick={onClick}\n href={href}\n target={target}\n rel={href && target === \"_blank\" ? \"noopener noreferrer\" : rel}\n role={href ? undefined : \"button\"}\n tabIndex={0}\n {...props}\n >\n {Children.map(children, (child) => cloneElement(child as ReactElement, { width, isHovered }))}\n \n )\n}\n\nexport function DockLabel({ children, className }: DockLabelProps & { isHovered?: MotionValue }) {\n const [isVisible, setIsVisible] = useState(false)\n const { isHovered } = useContext(DockContext) || {}\n\n useEffect(() => {\n if (!isHovered) return\n const unsubscribe = isHovered.on(\"change\", (latest) => {\n setIsVisible(latest === 1)\n })\n return () => unsubscribe()\n }, [isHovered])\n\n return (\n \n {isVisible && (\n \n {children}\n \n )}\n \n )\n}\n\nexport function DockIcon({ children, className, width }: DockIconProps & { width?: MotionValue }) {\n const scale = width ? useTransform(width, (val) => val / 2) : undefined\n\n return (\n \n {children}\n \n )\n}\n\n", "type": "registry:ui", "target": "components/pioneerui/dock.tsx" } ], "type": "registry:ui" }