All PromptsAll Prompts
ui componentnav
Card Nav
Карточная навигация UI: выпадающее меню с карточками и плавными анимациями. Идеально для минималистичных сайтов.
by Zhou JasonLive Preview
Prompt
# Card Nav
You are given a task to integrate an existing React component in the codebase
~~~/README.md
# CardNavShowcase
A premium, expandable navigation component with card-based submenu reveals and GSAP-powered height transitions. Designed for a high-end, minimalist aesthetic.
## Dependencies
- `gsap`: ^3.12.5
- `lucide-react`: ^0.454.0
- `framer-motion`: ^11.11.11
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `logo` | `string` | Placeholder | URL for the brand logo |
| `logoAlt` | `string` | "Logo" | Alt text for the logo |
| `items` | `CardNavItem[]` | **Required** | Array of navigation cards with sublinks |
| `className` | `string` | `""` | Additional CSS classes for the container |
| `ease` | `string` | `"power3.out"` | GSAP easing function |
| `baseColor` | `string` | `hsl(var(--background))` | Background color of the nav bar |
| `menuColor` | `string` | `hsl(var(--foreground))` | Color of the menu icon |
| `buttonBgColor` | `string` | `hsl(var(--primary))` | Background color of the CTA button |
| `buttonTextColor` | `string` | `hsl(var(--primary-foreground))` | Text color of the CTA button |
| `buttonText` | `string` | `"Get Started"` | Text for the CTA button |
### CardNavItem
```typescript
{
label: string;
bgColor: string;
textColor: string;
links: {
label: string;
href: string;
ariaLabel: string;
}[];
}
```
## Usage Example
```tsx
import { CardNav } from '@/sd-components/c2abf9b1-3ee1-4943-af47-f000d9e9c4c1';
function MyApp() {
const navItems = [
{
label: "Product",
bgColor: "#000",
textColor: "#fff",
links: [{ label: "Features", href: "/features", ariaLabel: "View Features" }]
}
];
return (
<CardNav items={navItems} buttonText="Try Now" />
);
}
```
~~~
~~~/src/App.tsx
import React from 'react';
import CardNav, { CardNavItem } from './Component';
/**
* App - Demo of the CardNav component
* Following the Minimalist Showcase style:
* - Deep slate background
* - Ample whitespace
* - Title display
*/
export default function App() {
const items: CardNavItem[] = [
{
label: "About",
bgColor: "#0D0716",
textColor: "#FFFFFF",
links: [
{ label: "Our Story", href: "#", ariaLabel: "About Company" },
{ label: "Team", href: "#", ariaLabel: "Our Team" },
{ label: "Careers", href: "#", ariaLabel: "Join Us" }
]
},
{
label: "Services",
bgColor: "#170D27",
textColor: "#FFFFFF",
links: [
{ label: "Design", href: "#", ariaLabel: "Design Services" },
{ label: "Development", href: "#", ariaLabel: "Development Services" },
{ label: "Consulting", href: "#", ariaLabel: "Business Consulting" }
]
},
{
label: "Portfolio",
bgColor: "#271E37",
textColor: "#FFFFFF",
links: [
{ label: "Projects", href: "#", ariaLabel: "Recent Work" },
{ label: "Clients", href: "#", ariaLabel: "Our Clients" }
]
}
];
return (
<div className="min-h-screen bg-[#1A1A1B] flex flex-col items-center justify-start pt-32 px-10">
<div className="w-full max-w-[800px] mb-20 text-center">
<h1 className="text-[#F9F9F9] text-4xl font-semibold tracking-tight mb-4">
CardNav Showcase
</h1>
<p className="text-[#F9F9F9]/60 text-lg">
A premium expandable navigation with staggered card reveals.
</p>
</div>
<div className="w-full relative py-20 flex justify-center">
{/* The Component */}
<CardNav
items={items}
baseColor="#F9F9F9"
menuColor="#1A1A1B"
buttonBgColor="#1A1A1B"
buttonTextColor="#F9F9F9"
buttonText="Contact Us"
/>
{/* Placeholder Content to demonstrate overlay behavior */}
<div className="absolute top-full left-0 right-0 py-20 text-center pointer-events-none">
<p className="text-[#F9F9F9]/20 text-sm italic">
Component floats above page content when expanded
</p>
</div>
</div>
</div>
);
}
~~~
~~~/package.json
{
"name": "card-nav-showcase",
"description": "Premium expandable navigation with card reveals",
"dependencies": {
"gsap": "^3.12.5",
"lucide-react": "^0.454.0",
"framer-motion": "^11.11.11"
}
}
~~~
~~~/src/Component.tsx
/**
* CardNav - A premium, expandable navigation component.
* Features:
* - GSAP-powered height transitions
* - Card-based submenu reveals with staggered animations
* - Mobile-responsive design
* - Highly customizable colors and animations
*/
import React, { useLayoutEffect, useRef, useState } from 'react';
import { gsap } from 'gsap';
import { ArrowUpRight, Menu, X } from 'lucide-react';
export type CardNavLink = {
label: string;
href: string;
ariaLabel: string;
};
export type CardNavItem = {
label: string;
bgColor: string;
textColor: string;
links: CardNavLink[];
};
export interface CardNavProps {
logo?: string;
logoAlt?: string;
items: CardNavItem[];
className?: string;
ease?: string;
baseColor?: string;
menuColor?: string;
buttonBgColor?: string;
buttonTextColor?: string;
buttonText?: string;
}
export const CardNav: React.FC<CardNavProps> = ({
logo = 'https://placehold.co/120x40?text=Logo',
logoAlt = 'Logo',
items,
className = '',
ease = 'power3.out',
baseColor = 'hsl(var(--background))',
menuColor = 'hsl(var(--foreground))',
buttonBgColor = 'hsl(var(--primary))',
buttonTextColor = 'hsl(var(--primary-foreground))',
buttonText = 'Get Started'
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const navRef = useRef<HTMLDivElement | null>(null);
const cardsRef = useRef<HTMLDivElement[]>([]);
const tlRef = useRef<gsap.core.Timeline | null>(null);
const calculateHeight = () => {
const navEl = navRef.current;
if (!navEl) return 260;
const isMobile = window.matchMedia('(max-width: 768px)').matches;
if (isMobile) {
const contentEl = navEl.querySelector('.card-nav-content') as HTMLElement;
if (contentEl) {
const wasVisible = contentEl.style.visibility;
const wasPosition = contentEl.style.position;
const wasHeight = contentEl.style.height;
contentEl.style.visibility = 'visible';
contentEl.style.position = 'static';
contentEl.style.height = 'auto';
const topBar = 60;
const padding = 16;
const contentHeight = contentEl.scrollHeight;
contentEl.style.visibility = wasVisible;
contentEl.style.position = wasPosition;
contentEl.style.height = wasHeight;
return topBar + contentHeight + padding;
}
}
return 280;
};
const createTimeline = () => {
const navEl = navRef.current;
if (!navEl) return null;
gsap.set(navEl, { height: 60, overflow: 'hidden' });
gsap.set(cardsRef.current, { y: 30, opacity: 0 });
const tl = gsap.timeline({ paused: true });
tl.to(navEl, {
height: calculateHeight,
duration: 0.5,
ease: ease
});
tl.to(cardsRef.current, {
y: 0,
opacity: 1,
duration: 0.4,
ease: ease,
stagger: 0.08
}, '-=0.2');
return tl;
};
useLayoutEffect(() => {
const ctx = gsap.context(() => {
const tl = createTimeline();
tlRef.current = tl;
}, navRef);
return () => ctx.revert();
}, [ease, items]);
useLayoutEffect(() => {
const handleResize = () => {
if (!tlRef.current) return;
if (isExpanded) {
const newHeight = calculateHeight();
gsap.to(navRef.current, { height: newHeight, duration: 0.3 });
}
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [isExpanded]);
const toggleMenu = () => {
const tl = tlRef.current;
if (!tl) return;
if (!isExpanded) {
setIsExpanded(true);
tl.play();
} else {
tl.reverse().eventCallback('onReverseComplete', () => setIsExpanded(false));
}
};
const setCardRef = (i: number) => (el: HTMLDivElement | null) => {
if (el) cardsRef.current[i] = el;
};
return (
<div className={`card-nav-container relative w-full max-w-[800px] mx-auto z-[99] ${className}`}>
<nav
ref={navRef}
className={`card-nav ${isExpanded ? 'open' : ''} block h-[60px] p-0 rounded-2xl shadow-lg relative overflow-hidden will-change-[height] border border-border`}
style={{ backgroundColor: baseColor }}
>
{/* Top Bar */}
<div className="card-nav-top absolute inset-x-0 top-0 h-[60px] flex items-center justify-between px-4 z-[2]">
<button
onClick={toggleMenu}
className="group h-10 w-10 flex items-center justify-center rounded-xl hover:bg-muted/50 transition-colors cursor-pointer"
aria-label={isExpanded ? 'Close menu' : 'Open menu'}
style={{ color: menuColor }}
>
{isExpanded ? (
<X size={24} className="transition-transform duration-300 rotate-0 group-hover:rotate-90" />
) : (
<Menu size={24} className="transition-transform duration-300" />
)}
</button>
<div className="logo-container absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
<img src={logo} alt={logoAlt} className="h-7 w-auto object-contain" />
</div>
<button
type="button"
className="card-nav-cta-button hidden md:inline-flex rounded-xl px-5 items-center h-10 font-semibold text-sm transition-all duration-300 hover:scale-[1.02] active:scale-[0.98]"
style={{ backgroundColor: buttonBgColor, color: buttonTextColor }}
>
{buttonText}
</button>
</div>
{/* Content/Cards */}
<div
className={`card-nav-content absolute left-0 right-0 top-[60px] bottom-0 p-3 flex flex-col gap-3 justify-start z-[1] ${
isExpanded ? 'visible pointer-events-auto' : 'invisible pointer-events-none'
} md:flex-row md:items-stretch md:gap-4`}
aria-hidden={!isExpanded}
>
{items.map((item, idx) => (
<div
key={`${item.label}-${idx}`}
ref={setCardRef(idx)}
className="nav-card relative flex flex-col gap-3 p-5 rounded-xl min-w-0 flex-[1_1_auto] md:flex-1 group/card"
style={{ backgroundColor: item.bgColor, color: item.textColor }}
>
<div className="nav-card-label font-bold tracking-tight text-xl md:text-2xl">
{item.label}
</div>
<div className="nav-card-links mt-auto flex flex-col gap-1.5">
{item.links.map((lnk, i) => (
<a
key={`${lnk.label}-${i}`}
className="nav-card-link inline-flex items-center gap-2 no-underline cursor-pointer transition-all duration-300 hover:translate-x-1 text-sm md:text-base opacity-80 hover:opacity-100"
href={lnk.href}
aria-label={lnk.ariaLabel}
>
<ArrowUpRight size={16} className="shrink-0" />
<span className="font-medium">{lnk.label}</span>
</a>
))}
</div>
</div>
))}
</div>
</nav>
</div>
);
};
export default CardNav;
~~~
Implementation Guidelines
1. Analyze the component structure, styling, animation implementations
2. Review the component's arguments and state
3. Think through what is the best place to adopt this component/style into the design we are doing
4. Then adopt the component/design to our current system
Help me integrate this into my design