{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "glass-dock", "type": "registry:ui", "dependencies": [ "framer-motion", "lucide-react" ], "files": [ { "path": "components/ui/glass-dock.tsx", "content": "'use client';\r\n\r\nimport React, { useRef, useState, useEffect } from 'react';\r\nimport { LucideIcon } from 'lucide-react';\r\nimport { motion, AnimatePresence } from 'framer-motion';\r\nimport { cn } from '@/lib/utils';\r\nimport gsap from \"gsap\";\r\n\r\nexport interface DockItem {\r\n title: string;\r\n icon: LucideIcon;\r\n onClick?: () => void;\r\n href?: string;\r\n}\r\n\r\nexport interface GlassDockProps extends React.HTMLAttributes {\r\n items: DockItem[];\r\n dockClassName?: string;\r\n}\r\n\r\n// Attempt to register MorphSVGPlugin if available. \r\nif (typeof window !== \"undefined\") {\r\n try {\r\n // @ts-ignore\r\n import(\"gsap/MorphSVGPlugin\").then((plugin) => {\r\n gsap.registerPlugin(plugin.MorphSVGPlugin);\r\n }).catch(e => {\r\n console.warn(\"GSAP MorphSVGPlugin not found. Morphing animations will be disabled.\", e);\r\n });\r\n } catch (e) {\r\n console.warn(\"GSAP MorphSVGPlugin not found.\", e);\r\n }\r\n}\r\n\r\n// Helper component for Morphing Icons\r\nconst MorphingIcon = ({ type, isActive, onClick, onMouseEnter }: { type: string, isActive: boolean, onClick: () => void, onMouseEnter?: () => void }) => {\r\n const buttonRef = useRef(null);\r\n const pathRef = useRef(null);\r\n\r\n // Animation functions integrated directly\r\n const animateHome = () => {\r\n if (!buttonRef.current || !pathRef.current) return;\r\n const button = buttonRef.current;\r\n const path = pathRef.current;\r\n\r\n gsap.to(button, {\r\n \"--tab-bar-home-scale\": 0.25,\r\n \"--tab-bar-home-opacity\": 0,\r\n duration: 0.1,\r\n onComplete: () => {\r\n gsap.to(path, {\r\n keyframes: [\r\n { morphSVG: \"M12.6387 3.53796L15.1949 7.69178C15.7004 8.51322 15.7802 9.5276 15.4092 10.4179L11.3846 20.0769C11.2016 20.516 11.5243 21 12 21V21C12.4757 21 12.7984 20.516 12.6154 20.0769L8.5908 10.4179C8.21983 9.5276 8.29956 8.51322 8.80506 7.69178L11.3613 3.53796C11.6541 3.06206 12.3459 3.06206 12.6387 3.53796Z\", duration: 0.1 },\r\n { morphSVG: \"M12.1483 3.46366L12.8548 8.05624C12.9493 8.67024 12.8508 9.29842 12.573 9.85405L8.08541 18.8292C7.58673 19.8265 8.31198 21 9.42705 21H14.5729C15.688 21 16.4133 19.8265 15.9146 18.8292L11.427 9.85405C11.1492 9.29842 11.0507 8.67024 11.1452 8.05624L11.8517 3.46366C11.8778 3.29407 12.1222 3.29407 12.1483 3.46366Z\", duration: 0.09 },\r\n {\r\n morphSVG: \"M21 18V10.5339C21 9.57062 20.5374 8.66591 19.7565 8.1019L13.7565 3.76856C12.7079 3.01128 11.2921 3.01128 10.2435 3.76856L4.24353 8.1019C3.46259 8.66591 3 9.57062 3 10.5339V18C3 19.6569 4.34315 21 6 21H18C19.6569 21 21 19.6569 21 18Z\",\r\n duration: 0.71, ease: \"elastic.out(1, .9)\",\r\n onStart: () => {\r\n gsap.to(button, { \"--tab-bar-home-scale\": 0.7, duration: 0.71, ease: \"elastic.out(1, .9)\" });\r\n gsap.to(button, { \"--tab-bar-home-opacity\": 1, duration: 0.2 });\r\n },\r\n },\r\n ],\r\n });\r\n },\r\n });\r\n };\r\n\r\n // ... (Simulate other animations similarly or simplify for brevity in this single file component)\r\n // For brevity, mapping only the structure. Full GSAP keyframes should be here as in previous file.\r\n // I will include all animations to ensure full functionality.\r\n\r\n const animateBlog = () => {\r\n if (!buttonRef.current || !pathRef.current) return;\r\n const button = buttonRef.current;\r\n const path = pathRef.current;\r\n\r\n // Morphing animation for Blog\r\n gsap.to(button, {\r\n \"--tab-bar-blog-scale\": 0.25,\r\n \"--tab-bar-blog-opacity\": 0,\r\n duration: 0.1,\r\n onComplete: () => {\r\n gsap.to(path, {\r\n keyframes: [\r\n { morphSVG: \"M12 21C12 21 15.3954 18.8605 13.3637 16C12.0647 14.1711 9.51275 11.9823 9 10C8 6.134 10.134 3 12 3C13.866 3 16 6.134 15 10C14.4873 11.9823 11.9353 14.1711 10.6363 16C8.60464 18.8605 12 21 12 21Z\", duration: 0.1 },\r\n { morphSVG: \"M12 21C12 21 14.0216 19.0215 14.3637 16C14.6026 13.8898 13.5128 11.9823 13 10C12 6.134 13.134 3 12 3C10.866 3 12 6.134 11 10C10.4873 11.9823 9.39736 13.8898 9.6363 16C9.97843 19.0215 12 21 12 21Z\", duration: 0.05 },\r\n {\r\n morphSVG: \"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z\",\r\n duration: 0.7,\r\n ease: \"elastic.out(1, .9)\",\r\n onStart: () => {\r\n gsap.to(button, { \"--tab-bar-blog-scale\": 0.7, duration: 0.7, ease: \"elastic.out(1, .9)\" });\r\n gsap.to(button, { \"--tab-bar-blog-opacity\": 1, duration: 0.2 });\r\n }\r\n }\r\n ]\r\n });\r\n }\r\n });\r\n };\r\n\r\n const animateMarker = () => {\r\n if (!buttonRef.current || !pathRef.current) return;\r\n const button = buttonRef.current;\r\n const path = pathRef.current;\r\n gsap.to(button, {\r\n \"--tab-bar-marker-scale\": 0.25, \"--tab-bar-marker-opacity\": 0, duration: 0.1, onComplete: () => {\r\n gsap.to(path, {\r\n keyframes: [\r\n { morphSVG: \"M12 21C12 21 15.3954 18.8605 13.3637 16C12.0647 14.1711 9.51275 11.9823 9 10C8 6.134 10.134 3 12 3C13.866 3 16 6.134 15 10C14.4873 11.9823 11.9353 14.1711 10.6363 16C8.60464 18.8605 12 21 12 21Z\", duration: 0.1 },\r\n { morphSVG: \"M12 21C12 21 14.0216 19.0215 14.3637 16C14.6026 13.8898 13.5128 11.9823 13 10C12 6.134 13.134 3 12 3C10.866 3 12 6.134 11 10C10.4873 11.9823 9.39736 13.8898 9.6363 16C9.97843 19.0215 12 21 12 21Z\", duration: 0.05 },\r\n {\r\n morphSVG: \"M12 21C12 21 14.6062 18.8589 16.64 16C17.941 14.1711 19 12.0475 19 10C19 6.134 15.87 3 12 3C8.13 3 5 6.134 5 10C5 12.0475 6.05896 14.1711 7.36 16C9.39381 18.8589 12 21 12 21Z\",\r\n duration: 0.75, ease: \"elastic.out(1, .9)\",\r\n onStart: () => {\r\n gsap.to(button, { \"--tab-bar-marker-scale\": 0.7, duration: 0.75, ease: \"elastic.out(1, .9)\" });\r\n gsap.to(button, { \"--tab-bar-marker-opacity\": 1, duration: 0.2 });\r\n }\r\n }\r\n ]\r\n });\r\n }\r\n });\r\n };\r\n\r\n const animateEmail = () => {\r\n if (!buttonRef.current || !pathRef.current) return;\r\n const button = buttonRef.current;\r\n const path = pathRef.current;\r\n gsap.to(button, {\r\n \"--tab-bar-email-scale\": 0.25, \"--tab-bar-email-opacity\": 0, duration: 0.1, onComplete: () => {\r\n gsap.to(path, {\r\n keyframes: [\r\n { morphSVG: \"M12 21C12 21 15.3954 18.8605 13.3637 16C12.0647 14.1711 9.51275 11.9823 9 10C8 6.134 10.134 3 12 3C13.866 3 16 6.134 15 10C14.4873 11.9823 11.9353 14.1711 10.6363 16C8.60464 18.8605 12 21 12 21Z\", duration: 0.1 },\r\n { morphSVG: \"M12 21C12 21 14.0216 19.0215 14.3637 16C14.6026 13.8898 13.5128 11.9823 13 10C12 6.134 13.134 3 12 3C10.866 3 12 6.134 11 10C10.4873 11.9823 9.39736 13.8898 9.6363 16C9.97843 19.0215 12 21 12 21Z\", duration: 0.05 },\r\n {\r\n morphSVG: \"M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4-8 5-8-5V6l8 5 8-5v2z\",\r\n duration: 0.7, ease: \"elastic.out(1, .9)\",\r\n onStart: () => {\r\n gsap.to(button, { \"--tab-bar-email-scale\": 0.7, duration: 0.7, ease: \"elastic.out(1, .9)\" });\r\n gsap.to(button, { \"--tab-bar-email-opacity\": 1, duration: 0.2 });\r\n }\r\n }\r\n ]\r\n });\r\n }\r\n });\r\n };\r\n\r\n const animateLinkedIn = () => {\r\n if (!buttonRef.current || !pathRef.current) return;\r\n const button = buttonRef.current;\r\n const path = pathRef.current;\r\n gsap.to(button, {\r\n \"--tab-bar-linkedin-scale\": 0.25, \"--tab-bar-linkedin-opacity\": 0, duration: 0.1, onComplete: () => {\r\n gsap.to(path, {\r\n keyframes: [\r\n { morphSVG: \"M12 21C12 21 15.3954 18.8605 13.3637 16C12.0647 14.1711 9.51275 11.9823 9 10C8 6.134 10.134 3 12 3C13.866 3 16 6.134 15 10C14.4873 11.9823 11.9353 14.1711 10.6363 16C8.60464 18.8605 12 21 12 21Z\", duration: 0.125 },\r\n { morphSVG: \"M13.364 5.63604C14.0062 9.12971 7.68417 13.4401 8.36401 18.3639C8.84929 21.8787 15.1508 21.8787 15.6361 18.3639C16.316 13.4401 9.99389 9.12969 10.6361 5.63604C11.3564 1.71793 12.6438 1.71795 13.364 5.63604Z\", duration: 0.05 },\r\n {\r\n morphSVG: \"M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z\",\r\n duration: 0.8, ease: \"elastic.out(1, .9)\",\r\n onStart: () => {\r\n gsap.to(button, { \"--tab-bar-linkedin-scale\": 0.7, duration: 0.8, ease: \"elastic.out(1, .9)\" });\r\n gsap.to(button, { \"--tab-bar-linkedin-opacity\": 1, duration: 0.2 });\r\n }\r\n }\r\n ]\r\n });\r\n }\r\n });\r\n };\r\n\r\n const animateX = () => {\r\n if (!buttonRef.current || !pathRef.current) return;\r\n const button = buttonRef.current;\r\n const path = pathRef.current;\r\n\r\n // Morphing animation for X\r\n gsap.to(button, {\r\n \"--tab-bar-x-scale\": 0.25,\r\n \"--tab-bar-x-opacity\": 0,\r\n duration: 0.1,\r\n onComplete: () => {\r\n gsap.to(path, {\r\n keyframes: [\r\n {\r\n morphSVG: \"M12 21C12 21 15.3954 18.8605 13.3637 16C12.0647 14.1711 9.51275 11.9823 9 10C8 6.134 10.134 3 12 3C13.866 3 16 6.134 15 10C14.4873 11.9823 11.9353 14.1711 10.6363 16C8.60464 18.8605 12 21 12 21Z\",\r\n duration: 0.1\r\n },\r\n {\r\n morphSVG: \"M12 21C12 21 14.0216 19.0215 14.3637 16C14.6026 13.8898 13.5128 11.9823 13 10C12 6.134 13.134 3 12 3C10.866 3 12 6.134 11 10C10.4873 11.9823 9.39736 13.8898 9.6363 16C9.97843 19.0215 12 21 12 21Z\",\r\n duration: 0.05\r\n },\r\n {\r\n // Return to X shape\r\n morphSVG: \"M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z\",\r\n duration: 0.75,\r\n ease: \"elastic.out(1, .9)\",\r\n onStart: () => {\r\n gsap.to(button, {\r\n \"--tab-bar-x-scale\": 0.7,\r\n duration: 0.75,\r\n ease: \"elastic.out(1, .9)\"\r\n });\r\n gsap.to(button, {\r\n \"--tab-bar-x-opacity\": 1,\r\n duration: 0.2\r\n });\r\n }\r\n }\r\n ]\r\n });\r\n }\r\n });\r\n };\r\n\r\n const animateGithub = () => {\r\n if (!buttonRef.current || !pathRef.current) return;\r\n const button = buttonRef.current;\r\n const path = pathRef.current;\r\n\r\n // Morphing animation for Github\r\n gsap.to(button, {\r\n \"--tab-bar-github-scale\": 0.25,\r\n \"--tab-bar-github-opacity\": 0,\r\n duration: 0.1,\r\n onComplete: () => {\r\n gsap.to(path, {\r\n keyframes: [\r\n {\r\n morphSVG: \"M12 21C12 21 15.3954 18.8605 13.3637 16C12.0647 14.1711 9.51275 11.9823 9 10C8 6.134 10.134 3 12 3C13.866 3 16 6.134 15 10C14.4873 11.9823 11.9353 14.1711 10.6363 16C8.60464 18.8605 12 21 12 21Z\",\r\n duration: 0.1,\r\n },\r\n {\r\n morphSVG: \"M12 21C12 21 14.0216 19.0215 14.3637 16C14.6026 13.8898 13.5128 11.9823 13 10C12 6.134 13.134 3 12 3C10.866 3 12 6.134 11 10C10.4873 11.9823 9.39736 13.8898 9.6363 16C9.97843 19.0215 12 21 12 21Z\",\r\n duration: 0.05,\r\n },\r\n {\r\n // Return to Github shape\r\n morphSVG: \"M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z\",\r\n duration: 0.75,\r\n ease: \"elastic.out(1, .9)\",\r\n onStart: () => {\r\n gsap.to(button, {\r\n \"--tab-bar-github-scale\": 0.7,\r\n duration: 0.75,\r\n ease: \"elastic.out(1, .9)\",\r\n });\r\n gsap.to(button, {\r\n \"--tab-bar-github-opacity\": 1,\r\n duration: 0.2,\r\n });\r\n },\r\n },\r\n ],\r\n });\r\n },\r\n });\r\n };\r\n\r\n const handleMouseEnter = () => {\r\n onMouseEnter && onMouseEnter();\r\n if (type === 'home') animateHome();\r\n else if (type === 'blog') animateBlog();\r\n else if (type === 'marker') animateMarker();\r\n else if (type === 'email') animateEmail();\r\n else if (type === 'linkedin') animateLinkedIn();\r\n else if (type === 'x') animateX();\r\n else if (type === 'github') animateGithub();\r\n };\r\n\r\n if (type === 'home') {\r\n return (\r\n \r\n );\r\n }\r\n if (type === 'blog') {\r\n return (\r\n \r\n );\r\n }\r\n if (type === 'marker') {\r\n return (\r\n \r\n );\r\n }\r\n if (type === 'email') {\r\n return (\r\n \r\n );\r\n }\r\n if (type === 'linkedin') {\r\n return (\r\n \r\n );\r\n }\r\n if (type === 'x') {\r\n return (\r\n \r\n );\r\n }\r\n if (type === 'github') {\r\n return (\r\n \r\n );\r\n }\r\n return null;\r\n};\r\n\r\nexport const GlassDock = React.forwardRef(\r\n (\r\n {\r\n items,\r\n className,\r\n dockClassName,\r\n ...props\r\n },\r\n ref\r\n ) => {\r\n const [hoveredIndex, setHoveredIndex] = useState(null);\r\n const [direction, setDirection] = useState(0);\r\n // Default to not darker for dock itself, but buttons handle colors\r\n // We removed isDark check as styles in globals.css handle it via .dark selector\r\n\r\n const handleMouseEnter = (index: number) => {\r\n if (hoveredIndex !== null && index !== hoveredIndex) {\r\n setDirection(index > hoveredIndex ? 1 : -1);\r\n }\r\n setHoveredIndex(index);\r\n };\r\n\r\n const getTooltipPosition = (index: number) => index * 52 + 12;\r\n\r\n return (\r\n \r\n {\r\n setHoveredIndex(null);\r\n setDirection(0);\r\n }}\r\n >\r\n \r\n {hoveredIndex !== null && (\r\n \r\n \r\n
\r\n \r\n 0 ? 35 : -35,\r\n opacity: 0,\r\n filter: 'blur(6px)',\r\n }}\r\n animate={{\r\n x: 0,\r\n opacity: 1,\r\n filter: 'blur(0px)',\r\n }}\r\n exit={{\r\n x: direction > 0 ? -35 : 35,\r\n opacity: 0,\r\n filter: 'blur(6px)',\r\n }}\r\n transition={{\r\n duration: 0.3,\r\n ease: 'easeOut',\r\n }}\r\n className=\"text-[13px] font-medium tracking-wide whitespace-nowrap\"\r\n >\r\n {items[hoveredIndex].title}\r\n \r\n \r\n
\r\n \r\n \r\n )}\r\n
\r\n\r\n {items.map((el, index) => {\r\n const Icon = el.icon;\r\n const isHovered = hoveredIndex === index;\r\n const isActive = isHovered; // Simplified for this demo\r\n\r\n const handleClick = () => {\r\n if (el.onClick) {\r\n el.onClick();\r\n } else if (el.href) {\r\n window.location.href = el.href;\r\n }\r\n };\r\n\r\n // Check if title matches one of our animated icons\r\n const type = el.title.toLowerCase();\r\n const isAnimated = ['home', 'blog', 'marker', 'email', 'linkedin', 'x', 'github'].includes(type);\r\n\r\n return (\r\n handleMouseEnter(index)}\r\n onClick={handleClick}\r\n className=\"relative w-10 h-10 flex items-center justify-center cursor-pointer\"\r\n role=\"button\"\r\n tabIndex={0}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter' || e.key === ' ') {\r\n handleClick();\r\n }\r\n }}\r\n >\r\n \r\n {isAnimated ? (\r\n { }}\r\n />\r\n ) : (\r\n \r\n )}\r\n \r\n \r\n );\r\n })}\r\n \r\n \r\n );\r\n }\r\n);\r\n\r\nGlassDock.displayName = 'GlassDock';\r\nexport default GlassDock;", "type": "registry:ui", "target": "components/ui/glass-dock.tsx" } ] }