{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "CardNav-JS-CSS", "type": "registry:block", "title": "CardNav", "description": "Expandable navigation bar with card panels revealing nested links.", "dependencies": [ "gsap" ], "files": [ { "path": "public/default/src/content/Components/CardNav/CardNav.jsx", "content": "import { useLayoutEffect, useRef, useState } from 'react';\nimport { gsap } from 'gsap';\n// use your own icon import if react-icons is not available\nimport { GoArrowUpRight } from 'react-icons/go';\nimport './CardNav.css';\n\nconst CardNav = ({\n logo,\n logoAlt = 'Logo',\n items,\n className = '',\n ease = 'power3.out',\n baseColor = '#fff',\n menuColor,\n buttonBgColor,\n buttonTextColor\n}) => {\n const [isHamburgerOpen, setIsHamburgerOpen] = useState(false);\n const [isExpanded, setIsExpanded] = useState(false);\n const navRef = useRef(null);\n const cardsRef = useRef([]);\n const tlRef = useRef(null);\n\n const calculateHeight = () => {\n const navEl = navRef.current;\n if (!navEl) return 260;\n\n const isMobile = window.matchMedia('(max-width: 768px)').matches;\n if (isMobile) {\n const contentEl = navEl.querySelector('.card-nav-content');\n if (contentEl) {\n const wasVisible = contentEl.style.visibility;\n const wasPointerEvents = contentEl.style.pointerEvents;\n const wasPosition = contentEl.style.position;\n const wasHeight = contentEl.style.height;\n\n contentEl.style.visibility = 'visible';\n contentEl.style.pointerEvents = 'auto';\n contentEl.style.position = 'static';\n contentEl.style.height = 'auto';\n\n contentEl.offsetHeight;\n\n const topBar = 60;\n const padding = 16;\n const contentHeight = contentEl.scrollHeight;\n\n contentEl.style.visibility = wasVisible;\n contentEl.style.pointerEvents = wasPointerEvents;\n contentEl.style.position = wasPosition;\n contentEl.style.height = wasHeight;\n\n return topBar + contentHeight + padding;\n }\n }\n return 260;\n };\n\n const createTimeline = () => {\n const navEl = navRef.current;\n if (!navEl) return null;\n\n gsap.set(navEl, { height: 60, overflow: 'hidden' });\n gsap.set(cardsRef.current, { y: 50, opacity: 0 });\n\n const tl = gsap.timeline({ paused: true });\n\n tl.to(navEl, {\n height: calculateHeight,\n duration: 0.4,\n ease\n });\n\n tl.to(cardsRef.current, { y: 0, opacity: 1, duration: 0.4, ease, stagger: 0.08 }, '-=0.1');\n\n return tl;\n };\n\n useLayoutEffect(() => {\n const tl = createTimeline();\n tlRef.current = tl;\n\n return () => {\n tl?.kill();\n tlRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ease, items]);\n\n useLayoutEffect(() => {\n const handleResize = () => {\n if (!tlRef.current) return;\n\n if (isExpanded) {\n const newHeight = calculateHeight();\n gsap.set(navRef.current, { height: newHeight });\n\n tlRef.current.kill();\n const newTl = createTimeline();\n if (newTl) {\n newTl.progress(1);\n tlRef.current = newTl;\n }\n } else {\n tlRef.current.kill();\n const newTl = createTimeline();\n if (newTl) {\n tlRef.current = newTl;\n }\n }\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isExpanded]);\n\n const toggleMenu = () => {\n const tl = tlRef.current;\n if (!tl) return;\n if (!isExpanded) {\n setIsHamburgerOpen(true);\n setIsExpanded(true);\n tl.play(0);\n } else {\n setIsHamburgerOpen(false);\n tl.eventCallback('onReverseComplete', () => setIsExpanded(false));\n tl.reverse();\n }\n };\n\n const setCardRef = i => el => {\n if (el) cardsRef.current[i] = el;\n };\n\n return (\n