2218 lines
80 KiB
Plaintext
2218 lines
80 KiB
Plaintext
import { useState, useEffect } from 'react'
|
||
import {
|
||
User,
|
||
Bell,
|
||
Calendar,
|
||
Settings,
|
||
Check,
|
||
X,
|
||
MoreVertical,
|
||
MessageSquare,
|
||
ChevronLeft,
|
||
ChevronRight,
|
||
Slack,
|
||
Github,
|
||
Video,
|
||
Sun,
|
||
Moon,
|
||
Play,
|
||
RotateCcw,
|
||
Sparkles,
|
||
Zap,
|
||
Heart,
|
||
Star,
|
||
ArrowRight,
|
||
Plus,
|
||
Minus
|
||
} from 'lucide-react'
|
||
import { motion, AnimatePresence, useMotionValue, useTransform, useSpring } from 'framer-motion'
|
||
import { cn } from './lib/utils'
|
||
|
||
// ============================================
|
||
// THEME SYSTEM
|
||
// ============================================
|
||
type ColorTheme = 'default' | 'dusk' | 'lime' | 'ocean' | 'retro' | 'neo' | 'forest'
|
||
type Mode = 'light' | 'dark'
|
||
|
||
interface ThemeConfig {
|
||
colorTheme: ColorTheme
|
||
mode: Mode
|
||
}
|
||
|
||
const COLOR_THEMES: { id: ColorTheme; name: string; description: string; previewColors: { bg: string; accent: string; darkBg: string; darkAccent?: string } }[] = [
|
||
{
|
||
id: 'default',
|
||
name: 'Default',
|
||
description: 'Oscura-inspired with pale yellow accent',
|
||
previewColors: { bg: '#F2F2ED', accent: '#E6E7A3', darkBg: '#0B0B0F', darkAccent: '#E6E7A3' }
|
||
},
|
||
{
|
||
id: 'dusk',
|
||
name: 'Dusk',
|
||
description: 'Warmer variant with slightly lighter dark mode',
|
||
previewColors: { bg: '#F5F5F0', accent: '#E6E7A3', darkBg: '#131419', darkAccent: '#E6E7A3' }
|
||
},
|
||
{
|
||
id: 'lime',
|
||
name: 'Lime',
|
||
description: 'Fresh, energetic lime with purple accents',
|
||
previewColors: { bg: '#E8F5A3', accent: '#7C3AED', darkBg: '#0F0F1A' }
|
||
},
|
||
{
|
||
id: 'ocean',
|
||
name: 'Ocean',
|
||
description: 'Calm, professional blue tones',
|
||
previewColors: { bg: '#E0F2FE', accent: '#0284C7', darkBg: '#082F49' }
|
||
},
|
||
{
|
||
id: 'retro',
|
||
name: 'Retro',
|
||
description: 'Warm, nostalgic amber vibes',
|
||
previewColors: { bg: '#FEF3C7', accent: '#D97706', darkBg: '#1C1917' }
|
||
},
|
||
{
|
||
id: 'neo',
|
||
name: 'Neo',
|
||
description: 'Modern cyberpunk pink/magenta',
|
||
previewColors: { bg: '#FDF4FF', accent: '#D946EF', darkBg: '#0F0720' }
|
||
},
|
||
{
|
||
id: 'forest',
|
||
name: 'Forest',
|
||
description: 'Natural, earthy green tones',
|
||
previewColors: { bg: '#DCFCE7', accent: '#16A34A', darkBg: '#052E16' }
|
||
}
|
||
]
|
||
|
||
function useTheme() {
|
||
const [config, setConfig] = useState<ThemeConfig>(() => {
|
||
if (typeof window !== 'undefined') {
|
||
const stored = localStorage.getItem('design-system-theme-config')
|
||
if (stored) {
|
||
try {
|
||
const parsed = JSON.parse(stored)
|
||
// Validate that the stored theme still exists
|
||
const themeExists = COLOR_THEMES.some(t => t.id === parsed.colorTheme)
|
||
if (themeExists) {
|
||
return parsed
|
||
}
|
||
// Fall back to default if theme was removed
|
||
return {
|
||
colorTheme: 'default' as ColorTheme,
|
||
mode: parsed.mode || 'light'
|
||
}
|
||
} catch {}
|
||
}
|
||
return {
|
||
colorTheme: 'default' as ColorTheme,
|
||
mode: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||
}
|
||
}
|
||
return { colorTheme: 'default', mode: 'light' }
|
||
})
|
||
|
||
useEffect(() => {
|
||
const root = document.documentElement
|
||
|
||
// Set color theme
|
||
if (config.colorTheme === 'default') {
|
||
root.removeAttribute('data-theme')
|
||
} else {
|
||
root.setAttribute('data-theme', config.colorTheme)
|
||
}
|
||
|
||
// Set mode
|
||
if (config.mode === 'dark') {
|
||
root.classList.add('dark')
|
||
} else {
|
||
root.classList.remove('dark')
|
||
}
|
||
|
||
localStorage.setItem('design-system-theme-config', JSON.stringify(config))
|
||
}, [config])
|
||
|
||
const setColorTheme = (colorTheme: ColorTheme) => setConfig(c => ({ ...c, colorTheme }))
|
||
const setMode = (mode: Mode) => setConfig(c => ({ ...c, mode }))
|
||
const toggleMode = () => setConfig(c => ({ ...c, mode: c.mode === 'light' ? 'dark' : 'light' }))
|
||
|
||
return {
|
||
colorTheme: config.colorTheme,
|
||
mode: config.mode,
|
||
setColorTheme,
|
||
setMode,
|
||
toggleMode,
|
||
themes: COLOR_THEMES
|
||
}
|
||
}
|
||
|
||
// Theme Selector Component
|
||
function ThemeSelector({
|
||
colorTheme,
|
||
mode,
|
||
onColorThemeChange,
|
||
onModeToggle,
|
||
themes
|
||
}: {
|
||
colorTheme: ColorTheme
|
||
mode: Mode
|
||
onColorThemeChange: (theme: ColorTheme) => void
|
||
onModeToggle: () => void
|
||
themes: typeof COLOR_THEMES
|
||
}) {
|
||
const [isOpen, setIsOpen] = useState(false)
|
||
|
||
// Find theme with fallback to first theme (default)
|
||
const currentTheme = themes.find(t => t.id === colorTheme) || themes[0]
|
||
|
||
return (
|
||
<div className="flex items-center gap-3">
|
||
{/* Color Theme Dropdown */}
|
||
<div className="relative">
|
||
<button
|
||
onClick={() => setIsOpen(!isOpen)}
|
||
className="flex items-center gap-2 px-3 py-2 rounded-[var(--radius-lg)] bg-[var(--color-background-secondary)] hover:bg-[var(--color-border-default)] transition-colors"
|
||
>
|
||
<div
|
||
className="w-4 h-4 rounded-full border-2 border-white shadow-sm"
|
||
style={{ backgroundColor: mode === 'dark' ? currentTheme.previewColors.accent : currentTheme.previewColors.bg }}
|
||
/>
|
||
<span className="text-body-medium font-medium">{currentTheme.name}</span>
|
||
<ChevronLeft className={cn(
|
||
"w-4 h-4 text-[var(--color-text-tertiary)] transition-transform",
|
||
isOpen ? "rotate-90" : "-rotate-90"
|
||
)} />
|
||
</button>
|
||
|
||
{isOpen && (
|
||
<>
|
||
<div
|
||
className="fixed inset-0 z-40"
|
||
onClick={() => setIsOpen(false)}
|
||
/>
|
||
<div className="absolute top-full right-0 mt-2 w-64 p-2 bg-[var(--color-surface-card)] rounded-[var(--radius-lg)] shadow-[var(--shadow-lg)] border border-[var(--color-border-default)] z-50">
|
||
{themes.map((theme) => (
|
||
<button
|
||
key={theme.id}
|
||
onClick={() => {
|
||
onColorThemeChange(theme.id)
|
||
setIsOpen(false)
|
||
}}
|
||
className={cn(
|
||
"w-full flex items-center gap-3 px-3 py-2 rounded-[var(--radius-md)] transition-colors text-left",
|
||
colorTheme === theme.id
|
||
? "bg-[var(--color-accent-primary-light)]"
|
||
: "hover:bg-[var(--color-background-secondary)]"
|
||
)}
|
||
>
|
||
<div className="flex -space-x-1">
|
||
<div
|
||
className="w-5 h-5 rounded-full border-2 border-white shadow-sm"
|
||
style={{ backgroundColor: theme.previewColors.bg }}
|
||
/>
|
||
<div
|
||
className="w-5 h-5 rounded-full border-2 border-white shadow-sm"
|
||
style={{ backgroundColor: theme.previewColors.accent }}
|
||
/>
|
||
</div>
|
||
<div className="flex-1 min-w-0">
|
||
<p className="text-body-medium font-medium">{theme.name}</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)] truncate">{theme.description}</p>
|
||
</div>
|
||
{colorTheme === theme.id && (
|
||
<Check className="w-4 h-4 text-[var(--color-accent-primary)]" />
|
||
)}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
|
||
{/* Light/Dark Toggle */}
|
||
<button
|
||
onClick={onModeToggle}
|
||
className="p-2 rounded-[var(--radius-lg)] bg-[var(--color-background-secondary)] hover:bg-[var(--color-border-default)] transition-colors"
|
||
aria-label={`Switch to ${mode === 'light' ? 'dark' : 'light'} mode`}
|
||
>
|
||
{mode === 'light' ? (
|
||
<Moon className="w-5 h-5 text-[var(--color-text-secondary)]" />
|
||
) : (
|
||
<Sun className="w-5 h-5 text-[var(--color-text-secondary)]" />
|
||
)}
|
||
</button>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// ============================================
|
||
// DESIGN SYSTEM COMPONENTS
|
||
// ============================================
|
||
|
||
// Button Component
|
||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||
variant?: 'primary' | 'secondary' | 'ghost' | 'success' | 'danger'
|
||
size?: 'sm' | 'md' | 'lg'
|
||
pill?: boolean
|
||
}
|
||
|
||
function Button({
|
||
children,
|
||
variant = 'primary',
|
||
size = 'md',
|
||
pill = false,
|
||
className,
|
||
...props
|
||
}: ButtonProps) {
|
||
const baseStyles = 'inline-flex items-center justify-center font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2'
|
||
|
||
const variants = {
|
||
primary: 'bg-[var(--color-accent-primary)] text-[var(--color-text-inverse)] hover:bg-[var(--color-accent-primary-hover)] focus:ring-[var(--color-accent-primary)]',
|
||
secondary: 'bg-transparent border border-[var(--color-border-default)] text-[var(--color-text-primary)] hover:bg-[var(--color-background-secondary)]',
|
||
ghost: 'bg-transparent text-[var(--color-text-secondary)] hover:bg-[var(--color-background-secondary)]',
|
||
success: 'bg-[var(--color-semantic-success)] text-white hover:opacity-90',
|
||
danger: 'bg-[var(--color-semantic-error)] text-white hover:opacity-90'
|
||
}
|
||
|
||
const sizes = {
|
||
sm: 'h-8 px-3 text-xs',
|
||
md: 'h-10 px-4 text-sm',
|
||
lg: 'h-12 px-6 text-base'
|
||
}
|
||
|
||
const radius = pill ? 'rounded-full' : 'rounded-[var(--radius-md)]'
|
||
|
||
return (
|
||
<button
|
||
className={cn(baseStyles, variants[variant], sizes[size], radius, className)}
|
||
{...props}
|
||
>
|
||
{children}
|
||
</button>
|
||
)
|
||
}
|
||
|
||
// Badge Component
|
||
interface BadgeProps {
|
||
children: React.ReactNode
|
||
variant?: 'default' | 'primary' | 'success' | 'warning' | 'error' | 'outline'
|
||
}
|
||
|
||
function Badge({ children, variant = 'default' }: BadgeProps) {
|
||
const variants = {
|
||
default: 'bg-[var(--color-background-secondary)] text-[var(--color-text-secondary)]',
|
||
primary: 'bg-[var(--color-accent-primary-light)] text-[var(--color-accent-primary)]',
|
||
success: 'bg-[var(--color-semantic-success-light)] text-[var(--color-semantic-success)]',
|
||
warning: 'bg-[var(--color-semantic-warning-light)] text-[var(--color-semantic-warning)]',
|
||
error: 'bg-[var(--color-semantic-error-light)] text-[var(--color-semantic-error)]',
|
||
outline: 'bg-transparent border border-[var(--color-border-default)] text-[var(--color-text-secondary)]'
|
||
}
|
||
|
||
return (
|
||
<span className={cn(
|
||
'inline-flex items-center px-3 py-1 rounded-full text-label-small',
|
||
variants[variant]
|
||
)}>
|
||
{children}
|
||
</span>
|
||
)
|
||
}
|
||
|
||
// Avatar Component
|
||
interface AvatarProps {
|
||
src?: string
|
||
name?: string
|
||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
||
}
|
||
|
||
function Avatar({ src, name = 'User', size = 'md', color }: AvatarProps & { color?: string }) {
|
||
const sizes = {
|
||
xs: 'w-6 h-6 text-[10px]',
|
||
sm: 'w-8 h-8 text-xs',
|
||
md: 'w-10 h-10 text-sm',
|
||
lg: 'w-14 h-14 text-base',
|
||
xl: 'w-20 h-20 text-xl',
|
||
'2xl': 'w-[120px] h-[120px] text-3xl'
|
||
}
|
||
|
||
const initials = name.split(' ').map(n => n[0]).join('').slice(0, 2).toUpperCase()
|
||
|
||
// Default to neutral gray, can be overridden with color prop
|
||
const bgStyle = color
|
||
? { backgroundColor: color }
|
||
: {}
|
||
|
||
return (
|
||
<div
|
||
className={cn(
|
||
'rounded-full flex items-center justify-center font-semibold border-2 border-[var(--color-surface-card)] overflow-hidden',
|
||
!color && 'bg-[var(--color-border-default)]',
|
||
sizes[size]
|
||
)}
|
||
style={bgStyle}
|
||
>
|
||
{src ? (
|
||
<img src={src} alt={name} className="w-full h-full object-cover" />
|
||
) : (
|
||
<span className={cn(color ? 'text-white' : 'text-[var(--color-text-primary)]')}>{initials}</span>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Avatar Group
|
||
function AvatarGroup({ avatars, max = 4 }: { avatars: { name: string; src?: string }[]; max?: number }) {
|
||
const visible = avatars.slice(0, max)
|
||
const remaining = avatars.length - max
|
||
|
||
return (
|
||
<div className="flex -space-x-2">
|
||
{visible.map((avatar, i) => (
|
||
<Avatar key={i} {...avatar} size="sm" />
|
||
))}
|
||
{remaining > 0 && (
|
||
<div className="w-8 h-8 rounded-full bg-[var(--color-background-secondary)] flex items-center justify-center text-xs font-medium text-[var(--color-text-secondary)] border-2 border-[var(--color-surface-card)]">
|
||
+{remaining}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Progress Circle Component
|
||
function ProgressCircle({
|
||
value,
|
||
size = 'md',
|
||
color = 'var(--color-accent-primary)'
|
||
}: {
|
||
value: number
|
||
size?: 'sm' | 'md' | 'lg'
|
||
color?: string
|
||
}) {
|
||
const sizes = {
|
||
sm: { width: 40, stroke: 4, fontSize: 'text-[10px]' },
|
||
md: { width: 56, stroke: 5, fontSize: 'text-xs' },
|
||
lg: { width: 80, stroke: 6, fontSize: 'text-base' }
|
||
}
|
||
|
||
const { width, stroke, fontSize } = sizes[size]
|
||
const radius = (width - stroke) / 2
|
||
const circumference = 2 * Math.PI * radius
|
||
const offset = circumference - (value / 100) * circumference
|
||
|
||
return (
|
||
<div className="relative inline-flex items-center justify-center">
|
||
<svg width={width} height={width} className="-rotate-90">
|
||
<circle
|
||
cx={width / 2}
|
||
cy={width / 2}
|
||
r={radius}
|
||
fill="none"
|
||
stroke="var(--color-border-default)"
|
||
strokeWidth={stroke}
|
||
/>
|
||
<circle
|
||
cx={width / 2}
|
||
cy={width / 2}
|
||
r={radius}
|
||
fill="none"
|
||
stroke={color}
|
||
strokeWidth={stroke}
|
||
strokeDasharray={circumference}
|
||
strokeDashoffset={offset}
|
||
strokeLinecap="round"
|
||
className="transition-all duration-500"
|
||
/>
|
||
</svg>
|
||
<span className={cn('absolute font-semibold', fontSize)}>
|
||
{value}%
|
||
</span>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Card Component
|
||
function Card({
|
||
children,
|
||
className,
|
||
padding = true
|
||
}: {
|
||
children: React.ReactNode
|
||
className?: string
|
||
padding?: boolean
|
||
}) {
|
||
return (
|
||
<div className={cn(
|
||
'bg-[var(--color-surface-card)] rounded-[var(--radius-xl)] shadow-[var(--shadow-md)]',
|
||
padding && 'p-6',
|
||
className
|
||
)}>
|
||
{children}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Input Component
|
||
function Input({
|
||
placeholder,
|
||
className,
|
||
...props
|
||
}: React.InputHTMLAttributes<HTMLInputElement>) {
|
||
return (
|
||
<input
|
||
className={cn(
|
||
'h-10 w-full px-4 rounded-[var(--radius-md)] border border-[var(--color-border-default)]',
|
||
'bg-[var(--color-surface-card)] text-[var(--color-text-primary)] text-sm',
|
||
'focus:outline-none focus:border-[var(--color-accent-primary)] focus:ring-2 focus:ring-[var(--color-accent-primary)]/20',
|
||
'placeholder:text-[var(--color-text-tertiary)]',
|
||
'transition-all duration-200',
|
||
'disabled:bg-[var(--color-background-secondary)] disabled:opacity-60',
|
||
className
|
||
)}
|
||
placeholder={placeholder}
|
||
{...props}
|
||
/>
|
||
)
|
||
}
|
||
|
||
// Toggle Component
|
||
function Toggle({ checked, onChange }: { checked: boolean; onChange: (checked: boolean) => void }) {
|
||
return (
|
||
<button
|
||
role="switch"
|
||
aria-checked={checked}
|
||
onClick={() => onChange(!checked)}
|
||
className={cn(
|
||
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200',
|
||
checked ? 'bg-[var(--color-accent-primary)]' : 'bg-[var(--color-border-default)]'
|
||
)}
|
||
>
|
||
<span
|
||
className={cn(
|
||
'inline-block h-5 w-5 rounded-full bg-white shadow-sm transition-transform duration-200',
|
||
checked ? 'translate-x-[22px]' : 'translate-x-[2px]'
|
||
)}
|
||
/>
|
||
</button>
|
||
)
|
||
}
|
||
|
||
// ============================================
|
||
// DEMO COMPONENTS (Matching the screenshot)
|
||
// ============================================
|
||
|
||
// Profile Card
|
||
function ProfileCard() {
|
||
return (
|
||
<Card className="w-[280px]">
|
||
<div className="flex justify-end mb-4">
|
||
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded transition-colors">
|
||
<MoreVertical className="w-5 h-5 text-[var(--color-text-tertiary)]" />
|
||
</button>
|
||
</div>
|
||
<div className="flex flex-col items-center text-center">
|
||
<Avatar size="2xl" name="Christine Thompson" />
|
||
<h3 className="text-heading-large mt-4">Christine Thompson</h3>
|
||
<p className="text-body-medium text-[var(--color-text-secondary)]">Project manager</p>
|
||
<div className="flex flex-wrap gap-2 mt-4 justify-center">
|
||
<Badge variant="outline">UI/UX Design</Badge>
|
||
<Badge variant="outline">Project management</Badge>
|
||
<Badge variant="outline">Agile methodologies</Badge>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
// Notifications Card
|
||
function NotificationsCard() {
|
||
return (
|
||
<Card className="w-[320px]" padding={false}>
|
||
<div className="p-4 border-b border-[var(--color-border-default)]">
|
||
<div className="flex items-center justify-between">
|
||
<h3 className="text-heading-small">Notifications</h3>
|
||
<Badge variant="primary">6</Badge>
|
||
</div>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)] mt-1">Unread</p>
|
||
</div>
|
||
|
||
<div className="divide-y divide-[var(--color-border-default)]">
|
||
<div className="p-4 flex gap-3">
|
||
<Avatar size="sm" name="Ashlynn George" />
|
||
<div className="flex-1 min-w-0">
|
||
<p className="text-body-small">
|
||
<span className="font-semibold">Ashlynn George</span>
|
||
<span className="text-[var(--color-text-tertiary)]"> · 1h</span>
|
||
</p>
|
||
<p className="text-body-small text-[var(--color-text-secondary)]">
|
||
has invited you to access "Magma project"
|
||
</p>
|
||
<div className="flex gap-2 mt-2">
|
||
<Button size="sm" variant="success" pill>
|
||
<Check className="w-3 h-3 mr-1" /> Accept
|
||
</Button>
|
||
<Button size="sm" variant="secondary" pill>
|
||
<X className="w-3 h-3 mr-1" /> Deny request
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded self-start transition-colors">
|
||
<MoreVertical className="w-4 h-4 text-[var(--color-text-tertiary)]" />
|
||
</button>
|
||
</div>
|
||
|
||
<div className="p-4 flex gap-3">
|
||
<Avatar size="sm" name="Ashlynn George" />
|
||
<div className="flex-1">
|
||
<p className="text-body-small">
|
||
<span className="font-semibold">Ashlynn George</span>
|
||
<span className="text-[var(--color-text-tertiary)]"> · 1h</span>
|
||
</p>
|
||
<p className="text-body-small text-[var(--color-text-secondary)]">
|
||
changed status of task in "Magma project"
|
||
</p>
|
||
</div>
|
||
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded self-start transition-colors">
|
||
<MoreVertical className="w-4 h-4 text-[var(--color-text-tertiary)]" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-4 flex gap-2 border-t border-[var(--color-border-default)]">
|
||
<Button variant="secondary" className="flex-1" pill>Mark all as read</Button>
|
||
<Button variant="primary" className="flex-1" pill>View all</Button>
|
||
</div>
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
// Calendar Card
|
||
function CalendarCard() {
|
||
const days = ['M', 'T', 'W', 'T', 'F', 'S', 'S']
|
||
const dates = [
|
||
[29, 30, 31, 1, 2, 3, 4],
|
||
[5, 6, 7, 8, 9, 10, 11],
|
||
[12, 13, 14, 15, 16, 17, 18],
|
||
[19, 20, 21, 22, 23, 24, 25],
|
||
[26, 27, 28, 29, 30, 31, 1]
|
||
]
|
||
|
||
return (
|
||
<Card className="w-[300px]">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded transition-colors">
|
||
<ChevronLeft className="w-5 h-5 text-[var(--color-text-tertiary)]" />
|
||
</button>
|
||
<h3 className="text-heading-small">February, 2021</h3>
|
||
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded transition-colors">
|
||
<ChevronRight className="w-5 h-5 text-[var(--color-text-tertiary)]" />
|
||
</button>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-7 gap-1 text-center">
|
||
{days.map((day, i) => (
|
||
<div key={i} className="text-label-small text-[var(--color-text-tertiary)] py-2">
|
||
{day}
|
||
</div>
|
||
))}
|
||
{dates.flat().map((date, i) => {
|
||
const isCurrentMonth = (i < 3 && date > 20) || (i > 30 && date < 10) ? false : true
|
||
const isSelected = date === 26 && isCurrentMonth
|
||
const isToday = date === 16 && isCurrentMonth
|
||
|
||
return (
|
||
<button
|
||
key={i}
|
||
className={cn(
|
||
'w-9 h-9 rounded-[var(--radius-md)] text-body-medium transition-colors',
|
||
!isCurrentMonth && 'text-[var(--color-text-tertiary)]',
|
||
isSelected && 'bg-[var(--color-accent-primary)] text-[var(--color-text-inverse)] rounded-full',
|
||
isToday && !isSelected && 'text-[var(--color-accent-primary)] font-semibold',
|
||
!isSelected && 'hover:bg-[var(--color-background-secondary)]'
|
||
)}
|
||
>
|
||
{date}
|
||
</button>
|
||
)
|
||
})}
|
||
</div>
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
// Team Members Card
|
||
function TeamMembersCard() {
|
||
const members = [
|
||
{ name: 'Julie Andrews', role: 'Project manager' },
|
||
{ name: 'Kevin Conroy', role: 'Project manager' },
|
||
{ name: 'Jim Connor', role: 'Project manager' },
|
||
{ name: 'Tom Kinley', role: 'Project manager' }
|
||
]
|
||
|
||
return (
|
||
<Card className="w-[320px]" padding={false}>
|
||
<div className="divide-y divide-[var(--color-border-default)]">
|
||
{members.map((member, i) => (
|
||
<div key={i} className="p-4 flex items-center gap-3">
|
||
<Avatar name={member.name} />
|
||
<div className="flex-1">
|
||
<p className="text-heading-small">{member.name}</p>
|
||
<p className="text-body-small text-[var(--color-text-secondary)]">{member.role}</p>
|
||
</div>
|
||
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded transition-colors">
|
||
<MoreVertical className="w-4 h-4 text-[var(--color-text-tertiary)]" />
|
||
</button>
|
||
<button className="p-2 bg-[var(--color-semantic-error-light)] text-[var(--color-semantic-error)] rounded-[var(--radius-md)] hover:opacity-80 transition-opacity">
|
||
<MessageSquare className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div className="p-4 border-t border-[var(--color-border-default)] flex justify-center gap-3">
|
||
<img src="https://upload.wikimedia.org/wikipedia/commons/b/ba/Stripe_Logo%2C_revised_2016.svg" alt="Stripe" className="h-6" />
|
||
<div className="px-3 py-1 bg-[#1A1F71] text-white text-sm font-bold rounded">VISA</div>
|
||
<div className="px-2 py-1 bg-[#003087] text-white text-xs font-bold rounded">PayPal</div>
|
||
<div className="w-8 h-8 bg-gradient-to-r from-red-500 to-yellow-500 rounded-full" />
|
||
</div>
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
// Project Status Card
|
||
function ProjectStatusCard() {
|
||
return (
|
||
<Card className="w-[380px]">
|
||
<div className="flex justify-between items-start mb-4">
|
||
<ProgressCircle value={43} size="md" />
|
||
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded transition-colors">
|
||
<MoreVertical className="w-5 h-5 text-[var(--color-text-tertiary)]" />
|
||
</button>
|
||
</div>
|
||
|
||
<h3 className="text-heading-large mb-2">Amber website redesign</h3>
|
||
<p className="text-body-medium text-[var(--color-text-secondary)] mb-4">
|
||
In today's fast-paced digital landscape, our mission is to transform our website into a more intuitive, engaging, and user-friendly platfor...
|
||
</p>
|
||
|
||
<AvatarGroup
|
||
avatars={[
|
||
{ name: 'User 1' },
|
||
{ name: 'User 2' },
|
||
{ name: 'User 3' },
|
||
{ name: 'User 4' },
|
||
{ name: 'User 5' }
|
||
]}
|
||
max={4}
|
||
/>
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
// Milestone Card
|
||
function MilestoneCard() {
|
||
return (
|
||
<Card className="w-[380px]">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-heading-medium">Wireframes milestone</h3>
|
||
<Button variant="secondary" size="sm" pill>View details</Button>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-6">
|
||
<div>
|
||
<p className="text-body-small text-[var(--color-text-secondary)]">Due date:</p>
|
||
<p className="text-heading-small">March 20th</p>
|
||
</div>
|
||
|
||
<ProgressCircle value={39} size="lg" />
|
||
|
||
<div>
|
||
<p className="text-body-small text-[var(--color-text-secondary)]">Asignees:</p>
|
||
<AvatarGroup
|
||
avatars={[
|
||
{ name: 'A' },
|
||
{ name: 'B' },
|
||
{ name: 'C' },
|
||
{ name: 'D' },
|
||
{ name: 'E' }
|
||
]}
|
||
max={4}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
// Integrations Card
|
||
function IntegrationsCard() {
|
||
const [slack, setSlack] = useState(true)
|
||
const [meet, setMeet] = useState(true)
|
||
const [github, setGithub] = useState(false)
|
||
|
||
const integrations = [
|
||
{ icon: Slack, name: 'Slack', desc: 'Used as a main source of communication', enabled: slack, toggle: setSlack, color: '#E91E63' },
|
||
{ icon: Video, name: 'Google meet', desc: 'Used for all types of calls', enabled: meet, toggle: setMeet, color: '#00897B' },
|
||
{ icon: Github, name: 'Github', desc: 'Enables automated workflows, code synchronization', enabled: github, toggle: setGithub, color: '#333' }
|
||
]
|
||
|
||
return (
|
||
<Card className="w-[320px]">
|
||
<h3 className="text-heading-medium mb-4">Integrations</h3>
|
||
|
||
<div className="space-y-4">
|
||
{integrations.map((int, i) => (
|
||
<div key={i} className="flex items-center gap-3">
|
||
<div
|
||
className="w-10 h-10 rounded-[var(--radius-lg)] flex items-center justify-center"
|
||
style={{ backgroundColor: `${int.color}15` }}
|
||
>
|
||
<int.icon className="w-5 h-5" style={{ color: int.color }} />
|
||
</div>
|
||
<div className="flex-1 min-w-0">
|
||
<p className="text-heading-small">{int.name}</p>
|
||
<p className="text-body-small text-[var(--color-text-secondary)] truncate">{int.desc}</p>
|
||
</div>
|
||
<Toggle checked={int.enabled} onChange={int.toggle} />
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
// ============================================
|
||
// MAIN APP
|
||
// ============================================
|
||
|
||
export default function App() {
|
||
const [activeSection, setActiveSection] = useState('overview')
|
||
const { colorTheme, mode, setColorTheme, toggleMode, themes } = useTheme()
|
||
|
||
const sections = [
|
||
{ id: 'overview', label: 'Overview' },
|
||
{ id: 'colors', label: 'Colors' },
|
||
{ id: 'typography', label: 'Typography' },
|
||
{ id: 'components', label: 'Components' },
|
||
{ id: 'animations', label: 'Animations' },
|
||
{ id: 'themes', label: 'Themes' }
|
||
]
|
||
|
||
const currentThemeInfo = themes.find(t => t.id === colorTheme) || themes[0]
|
||
|
||
return (
|
||
<div className="min-h-screen p-8 transition-colors duration-300">
|
||
{/* Header */}
|
||
<div className="max-w-7xl mx-auto mb-8">
|
||
<Card className="!rounded-[var(--radius-2xl)]">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-display-medium">Auto-Build Design System</h1>
|
||
<p className="text-body-large text-[var(--color-text-secondary)] mt-1">
|
||
A modern, friendly design system for building beautiful interfaces
|
||
</p>
|
||
</div>
|
||
<div className="flex items-center gap-4">
|
||
{/* Theme Selector */}
|
||
<ThemeSelector
|
||
colorTheme={colorTheme}
|
||
mode={mode}
|
||
onColorThemeChange={setColorTheme}
|
||
onModeToggle={toggleMode}
|
||
themes={themes}
|
||
/>
|
||
|
||
{/* Section Navigation */}
|
||
<div className="flex gap-2">
|
||
{sections.map((section) => (
|
||
<Button
|
||
key={section.id}
|
||
variant={activeSection === section.id ? 'primary' : 'ghost'}
|
||
pill
|
||
onClick={() => setActiveSection(section.id)}
|
||
>
|
||
{section.label}
|
||
</Button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* Content */}
|
||
<div className="max-w-7xl mx-auto">
|
||
{activeSection === 'overview' && (
|
||
<div className="space-y-8">
|
||
{/* Demo Cards Grid - Replicating the screenshot layout */}
|
||
<section>
|
||
<h2 className="text-heading-large mb-6">Component Showcase</h2>
|
||
<div className="flex flex-wrap gap-6">
|
||
<ProfileCard />
|
||
<CalendarCard />
|
||
<ProjectStatusCard />
|
||
</div>
|
||
<div className="flex flex-wrap gap-6 mt-6">
|
||
<NotificationsCard />
|
||
<TeamMembersCard />
|
||
<div className="space-y-6">
|
||
<MilestoneCard />
|
||
<IntegrationsCard />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
)}
|
||
|
||
{activeSection === 'colors' && (
|
||
<div className="space-y-8">
|
||
<Card>
|
||
<div className="flex items-center justify-between mb-6">
|
||
<h2 className="text-heading-large">Color Palette</h2>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">
|
||
Currently showing: <strong className="text-[var(--color-text-primary)]">{currentThemeInfo.name}</strong> theme
|
||
</p>
|
||
</div>
|
||
|
||
<div className="space-y-6">
|
||
<div>
|
||
<h3 className="text-heading-small mb-3">Background</h3>
|
||
<div className="flex gap-4">
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-background-primary)] border border-[var(--color-border-default)]" />
|
||
<p className="text-label-small mt-2">Primary</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">--bg-primary</p>
|
||
</div>
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-background-secondary)] border border-[var(--color-border-default)]" />
|
||
<p className="text-label-small mt-2">Secondary</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">--bg-secondary</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h3 className="text-heading-small mb-3">Accent</h3>
|
||
<div className="flex gap-4">
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-accent-primary)]" />
|
||
<p className="text-label-small mt-2">Primary</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">--accent</p>
|
||
</div>
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-accent-primary-hover)]" />
|
||
<p className="text-label-small mt-2">Hover</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">--accent-hover</p>
|
||
</div>
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-accent-primary-light)] border border-[var(--color-border-default)]" />
|
||
<p className="text-label-small mt-2">Light</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">--accent-light</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h3 className="text-heading-small mb-3">Semantic</h3>
|
||
<div className="flex gap-4">
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-semantic-success)]" />
|
||
<p className="text-label-small mt-2">Success</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">--success</p>
|
||
</div>
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-semantic-warning)]" />
|
||
<p className="text-label-small mt-2">Warning</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">--warning</p>
|
||
</div>
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-semantic-error)]" />
|
||
<p className="text-label-small mt-2">Error</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">--error</p>
|
||
</div>
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-semantic-info)]" />
|
||
<p className="text-label-small mt-2">Info</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">--info</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h3 className="text-heading-small mb-3">Text</h3>
|
||
<div className="flex gap-4">
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-text-primary)]" />
|
||
<p className="text-label-small mt-2">Primary</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">--text-primary</p>
|
||
</div>
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-text-secondary)]" />
|
||
<p className="text-label-small mt-2">Secondary</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">--text-secondary</p>
|
||
</div>
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-text-tertiary)]" />
|
||
<p className="text-label-small mt-2">Tertiary</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">--text-tertiary</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Theme-specific color values */}
|
||
<div className="mt-8 p-4 bg-[var(--color-background-secondary)] rounded-[var(--radius-lg)]">
|
||
<p className="text-body-small text-[var(--color-text-secondary)]">
|
||
<strong>Note:</strong> Colors vary by theme and mode. Switch themes using the dropdown above to see different palettes.
|
||
For specific hex values, see the <strong>Themes</strong> tab or check <code className="font-mono bg-[var(--color-background-neutral)] px-1 rounded">design.json</code>.
|
||
</p>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
|
||
{activeSection === 'typography' && (
|
||
<div className="space-y-8">
|
||
<Card>
|
||
<h2 className="text-heading-large mb-6">Typography Scale</h2>
|
||
|
||
<div className="space-y-6">
|
||
<div className="border-b border-[var(--color-border-default)] pb-4">
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Display Large • 36px / 700</p>
|
||
<p className="text-display-large">The quick brown fox jumps</p>
|
||
</div>
|
||
<div className="border-b border-[var(--color-border-default)] pb-4">
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Display Medium • 30px / 700</p>
|
||
<p className="text-display-medium">The quick brown fox jumps over</p>
|
||
</div>
|
||
<div className="border-b border-[var(--color-border-default)] pb-4">
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Heading Large • 24px / 600</p>
|
||
<p className="text-heading-large">The quick brown fox jumps over the lazy dog</p>
|
||
</div>
|
||
<div className="border-b border-[var(--color-border-default)] pb-4">
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Heading Medium • 20px / 600</p>
|
||
<p className="text-heading-medium">The quick brown fox jumps over the lazy dog</p>
|
||
</div>
|
||
<div className="border-b border-[var(--color-border-default)] pb-4">
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Heading Small • 16px / 600</p>
|
||
<p className="text-heading-small">The quick brown fox jumps over the lazy dog</p>
|
||
</div>
|
||
<div className="border-b border-[var(--color-border-default)] pb-4">
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Body Large • 16px / 400</p>
|
||
<p className="text-body-large">The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.</p>
|
||
</div>
|
||
<div className="border-b border-[var(--color-border-default)] pb-4">
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Body Medium • 14px / 400</p>
|
||
<p className="text-body-medium">The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Body Small • 12px / 400</p>
|
||
<p className="text-body-small">The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.</p>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
|
||
{activeSection === 'components' && (
|
||
<div className="space-y-8">
|
||
{/* Buttons */}
|
||
<Card>
|
||
<h2 className="text-heading-large mb-6">Buttons</h2>
|
||
|
||
<div className="space-y-6">
|
||
<div>
|
||
<h3 className="text-heading-small mb-3">Variants</h3>
|
||
<div className="flex flex-wrap gap-4">
|
||
<Button variant="primary">Primary</Button>
|
||
<Button variant="secondary">Secondary</Button>
|
||
<Button variant="ghost">Ghost</Button>
|
||
<Button variant="success">Success</Button>
|
||
<Button variant="danger">Danger</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h3 className="text-heading-small mb-3">Pill Buttons</h3>
|
||
<div className="flex flex-wrap gap-4">
|
||
<Button variant="primary" pill>Primary Pill</Button>
|
||
<Button variant="secondary" pill>Secondary Pill</Button>
|
||
<Button variant="ghost" pill>Ghost Pill</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h3 className="text-heading-small mb-3">Sizes</h3>
|
||
<div className="flex flex-wrap items-center gap-4">
|
||
<Button size="sm">Small</Button>
|
||
<Button size="md">Medium</Button>
|
||
<Button size="lg">Large</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* Badges */}
|
||
<Card>
|
||
<h2 className="text-heading-large mb-6">Badges</h2>
|
||
<div className="flex flex-wrap gap-4">
|
||
<Badge variant="default">Default</Badge>
|
||
<Badge variant="primary">Primary</Badge>
|
||
<Badge variant="success">Success</Badge>
|
||
<Badge variant="warning">Warning</Badge>
|
||
<Badge variant="error">Error</Badge>
|
||
<Badge variant="outline">Outline</Badge>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* Avatars */}
|
||
<Card>
|
||
<h2 className="text-heading-large mb-6">Avatars</h2>
|
||
|
||
<div className="space-y-6">
|
||
<div>
|
||
<h3 className="text-heading-small mb-3">Sizes</h3>
|
||
<div className="flex items-end gap-4">
|
||
<Avatar size="xs" name="XS" />
|
||
<Avatar size="sm" name="SM" />
|
||
<Avatar size="md" name="MD" />
|
||
<Avatar size="lg" name="LG" />
|
||
<Avatar size="xl" name="XL" />
|
||
<Avatar size="2xl" name="2XL" />
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h3 className="text-heading-small mb-3">Avatar Group</h3>
|
||
<AvatarGroup
|
||
avatars={[
|
||
{ name: 'Alice' },
|
||
{ name: 'Bob' },
|
||
{ name: 'Charlie' },
|
||
{ name: 'Diana' },
|
||
{ name: 'Eve' },
|
||
{ name: 'Frank' }
|
||
]}
|
||
max={4}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* Progress */}
|
||
<Card>
|
||
<h2 className="text-heading-large mb-6">Progress Circles</h2>
|
||
<div className="flex items-center gap-8">
|
||
<ProgressCircle value={25} size="sm" />
|
||
<ProgressCircle value={50} size="md" />
|
||
<ProgressCircle value={75} size="lg" />
|
||
<ProgressCircle value={100} size="lg" color="var(--color-semantic-success)" />
|
||
</div>
|
||
</Card>
|
||
|
||
{/* Inputs */}
|
||
<Card>
|
||
<h2 className="text-heading-large mb-6">Inputs</h2>
|
||
<div className="max-w-md space-y-4">
|
||
<Input placeholder="Default input" />
|
||
<Input placeholder="Disabled input" disabled />
|
||
</div>
|
||
</Card>
|
||
|
||
{/* Toggles */}
|
||
<Card>
|
||
<h2 className="text-heading-large mb-6">Toggles</h2>
|
||
<div className="flex gap-8">
|
||
<div className="flex items-center gap-3">
|
||
<Toggle checked={false} onChange={() => {}} />
|
||
<span className="text-body-medium">Off</span>
|
||
</div>
|
||
<div className="flex items-center gap-3">
|
||
<Toggle checked={true} onChange={() => {}} />
|
||
<span className="text-body-medium">On</span>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* Cards */}
|
||
<Card>
|
||
<h2 className="text-heading-large mb-6">Cards</h2>
|
||
<div className="flex gap-4">
|
||
<Card className="w-64">
|
||
<h3 className="text-heading-small">Card Title</h3>
|
||
<p className="text-body-medium text-[var(--color-text-secondary)] mt-2">
|
||
This is a basic card with some content inside.
|
||
</p>
|
||
</Card>
|
||
<Card className="w-64 !rounded-[var(--radius-2xl)]">
|
||
<h3 className="text-heading-small">Large Radius</h3>
|
||
<p className="text-body-medium text-[var(--color-text-secondary)] mt-2">
|
||
This card uses the 2xl border radius.
|
||
</p>
|
||
</Card>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
|
||
{activeSection === 'animations' && (
|
||
<AnimationsSection theme={mode} colorTheme={currentThemeInfo.name} />
|
||
)}
|
||
|
||
{activeSection === 'themes' && (
|
||
<ThemesSection
|
||
currentTheme={colorTheme}
|
||
currentMode={mode}
|
||
themes={themes}
|
||
onThemeChange={setColorTheme}
|
||
onModeChange={toggleMode}
|
||
/>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// ============================================
|
||
// ANIMATIONS SECTION
|
||
// ============================================
|
||
|
||
// Animation Variants - Reusable motion configs
|
||
const animationVariants = {
|
||
// Fade animations
|
||
fadeIn: {
|
||
initial: { opacity: 0 },
|
||
animate: { opacity: 1 },
|
||
exit: { opacity: 0 }
|
||
},
|
||
|
||
// Scale animations
|
||
scaleIn: {
|
||
initial: { opacity: 0, scale: 0.9 },
|
||
animate: { opacity: 1, scale: 1 },
|
||
exit: { opacity: 0, scale: 0.9 }
|
||
},
|
||
|
||
// Slide animations
|
||
slideUp: {
|
||
initial: { opacity: 0, y: 20 },
|
||
animate: { opacity: 1, y: 0 },
|
||
exit: { opacity: 0, y: -20 }
|
||
},
|
||
|
||
slideDown: {
|
||
initial: { opacity: 0, y: -20 },
|
||
animate: { opacity: 1, y: 0 },
|
||
exit: { opacity: 0, y: 20 }
|
||
},
|
||
|
||
slideLeft: {
|
||
initial: { opacity: 0, x: 20 },
|
||
animate: { opacity: 1, x: 0 },
|
||
exit: { opacity: 0, x: -20 }
|
||
},
|
||
|
||
slideRight: {
|
||
initial: { opacity: 0, x: -20 },
|
||
animate: { opacity: 1, x: 0 },
|
||
exit: { opacity: 0, x: 20 }
|
||
},
|
||
|
||
// Spring pop
|
||
pop: {
|
||
initial: { opacity: 0, scale: 0.5 },
|
||
animate: {
|
||
opacity: 1,
|
||
scale: 1,
|
||
transition: { type: 'spring', stiffness: 500, damping: 25 }
|
||
},
|
||
exit: { opacity: 0, scale: 0.5 }
|
||
},
|
||
|
||
// Bounce
|
||
bounce: {
|
||
initial: { opacity: 0, y: -50 },
|
||
animate: {
|
||
opacity: 1,
|
||
y: 0,
|
||
transition: { type: 'spring', stiffness: 300, damping: 10 }
|
||
}
|
||
}
|
||
}
|
||
|
||
// Transition presets
|
||
const transitions = {
|
||
instant: { duration: 0.05 },
|
||
fast: { duration: 0.15 },
|
||
normal: { duration: 0.25 },
|
||
slow: { duration: 0.4 },
|
||
spring: { type: 'spring', stiffness: 400, damping: 25 },
|
||
springBouncy: { type: 'spring', stiffness: 300, damping: 10 },
|
||
springSmooth: { type: 'spring', stiffness: 200, damping: 20 },
|
||
easeOut: { duration: 0.25, ease: [0, 0, 0.2, 1] },
|
||
easeIn: { duration: 0.25, ease: [0.4, 0, 1, 1] },
|
||
easeInOut: { duration: 0.25, ease: [0.4, 0, 0.2, 1] }
|
||
}
|
||
|
||
// Demo component for showcasing an animation
|
||
function AnimationDemo({
|
||
title,
|
||
description,
|
||
children,
|
||
code
|
||
}: {
|
||
title: string
|
||
description: string
|
||
children: React.ReactNode
|
||
code?: string
|
||
}) {
|
||
const [key, setKey] = useState(0)
|
||
|
||
return (
|
||
<div className="bg-[var(--color-surface-card)] rounded-[var(--radius-xl)] shadow-[var(--shadow-md)] overflow-hidden border border-[var(--color-border-default)]">
|
||
<div className="p-6 border-b border-[var(--color-border-default)]">
|
||
<div className="flex items-center justify-between mb-2">
|
||
<h3 className="text-heading-small">{title}</h3>
|
||
<button
|
||
onClick={() => setKey(k => k + 1)}
|
||
className="p-2 rounded-[var(--radius-md)] bg-[var(--color-background-secondary)] hover:bg-[var(--color-border-default)] transition-colors"
|
||
title="Replay animation"
|
||
>
|
||
<RotateCcw className="w-4 h-4 text-[var(--color-text-secondary)]" />
|
||
</button>
|
||
</div>
|
||
<p className="text-body-small text-[var(--color-text-secondary)]">{description}</p>
|
||
</div>
|
||
|
||
<div className="p-8 bg-[var(--color-background-secondary)] min-h-[160px] flex items-center justify-center">
|
||
<div key={key}>
|
||
{children}
|
||
</div>
|
||
</div>
|
||
|
||
{code && (
|
||
<div className="p-4 bg-[var(--color-background-neutral)] border-t border-[var(--color-border-default)]">
|
||
<pre className="text-body-small font-mono text-[var(--color-text-secondary)] overflow-x-auto">
|
||
{code}
|
||
</pre>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Interactive hover card demo
|
||
function HoverCardDemo() {
|
||
return (
|
||
<motion.div
|
||
className="w-48 h-32 bg-[var(--color-surface-card)] rounded-[var(--radius-xl)] shadow-[var(--shadow-md)] flex items-center justify-center cursor-pointer border border-[var(--color-border-default)]"
|
||
whileHover={{
|
||
scale: 1.05,
|
||
boxShadow: 'var(--shadow-lg)',
|
||
y: -4
|
||
}}
|
||
whileTap={{ scale: 0.98 }}
|
||
transition={transitions.spring}
|
||
>
|
||
<span className="text-body-medium text-[var(--color-text-secondary)]">Hover me</span>
|
||
</motion.div>
|
||
)
|
||
}
|
||
|
||
// Button press demo
|
||
function ButtonPressDemo() {
|
||
return (
|
||
<motion.button
|
||
className="px-6 py-3 bg-[var(--color-accent-primary)] text-[var(--color-text-inverse)] rounded-[var(--radius-md)] font-medium"
|
||
whileHover={{ scale: 1.02 }}
|
||
whileTap={{ scale: 0.95 }}
|
||
transition={transitions.spring}
|
||
>
|
||
Press me
|
||
</motion.button>
|
||
)
|
||
}
|
||
|
||
// Staggered list demo
|
||
function StaggeredListDemo() {
|
||
const items = ['First item', 'Second item', 'Third item', 'Fourth item']
|
||
|
||
const container = {
|
||
hidden: { opacity: 0 },
|
||
show: {
|
||
opacity: 1,
|
||
transition: {
|
||
staggerChildren: 0.1
|
||
}
|
||
}
|
||
}
|
||
|
||
const item = {
|
||
hidden: { opacity: 0, x: -20 },
|
||
show: { opacity: 1, x: 0 }
|
||
}
|
||
|
||
return (
|
||
<motion.ul
|
||
className="space-y-2"
|
||
variants={container}
|
||
initial="hidden"
|
||
animate="show"
|
||
>
|
||
{items.map((text, i) => (
|
||
<motion.li
|
||
key={i}
|
||
variants={item}
|
||
className="px-4 py-2 bg-[var(--color-surface-card)] rounded-[var(--radius-md)] text-body-medium border border-[var(--color-border-default)]"
|
||
>
|
||
{text}
|
||
</motion.li>
|
||
))}
|
||
</motion.ul>
|
||
)
|
||
}
|
||
|
||
// Notification toast demo
|
||
function ToastDemo() {
|
||
const [show, setShow] = useState(true)
|
||
|
||
useEffect(() => {
|
||
if (!show) {
|
||
const timer = setTimeout(() => setShow(true), 500)
|
||
return () => clearTimeout(timer)
|
||
}
|
||
}, [show])
|
||
|
||
return (
|
||
<div className="relative h-20 w-72">
|
||
<AnimatePresence>
|
||
{show && (
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 50, scale: 0.9 }}
|
||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||
exit={{ opacity: 0, y: -20, scale: 0.9 }}
|
||
transition={transitions.spring}
|
||
className="absolute inset-x-0 bottom-0 px-4 py-3 bg-[var(--color-surface-card)] rounded-[var(--radius-lg)] shadow-[var(--shadow-lg)] flex items-center gap-3 border border-[var(--color-border-default)]"
|
||
>
|
||
<div className="w-8 h-8 rounded-full bg-[var(--color-semantic-success-light)] flex items-center justify-center">
|
||
<Check className="w-4 h-4 text-[var(--color-semantic-success)]" />
|
||
</div>
|
||
<div className="flex-1">
|
||
<p className="text-body-small font-medium">Success!</p>
|
||
<p className="text-body-small text-[var(--color-text-tertiary)]">Action completed</p>
|
||
</div>
|
||
<button
|
||
onClick={() => setShow(false)}
|
||
className="p-1 hover:bg-[var(--color-background-secondary)] rounded transition-colors"
|
||
>
|
||
<X className="w-4 h-4 text-[var(--color-text-tertiary)]" />
|
||
</button>
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Modal demo
|
||
function ModalDemo() {
|
||
const [isOpen, setIsOpen] = useState(false)
|
||
|
||
return (
|
||
<div className="relative">
|
||
<Button onClick={() => setIsOpen(true)}>Open Modal</Button>
|
||
|
||
<AnimatePresence>
|
||
{isOpen && (
|
||
<>
|
||
<motion.div
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
exit={{ opacity: 0 }}
|
||
transition={transitions.fast}
|
||
className="fixed inset-0 bg-black/50 z-40"
|
||
onClick={() => setIsOpen(false)}
|
||
/>
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
||
transition={transitions.springSmooth}
|
||
className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-80 p-6 bg-[var(--color-surface-card)] rounded-[var(--radius-2xl)] shadow-[var(--shadow-xl)] z-50"
|
||
>
|
||
<h3 className="text-heading-medium mb-2">Modal Title</h3>
|
||
<p className="text-body-medium text-[var(--color-text-secondary)] mb-4">
|
||
This is a modal dialog with smooth enter/exit animations.
|
||
</p>
|
||
<div className="flex gap-2 justify-end">
|
||
<Button variant="secondary" onClick={() => setIsOpen(false)}>Cancel</Button>
|
||
<Button onClick={() => setIsOpen(false)}>Confirm</Button>
|
||
</div>
|
||
</motion.div>
|
||
</>
|
||
)}
|
||
</AnimatePresence>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Counter animation demo
|
||
function CounterDemo() {
|
||
const [count, setCount] = useState(0)
|
||
|
||
return (
|
||
<div className="flex items-center gap-4">
|
||
<motion.button
|
||
whileHover={{ scale: 1.1 }}
|
||
whileTap={{ scale: 0.9 }}
|
||
onClick={() => setCount(c => c - 1)}
|
||
className="w-10 h-10 rounded-full bg-[var(--color-background-secondary)] flex items-center justify-center border border-[var(--color-border-default)]"
|
||
>
|
||
<Minus className="w-4 h-4" />
|
||
</motion.button>
|
||
|
||
<div className="w-20 text-center overflow-hidden">
|
||
<AnimatePresence mode="popLayout">
|
||
<motion.span
|
||
key={count}
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
exit={{ opacity: 0, y: -20 }}
|
||
transition={transitions.spring}
|
||
className="text-display-medium inline-block"
|
||
>
|
||
{count}
|
||
</motion.span>
|
||
</AnimatePresence>
|
||
</div>
|
||
|
||
<motion.button
|
||
whileHover={{ scale: 1.1 }}
|
||
whileTap={{ scale: 0.9 }}
|
||
onClick={() => setCount(c => c + 1)}
|
||
className="w-10 h-10 rounded-full bg-[var(--color-accent-primary)] text-[var(--color-text-inverse)] flex items-center justify-center"
|
||
>
|
||
<Plus className="w-4 h-4" />
|
||
</motion.button>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Loading spinner demo
|
||
function LoadingDemo() {
|
||
return (
|
||
<div className="flex items-center gap-8">
|
||
{/* Spinning loader */}
|
||
<motion.div
|
||
className="w-8 h-8 border-3 border-[var(--color-border-default)] border-t-[var(--color-accent-primary)] rounded-full"
|
||
animate={{ rotate: 360 }}
|
||
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
|
||
/>
|
||
|
||
{/* Pulsing dots */}
|
||
<div className="flex gap-1">
|
||
{[0, 1, 2].map((i) => (
|
||
<motion.div
|
||
key={i}
|
||
className="w-2 h-2 bg-[var(--color-accent-primary)] rounded-full"
|
||
animate={{ scale: [1, 1.5, 1], opacity: [1, 0.5, 1] }}
|
||
transition={{
|
||
duration: 0.8,
|
||
repeat: Infinity,
|
||
delay: i * 0.15,
|
||
ease: 'easeInOut'
|
||
}}
|
||
/>
|
||
))}
|
||
</div>
|
||
|
||
{/* Bouncing dots */}
|
||
<div className="flex gap-1">
|
||
{[0, 1, 2].map((i) => (
|
||
<motion.div
|
||
key={i}
|
||
className="w-2 h-2 bg-[var(--color-semantic-success)] rounded-full"
|
||
animate={{ y: [0, -8, 0] }}
|
||
transition={{
|
||
duration: 0.5,
|
||
repeat: Infinity,
|
||
delay: i * 0.1,
|
||
ease: 'easeInOut'
|
||
}}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Drag demo
|
||
function DragDemo() {
|
||
return (
|
||
<div className="relative w-64 h-32 bg-[var(--color-background-neutral)] rounded-[var(--radius-lg)] border-2 border-dashed border-[var(--color-border-default)]">
|
||
<motion.div
|
||
drag
|
||
dragConstraints={{ left: 0, right: 176, top: 0, bottom: 64 }}
|
||
dragElastic={0.1}
|
||
whileDrag={{ scale: 1.1, cursor: 'grabbing' }}
|
||
className="absolute w-16 h-16 bg-[var(--color-accent-primary)] rounded-[var(--radius-lg)] flex items-center justify-center cursor-grab shadow-[var(--shadow-md)]"
|
||
>
|
||
<span className="text-white text-body-small">Drag</span>
|
||
</motion.div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Progress animation demo
|
||
function ProgressAnimationDemo() {
|
||
const [progress, setProgress] = useState(0)
|
||
|
||
useEffect(() => {
|
||
const timer = setTimeout(() => {
|
||
setProgress(75)
|
||
}, 300)
|
||
return () => clearTimeout(timer)
|
||
}, [])
|
||
|
||
return (
|
||
<div className="w-64 space-y-4">
|
||
<div className="h-2 bg-[var(--color-border-default)] rounded-full overflow-hidden">
|
||
<motion.div
|
||
className="h-full bg-[var(--color-accent-primary)] rounded-full"
|
||
initial={{ width: 0 }}
|
||
animate={{ width: `${progress}%` }}
|
||
transition={{ duration: 1, ease: 'easeOut' }}
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex justify-between text-body-small text-[var(--color-text-secondary)]">
|
||
<span>Progress</span>
|
||
<motion.span
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
transition={{ delay: 0.5 }}
|
||
>
|
||
{progress}%
|
||
</motion.span>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Icon animation demos
|
||
function IconAnimationsDemo() {
|
||
const [liked, setLiked] = useState(false)
|
||
const [starred, setStarred] = useState(false)
|
||
|
||
return (
|
||
<div className="flex items-center gap-6">
|
||
{/* Heart like animation */}
|
||
<motion.button
|
||
whileTap={{ scale: 0.8 }}
|
||
onClick={() => setLiked(!liked)}
|
||
className="p-3 rounded-full bg-[var(--color-surface-card)] border border-[var(--color-border-default)]"
|
||
>
|
||
<motion.div
|
||
animate={liked ? { scale: [1, 1.3, 1] } : { scale: 1 }}
|
||
transition={{ duration: 0.3 }}
|
||
>
|
||
<Heart
|
||
className={cn(
|
||
'w-6 h-6 transition-colors',
|
||
liked ? 'fill-red-500 text-red-500' : 'text-[var(--color-text-tertiary)]'
|
||
)}
|
||
/>
|
||
</motion.div>
|
||
</motion.button>
|
||
|
||
{/* Star animation */}
|
||
<motion.button
|
||
whileTap={{ scale: 0.8 }}
|
||
onClick={() => setStarred(!starred)}
|
||
className="p-3 rounded-full bg-[var(--color-surface-card)] border border-[var(--color-border-default)]"
|
||
>
|
||
<motion.div
|
||
animate={starred ? { rotate: [0, 72, 144, 216, 288, 360], scale: [1, 1.2, 1] } : { rotate: 0, scale: 1 }}
|
||
transition={{ duration: 0.5 }}
|
||
>
|
||
<Star
|
||
className={cn(
|
||
'w-6 h-6 transition-colors',
|
||
starred ? 'fill-yellow-400 text-yellow-400' : 'text-[var(--color-text-tertiary)]'
|
||
)}
|
||
/>
|
||
</motion.div>
|
||
</motion.button>
|
||
|
||
{/* Continuous sparkle */}
|
||
<motion.div
|
||
animate={{
|
||
rotate: [0, 15, -15, 0],
|
||
scale: [1, 1.1, 1]
|
||
}}
|
||
transition={{
|
||
duration: 2,
|
||
repeat: Infinity,
|
||
ease: 'easeInOut'
|
||
}}
|
||
className="p-3 rounded-full bg-[var(--color-accent-primary-light)]"
|
||
>
|
||
<Sparkles className="w-6 h-6 text-[var(--color-accent-primary)]" />
|
||
</motion.div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Accordion demo
|
||
function AccordionDemo() {
|
||
const [isOpen, setIsOpen] = useState(false)
|
||
|
||
return (
|
||
<div className="w-72 bg-[var(--color-surface-card)] rounded-[var(--radius-lg)] overflow-hidden border border-[var(--color-border-default)]">
|
||
<motion.button
|
||
onClick={() => setIsOpen(!isOpen)}
|
||
className="w-full p-4 flex items-center justify-between text-left"
|
||
>
|
||
<span className="text-heading-small">Accordion Item</span>
|
||
<motion.div
|
||
animate={{ rotate: isOpen ? 180 : 0 }}
|
||
transition={transitions.spring}
|
||
>
|
||
<ChevronLeft className="w-5 h-5 -rotate-90 text-[var(--color-text-tertiary)]" />
|
||
</motion.div>
|
||
</motion.button>
|
||
|
||
<AnimatePresence>
|
||
{isOpen && (
|
||
<motion.div
|
||
initial={{ height: 0, opacity: 0 }}
|
||
animate={{ height: 'auto', opacity: 1 }}
|
||
exit={{ height: 0, opacity: 0 }}
|
||
transition={transitions.springSmooth}
|
||
className="overflow-hidden"
|
||
>
|
||
<div className="p-4 pt-0 text-body-medium text-[var(--color-text-secondary)]">
|
||
This content smoothly animates in and out with height transitions.
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Main Animations Section Component
|
||
function AnimationsSection({ theme, colorTheme }: { theme: 'light' | 'dark'; colorTheme: string }) {
|
||
return (
|
||
<div className="space-y-8">
|
||
{/* Header */}
|
||
<Card>
|
||
<div className="flex items-center gap-3 mb-4">
|
||
<div className="p-2 rounded-[var(--radius-lg)] bg-[var(--color-accent-primary-light)]">
|
||
<Zap className="w-6 h-6 text-[var(--color-accent-primary)]" />
|
||
</div>
|
||
<div>
|
||
<h2 className="text-heading-large">Animation System</h2>
|
||
<p className="text-body-medium text-[var(--color-text-secondary)]">
|
||
Powered by Framer Motion • <strong>{colorTheme}</strong> theme in <strong>{theme}</strong> mode
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-3 gap-4 mt-6">
|
||
<div className="p-4 bg-[var(--color-background-secondary)] rounded-[var(--radius-lg)]">
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-1">Duration Presets</p>
|
||
<p className="text-body-medium">instant (50ms) → slow (400ms)</p>
|
||
</div>
|
||
<div className="p-4 bg-[var(--color-background-secondary)] rounded-[var(--radius-lg)]">
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-1">Easing Functions</p>
|
||
<p className="text-body-medium">spring, easeOut, easeInOut</p>
|
||
</div>
|
||
<div className="p-4 bg-[var(--color-background-secondary)] rounded-[var(--radius-lg)]">
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-1">Interaction Types</p>
|
||
<p className="text-body-medium">hover, tap, drag, gesture</p>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* Basic Transitions */}
|
||
<div>
|
||
<h3 className="text-heading-medium mb-4">Basic Transitions</h3>
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<AnimationDemo
|
||
title="Fade In"
|
||
description="Simple opacity transition for subtle entrances"
|
||
>
|
||
<motion.div
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
transition={transitions.normal}
|
||
className="px-6 py-3 bg-[var(--color-surface-card)] rounded-[var(--radius-lg)] shadow-[var(--shadow-md)] border border-[var(--color-border-default)]"
|
||
>
|
||
<span className="text-body-medium">Faded In</span>
|
||
</motion.div>
|
||
</AnimationDemo>
|
||
|
||
<AnimationDemo
|
||
title="Scale In"
|
||
description="Scale with opacity for modal-like entrances"
|
||
>
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.9 }}
|
||
animate={{ opacity: 1, scale: 1 }}
|
||
transition={transitions.springSmooth}
|
||
className="px-6 py-3 bg-[var(--color-surface-card)] rounded-[var(--radius-lg)] shadow-[var(--shadow-md)] border border-[var(--color-border-default)]"
|
||
>
|
||
<span className="text-body-medium">Scaled In</span>
|
||
</motion.div>
|
||
</AnimationDemo>
|
||
|
||
<AnimationDemo
|
||
title="Slide Up"
|
||
description="Vertical slide for tooltips and dropdowns"
|
||
>
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={transitions.spring}
|
||
className="px-6 py-3 bg-[var(--color-surface-card)] rounded-[var(--radius-lg)] shadow-[var(--shadow-md)] border border-[var(--color-border-default)]"
|
||
>
|
||
<span className="text-body-medium">Slid Up</span>
|
||
</motion.div>
|
||
</AnimationDemo>
|
||
|
||
<AnimationDemo
|
||
title="Spring Pop"
|
||
description="Bouncy spring animation for attention-grabbing elements"
|
||
>
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.5 }}
|
||
animate={{ opacity: 1, scale: 1 }}
|
||
transition={transitions.springBouncy}
|
||
className="px-6 py-3 bg-[var(--color-accent-primary)] text-[var(--color-text-inverse)] rounded-[var(--radius-lg)] shadow-[var(--shadow-md)]"
|
||
>
|
||
<span className="text-body-medium">Popped!</span>
|
||
</motion.div>
|
||
</AnimationDemo>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Interactive Animations */}
|
||
<div>
|
||
<h3 className="text-heading-medium mb-4">Interactive Animations</h3>
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<AnimationDemo
|
||
title="Hover Card"
|
||
description="Scale and elevation change on hover"
|
||
>
|
||
<HoverCardDemo />
|
||
</AnimationDemo>
|
||
|
||
<AnimationDemo
|
||
title="Button Press"
|
||
description="Tactile feedback with scale on tap"
|
||
>
|
||
<ButtonPressDemo />
|
||
</AnimationDemo>
|
||
|
||
<AnimationDemo
|
||
title="Draggable Element"
|
||
description="Constrained drag with elastic boundaries"
|
||
>
|
||
<DragDemo />
|
||
</AnimationDemo>
|
||
|
||
<AnimationDemo
|
||
title="Icon Interactions"
|
||
description="Like, favorite, and animated icons"
|
||
>
|
||
<IconAnimationsDemo />
|
||
</AnimationDemo>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Component Animations */}
|
||
<div>
|
||
<h3 className="text-heading-medium mb-4">Component Animations</h3>
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<AnimationDemo
|
||
title="Staggered List"
|
||
description="Sequential item animation for lists"
|
||
>
|
||
<StaggeredListDemo />
|
||
</AnimationDemo>
|
||
|
||
<AnimationDemo
|
||
title="Toast Notification"
|
||
description="Enter/exit animations for notifications"
|
||
>
|
||
<ToastDemo />
|
||
</AnimationDemo>
|
||
|
||
<AnimationDemo
|
||
title="Modal Dialog"
|
||
description="Overlay + scale animation for modals"
|
||
>
|
||
<ModalDemo />
|
||
</AnimationDemo>
|
||
|
||
<AnimationDemo
|
||
title="Accordion"
|
||
description="Height animation for expandable content"
|
||
>
|
||
<AccordionDemo />
|
||
</AnimationDemo>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Utility Animations */}
|
||
<div>
|
||
<h3 className="text-heading-medium mb-4">Utility Animations</h3>
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<AnimationDemo
|
||
title="Number Counter"
|
||
description="Animated number transitions"
|
||
>
|
||
<CounterDemo />
|
||
</AnimationDemo>
|
||
|
||
<AnimationDemo
|
||
title="Loading States"
|
||
description="Spinner, pulse, and bounce loaders"
|
||
>
|
||
<LoadingDemo />
|
||
</AnimationDemo>
|
||
|
||
<AnimationDemo
|
||
title="Progress Bar"
|
||
description="Animated progress indication"
|
||
>
|
||
<ProgressAnimationDemo />
|
||
</AnimationDemo>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Animation Guidelines */}
|
||
<Card>
|
||
<h2 className="text-heading-large mb-6">Animation Guidelines</h2>
|
||
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<div>
|
||
<h3 className="text-heading-small mb-3 text-[var(--color-semantic-success)]">✓ Do</h3>
|
||
<ul className="space-y-2 text-body-medium text-[var(--color-text-secondary)]">
|
||
<li>• Use animations to provide feedback</li>
|
||
<li>• Keep durations short (150-400ms)</li>
|
||
<li>• Use spring physics for natural feel</li>
|
||
<li>• Animate transforms and opacity (GPU)</li>
|
||
<li>• Respect reduced-motion preferences</li>
|
||
<li>• Use consistent timing across similar elements</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div>
|
||
<h3 className="text-heading-small mb-3 text-[var(--color-semantic-error)]">✗ Don't</h3>
|
||
<ul className="space-y-2 text-body-medium text-[var(--color-text-secondary)]">
|
||
<li>• Animate for decoration's sake</li>
|
||
<li>• Use slow animations that block users</li>
|
||
<li>• Animate layout properties (slow)</li>
|
||
<li>• Create jarring or unexpected motions</li>
|
||
<li>• Overuse bouncy springs</li>
|
||
<li>• Animate critical error states</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-6 p-4 bg-[var(--color-accent-primary-light)] rounded-[var(--radius-lg)]">
|
||
<p className="text-body-medium text-[var(--color-accent-primary)]">
|
||
<strong>Accessibility Note:</strong> Always wrap animations in a check for <code className="font-mono bg-[var(--color-background-secondary)] px-1 rounded">prefers-reduced-motion</code> and provide static alternatives.
|
||
</p>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// ============================================
|
||
// THEMES SECTION
|
||
// ============================================
|
||
|
||
function ThemePreviewCard({
|
||
theme,
|
||
isActive,
|
||
mode,
|
||
onClick
|
||
}: {
|
||
theme: typeof COLOR_THEMES[0]
|
||
isActive: boolean
|
||
mode: 'light' | 'dark'
|
||
onClick: () => void
|
||
}) {
|
||
// Preview colors based on mode
|
||
const bgColor = mode === 'light' ? theme.previewColors.bg : theme.previewColors.darkBg
|
||
const cardColor = mode === 'light' ? '#FFFFFF' : '#1A1A1A'
|
||
const accentColor = mode === 'dark' && theme.previewColors.darkAccent
|
||
? theme.previewColors.darkAccent
|
||
: theme.previewColors.accent
|
||
|
||
return (
|
||
<motion.button
|
||
whileHover={{ scale: 1.02, y: -4 }}
|
||
whileTap={{ scale: 0.98 }}
|
||
onClick={onClick}
|
||
className={cn(
|
||
"relative p-4 rounded-[var(--radius-2xl)] text-left transition-all overflow-hidden",
|
||
isActive
|
||
? "ring-2 ring-[var(--color-accent-primary)] ring-offset-2 ring-offset-[var(--color-background-primary)]"
|
||
: "hover:shadow-[var(--shadow-lg)]"
|
||
)}
|
||
style={{ backgroundColor: bgColor }}
|
||
>
|
||
{/* Mini UI Preview */}
|
||
<div className="space-y-3">
|
||
{/* Mini header */}
|
||
<div
|
||
className="h-8 rounded-[var(--radius-md)] flex items-center px-3 gap-2"
|
||
style={{ backgroundColor: cardColor }}
|
||
>
|
||
<div className="w-2 h-2 rounded-full" style={{ backgroundColor: accentColor }} />
|
||
<div className="w-16 h-2 rounded-full bg-gray-300" />
|
||
</div>
|
||
|
||
{/* Mini cards */}
|
||
<div className="grid grid-cols-2 gap-2">
|
||
<div
|
||
className="h-16 rounded-[var(--radius-md)] p-2"
|
||
style={{ backgroundColor: cardColor }}
|
||
>
|
||
<div className="w-8 h-8 rounded-full mb-1" style={{ backgroundColor: accentColor, opacity: 0.2 }} />
|
||
<div className="w-full h-1.5 rounded-full bg-gray-200" />
|
||
</div>
|
||
<div
|
||
className="h-16 rounded-[var(--radius-md)] p-2"
|
||
style={{ backgroundColor: cardColor }}
|
||
>
|
||
<div className="w-full h-2 rounded-full mb-2" style={{ backgroundColor: accentColor }} />
|
||
<div className="w-3/4 h-1.5 rounded-full bg-gray-200" />
|
||
<div className="w-1/2 h-1.5 rounded-full bg-gray-200 mt-1" />
|
||
</div>
|
||
</div>
|
||
|
||
{/* Mini button */}
|
||
<div
|
||
className="h-6 rounded-full flex items-center justify-center"
|
||
style={{ backgroundColor: accentColor }}
|
||
>
|
||
<div className="w-12 h-1.5 rounded-full bg-white/50" />
|
||
</div>
|
||
</div>
|
||
|
||
{/* Theme info */}
|
||
<div className="mt-4">
|
||
<div className="flex items-center gap-2">
|
||
<h3 className="text-heading-small" style={{ color: mode === 'light' ? '#1A1A2E' : '#F8FAFC' }}>
|
||
{theme.name}
|
||
</h3>
|
||
{isActive && (
|
||
<div className="px-2 py-0.5 rounded-full text-[10px] font-medium" style={{ backgroundColor: accentColor, color: '#FFF' }}>
|
||
Active
|
||
</div>
|
||
)}
|
||
</div>
|
||
<p className="text-body-small mt-1" style={{ color: mode === 'light' ? '#64748B' : '#A1A1B5' }}>
|
||
{theme.description}
|
||
</p>
|
||
</div>
|
||
|
||
{/* Color swatches */}
|
||
<div className="flex gap-1 mt-3">
|
||
<div
|
||
className="w-6 h-6 rounded-full border-2 border-white shadow-sm"
|
||
style={{ backgroundColor: theme.previewColors.bg }}
|
||
/>
|
||
<div
|
||
className="w-6 h-6 rounded-full border-2 border-white shadow-sm"
|
||
style={{ backgroundColor: theme.previewColors.accent }}
|
||
/>
|
||
</div>
|
||
</motion.button>
|
||
)
|
||
}
|
||
|
||
function ThemesSection({
|
||
currentTheme,
|
||
currentMode,
|
||
themes,
|
||
onThemeChange,
|
||
onModeChange
|
||
}: {
|
||
currentTheme: ColorTheme
|
||
currentMode: Mode
|
||
themes: typeof COLOR_THEMES
|
||
onThemeChange: (theme: ColorTheme) => void
|
||
onModeChange: () => void
|
||
}) {
|
||
return (
|
||
<div className="space-y-8">
|
||
{/* Header */}
|
||
<Card>
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-3">
|
||
<div className="p-2 rounded-[var(--radius-lg)] bg-[var(--color-accent-primary-light)]">
|
||
<Sparkles className="w-6 h-6 text-[var(--color-accent-primary)]" />
|
||
</div>
|
||
<div>
|
||
<h2 className="text-heading-large">Theme Gallery</h2>
|
||
<p className="text-body-medium text-[var(--color-text-secondary)]">
|
||
{themes.length} color themes × 2 modes = {themes.length * 2} combinations
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Mode Toggle */}
|
||
<div className="flex items-center gap-3 p-1 bg-[var(--color-background-secondary)] rounded-full">
|
||
<button
|
||
onClick={() => currentMode === 'dark' && onModeChange()}
|
||
className={cn(
|
||
"px-4 py-2 rounded-full text-body-medium font-medium transition-all",
|
||
currentMode === 'light'
|
||
? "bg-[var(--color-surface-card)] shadow-sm"
|
||
: "text-[var(--color-text-secondary)]"
|
||
)}
|
||
>
|
||
<Sun className="w-4 h-4 inline mr-2" />
|
||
Light
|
||
</button>
|
||
<button
|
||
onClick={() => currentMode === 'light' && onModeChange()}
|
||
className={cn(
|
||
"px-4 py-2 rounded-full text-body-medium font-medium transition-all",
|
||
currentMode === 'dark'
|
||
? "bg-[var(--color-surface-card)] shadow-sm"
|
||
: "text-[var(--color-text-secondary)]"
|
||
)}
|
||
>
|
||
<Moon className="w-4 h-4 inline mr-2" />
|
||
Dark
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* Theme Grid */}
|
||
<div>
|
||
<h3 className="text-heading-medium mb-4">Color Themes</h3>
|
||
<div className="grid grid-cols-3 gap-6">
|
||
{themes.map((theme) => (
|
||
<ThemePreviewCard
|
||
key={theme.id}
|
||
theme={theme}
|
||
isActive={currentTheme === theme.id}
|
||
mode={currentMode}
|
||
onClick={() => onThemeChange(theme.id)}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Current Theme Details */}
|
||
<Card>
|
||
<h3 className="text-heading-medium mb-4">Current Theme Colors</h3>
|
||
|
||
<div className="grid grid-cols-4 gap-4">
|
||
<div>
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Background</p>
|
||
<div className="space-y-2">
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-8 h-8 rounded-[var(--radius-md)] bg-[var(--color-background-primary)] border border-[var(--color-border-default)]" />
|
||
<span className="text-body-small">Primary</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-8 h-8 rounded-[var(--radius-md)] bg-[var(--color-background-secondary)] border border-[var(--color-border-default)]" />
|
||
<span className="text-body-small">Secondary</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Surface</p>
|
||
<div className="space-y-2">
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-8 h-8 rounded-[var(--radius-md)] bg-[var(--color-surface-card)] border border-[var(--color-border-default)]" />
|
||
<span className="text-body-small">Card</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-8 h-8 rounded-[var(--radius-md)] bg-[var(--color-surface-elevated)] border border-[var(--color-border-default)]" />
|
||
<span className="text-body-small">Elevated</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Accent</p>
|
||
<div className="space-y-2">
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-8 h-8 rounded-[var(--radius-md)] bg-[var(--color-accent-primary)]" />
|
||
<span className="text-body-small">Primary</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-8 h-8 rounded-[var(--radius-md)] bg-[var(--color-accent-primary-light)] border border-[var(--color-border-default)]" />
|
||
<span className="text-body-small">Light</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Semantic</p>
|
||
<div className="space-y-2">
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-8 h-8 rounded-[var(--radius-md)] bg-[var(--color-semantic-success)]" />
|
||
<span className="text-body-small">Success</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-8 h-8 rounded-[var(--radius-md)] bg-[var(--color-semantic-error)]" />
|
||
<span className="text-body-small">Error</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* Usage Instructions */}
|
||
<Card>
|
||
<h3 className="text-heading-medium mb-4">Using Themes</h3>
|
||
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<div className="p-4 bg-[var(--color-background-secondary)] rounded-[var(--radius-lg)]">
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">HTML Setup</p>
|
||
<pre className="text-body-small font-mono text-[var(--color-text-primary)] overflow-x-auto">
|
||
{`<!-- Color theme -->
|
||
<html data-theme="ocean">
|
||
|
||
<!-- Mode -->
|
||
<html class="dark">
|
||
|
||
<!-- Combined -->
|
||
<html data-theme="neo" class="dark">`}
|
||
</pre>
|
||
</div>
|
||
|
||
<div className="p-4 bg-[var(--color-background-secondary)] rounded-[var(--radius-lg)]">
|
||
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">CSS Variables</p>
|
||
<pre className="text-body-small font-mono text-[var(--color-text-primary)] overflow-x-auto">
|
||
{`/* Use in your CSS */
|
||
background: var(--color-background-primary);
|
||
color: var(--color-text-primary);
|
||
border: 1px solid var(--color-border-default);`}
|
||
</pre>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-4 p-4 bg-[var(--color-accent-primary-light)] rounded-[var(--radius-lg)]">
|
||
<p className="text-body-medium text-[var(--color-accent-primary)]">
|
||
<strong>Tip:</strong> All themes automatically support light and dark modes. Just toggle the <code className="font-mono bg-[var(--color-background-secondary)] px-1 rounded">.dark</code> class!
|
||
</p>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)
|
||
}
|