design system token v0.1

This commit is contained in:
Juan
2026-04-13 15:33:00 -05:00
parent 52b4156653
commit c3215945f2
63 changed files with 11562 additions and 181 deletions

View File

@@ -0,0 +1,86 @@
import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const ctaSectionVariants = cva('py-16 px-6', {
variants: {
variant: {
centered: 'text-center',
'left-aligned': 'text-left',
},
background: {
default: 'bg-background',
muted: 'bg-muted',
accent: 'bg-primary text-primary-foreground',
subtle: 'bg-primary/5',
},
},
defaultVariants: {
variant: 'centered',
background: 'muted',
},
})
interface CTASectionProps
extends React.ComponentProps<'section'>,
VariantProps<typeof ctaSectionVariants> {
heading: React.ReactNode
description?: React.ReactNode
actions?: React.ReactNode
}
function CTASection({
className,
variant,
background,
heading,
description,
actions,
children,
...props
}: CTASectionProps) {
return (
<section
data-slot="cta-section"
className={cn(ctaSectionVariants({ variant, background, className }))}
{...props}
>
<div
className={cn(
'max-w-3xl',
variant === 'centered' && 'mx-auto',
)}
>
<h2 className="font-serif text-2xl sm:text-3xl font-semibold tracking-tight mb-4">
{heading}
</h2>
{description && (
<p
className={cn(
'text-base font-sans mb-8 leading-relaxed',
background === 'accent'
? 'text-primary-foreground/80'
: 'text-muted-foreground',
)}
>
{description}
</p>
)}
{actions && (
<div
className={cn(
'flex flex-wrap gap-4',
variant === 'centered' && 'justify-center',
)}
>
{actions}
</div>
)}
{children}
</div>
</section>
)
}
export { CTASection, ctaSectionVariants }

109
components/ui/footer.tsx Normal file
View File

@@ -0,0 +1,109 @@
import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const footerVariants = cva(
'border-t border-border bg-background font-sans',
{
variants: {
variant: {
minimal: 'py-8',
full: 'py-12',
},
},
defaultVariants: {
variant: 'minimal',
},
},
)
interface FooterLinkGroup {
title: string
links: { label: string; href: string }[]
}
interface FooterProps
extends React.ComponentProps<'footer'>,
VariantProps<typeof footerVariants> {
logo?: React.ReactNode
copyright?: React.ReactNode
linkGroups?: FooterLinkGroup[]
actions?: React.ReactNode
}
function Footer({
className,
variant,
logo,
copyright,
linkGroups,
actions,
children,
...props
}: FooterProps) {
if (variant === 'full' && linkGroups) {
return (
<footer
data-slot="footer"
className={cn(footerVariants({ variant, className }))}
{...props}
>
<div className="container mx-auto px-6">
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 mb-8">
{logo && (
<div className="col-span-2 md:col-span-1">
{logo}
</div>
)}
{linkGroups.map((group) => (
<div key={group.title}>
<h4 className="text-sm font-semibold mb-4">{group.title}</h4>
<ul className="space-y-2">
{group.links.map((link) => (
<li key={link.href}>
<a
href={link.href}
className="text-sm text-muted-foreground hover:text-primary transition-colors"
>
{link.label}
</a>
</li>
))}
</ul>
</div>
))}
</div>
<div className="border-t border-border pt-8 flex flex-col md:flex-row items-center justify-between gap-4">
{copyright && (
<p className="text-sm text-muted-foreground">{copyright}</p>
)}
{actions}
</div>
{children}
</div>
</footer>
)
}
return (
<footer
data-slot="footer"
className={cn(footerVariants({ variant, className }))}
{...props}
>
<div className="container mx-auto px-6">
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
{logo && <div>{logo}</div>}
{copyright && (
<p className="text-sm text-muted-foreground">{copyright}</p>
)}
{actions}
{children}
</div>
</div>
</footer>
)
}
export { Footer, footerVariants }

94
components/ui/hero.tsx Normal file
View File

@@ -0,0 +1,94 @@
import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const heroVariants = cva('py-24 px-6', {
variants: {
variant: {
centered: 'text-center',
'left-aligned': 'text-left',
split: 'text-left',
},
background: {
default: 'bg-background',
muted: 'bg-muted',
accent: 'bg-primary/5',
dark: 'bg-foreground text-background',
},
},
defaultVariants: {
variant: 'centered',
background: 'default',
},
})
interface HeroProps
extends React.ComponentProps<'section'>,
VariantProps<typeof heroVariants> {
heading: React.ReactNode
subheading?: React.ReactNode
actions?: React.ReactNode
media?: React.ReactNode
}
function Hero({
className,
variant,
background,
heading,
subheading,
actions,
media,
children,
...props
}: HeroProps) {
const isSplit = variant === 'split'
return (
<section
data-slot="hero"
className={cn(heroVariants({ variant, background, className }))}
{...props}
>
<div
className={cn(
'max-w-7xl mx-auto',
isSplit && 'grid grid-cols-1 lg:grid-cols-2 gap-12 items-center',
)}
>
<div
className={cn(
variant === 'centered' && 'max-w-3xl mx-auto',
!isSplit && 'max-w-3xl',
)}
>
<h1 className="font-serif text-4xl sm:text-5xl font-semibold tracking-tight mb-6">
{heading}
</h1>
{subheading && (
<p className="text-lg text-muted-foreground font-sans mb-8 leading-relaxed">
{subheading}
</p>
)}
{actions && (
<div
className={cn(
'flex flex-wrap gap-4',
variant === 'centered' && 'justify-center',
)}
>
{actions}
</div>
)}
{children}
</div>
{isSplit && media && (
<div className="flex items-center justify-center">{media}</div>
)}
</div>
</section>
)
}
export { Hero, heroVariants }

92
components/ui/logo.tsx Normal file
View File

@@ -0,0 +1,92 @@
import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const logoVariants = cva('inline-block', {
variants: {
size: {
sm: 'h-6 w-auto',
md: 'h-8 w-auto',
lg: 'h-10 w-auto',
xl: 'h-14 w-auto',
},
variant: {
color: '',
monochrome: '',
},
},
defaultVariants: {
size: 'md',
variant: 'color',
},
})
function Logo({
className,
size,
variant,
...props
}: React.ComponentProps<'svg'> &
VariantProps<typeof logoVariants>) {
return (
<svg
data-slot="logo"
viewBox="0 0 1818 448"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={cn(logoVariants({ size, variant, className }))}
{...props}
>
<g clipPath="url(#greyhaven-logo-clip)">
<path
d="M625.8 313.476L623.156 286.907C614.009 302.244 592.449 317.924 559.067 317.924C504.436 317.924 455.996 277.76 455.996 208.662C455.996 139.564 507.08 99.7111 561.4 99.7111C612.204 99.7111 644.684 128.956 655.884 163.489L622.502 176.182C615.409 152.569 594.751 132.471 561.369 132.471C527.987 132.471 491.991 156.676 491.991 208.662C491.991 260.649 525.062 285.444 561.089 285.444C603.307 285.444 619.267 256.511 621.04 238.498H551.942V207.48H654.422V313.476H625.769H625.8Z"
className={variant === 'monochrome' ? 'fill-foreground' : 'fill-foreground'}
/>
<path
d="M771.649 203.622C767.822 203.031 763.964 202.751 760.418 202.751C733.849 202.751 721.747 218.089 721.747 244.969V313.476H687.493V169.68H720.876V192.702C727.658 177.053 743.618 167.907 762.502 167.907C766.64 167.907 770.187 168.498 771.649 168.778V203.622Z"
className={variant === 'monochrome' ? 'fill-foreground' : 'fill-foreground'}
/>
<path
d="M919.582 272.44C911.898 297.547 889.156 317.924 854.622 317.924C815.64 317.924 781.107 289.582 781.107 240.862C781.107 195.378 814.769 165.262 851.076 165.262C895.378 165.262 921.356 194.507 921.356 239.96C921.356 245.56 920.764 250.289 920.453 250.88H815.329C816.2 272.72 833.342 288.369 854.591 288.369C875.84 288.369 885.889 277.449 890.618 263.262L919.551 272.409L919.582 272.44ZM886.822 225.773C886.231 208.942 875 193.884 851.387 193.884C829.827 193.884 817.444 210.404 816.262 225.773H886.822Z"
className={variant === 'monochrome' ? 'fill-foreground' : 'fill-foreground'}
/>
<path
d="M949.542 371.653L984.107 296.364L922.693 169.68H961.365L1002.71 260.618L1041.38 169.68H1077.69L986.16 371.653H949.542Z"
className={variant === 'monochrome' ? 'fill-foreground' : 'fill-foreground'}
/>
<path
d="M1128.09 313.476H1093.84V99.68H1128.09V183.556C1137.83 170.862 1154.07 165.542 1169.12 165.542C1204.56 165.542 1221.67 190.929 1221.67 222.538V313.476H1187.42V228.418C1187.42 210.684 1179.45 196.529 1157.89 196.529C1139.01 196.529 1128.65 210.684 1128.06 229.009V313.476H1128.09Z"
className={variant === 'monochrome' ? 'fill-foreground' : 'fill-foreground'}
/>
<path
d="M1288.56 231.093L1325.46 225.493C1333.73 224.311 1336.1 220.173 1336.1 215.164C1336.1 203.062 1327.82 193.324 1308.94 193.324C1290.05 193.324 1280.88 204.836 1279.41 219.302L1248.12 212.209C1250.76 187.413 1273.22 165.262 1308.66 165.262C1352.96 165.262 1369.79 190.369 1369.79 218.991V290.453C1369.79 303.458 1371.28 312.013 1371.56 313.476H1339.68C1339.4 312.573 1338.21 306.693 1338.21 295.151C1331.43 306.071 1317.24 317.893 1293.91 317.893C1263.8 317.893 1245.19 297.236 1245.19 274.493C1245.19 248.796 1264.08 234.64 1288.59 231.093H1288.56ZM1336.1 253.836V247.333L1298.61 252.933C1287.97 254.707 1279.41 260.618 1279.41 272.44C1279.41 282.178 1286.79 291.044 1300.38 291.044C1319.58 291.044 1336.1 281.898 1336.1 253.836Z"
className={variant === 'monochrome' ? 'fill-foreground' : 'fill-foreground'}
/>
<path
d="M1465.52 313.476H1431.27L1372.81 169.68H1410.61L1448.69 272.44L1485.9 169.68H1521.92L1465.52 313.476Z"
className={variant === 'monochrome' ? 'fill-foreground' : 'fill-foreground'}
/>
<path
d="M1663.08 272.44C1655.39 297.547 1632.65 317.924 1598.12 317.924C1559.13 317.924 1524.6 289.582 1524.6 240.862C1524.6 195.378 1558.26 165.262 1594.57 165.262C1638.87 165.262 1664.85 194.507 1664.85 239.96C1664.85 245.56 1664.26 250.289 1663.95 250.88H1558.82C1559.69 272.72 1576.84 288.369 1598.08 288.369C1619.33 288.369 1629.38 277.449 1634.11 263.262L1663.04 272.409L1663.08 272.44ZM1630.28 225.773C1629.69 208.942 1618.46 193.884 1594.85 193.884C1573.29 193.884 1560.91 210.404 1559.72 225.773H1630.28Z"
className={variant === 'monochrome' ? 'fill-foreground' : 'fill-foreground'}
/>
<path
d="M1724.12 313.476H1689.86V169.68H1723.24V188.876C1732.7 172.356 1749.81 165.542 1765.77 165.542C1800.9 165.542 1817.73 190.929 1817.73 222.538V313.476H1783.48V228.418C1783.48 210.684 1775.51 196.529 1753.95 196.529C1734.48 196.529 1724.12 211.587 1724.12 230.471V313.444V313.476Z"
className={variant === 'monochrome' ? 'fill-foreground' : 'fill-foreground'}
/>
<path
d="M345.582 100.551L287.498 66.4533H284.356L232.462 96.9111V34.0978L174.378 0H171.236L113.151 34.0978V96.9111L61.2578 66.4533H58.1156L0 100.551V102.791V347.013L58.0844 381.609H61.2578L113.12 350.747V413.467L171.204 448.062H174.378L232.462 413.467V350.747L284.324 381.609H287.498L345.582 347.013V100.551ZM59.6711 72.7378L111.627 103.258L59.6711 134.182L7.71556 103.258L59.6711 72.7378ZM6.22222 109.604L56.56 139.564V308.436L6.22222 337.991V109.604ZM56.56 315.653V373.427L7.71556 344.338L56.56 315.653ZM62.7822 373.427V315.653L111.627 344.338L62.7822 373.427ZM113.12 337.991L62.7822 308.436V139.564L113.12 109.604V337.991ZM226.24 102.791V163.333L175.902 133.778V73.1422L226.24 43.1822V102.791ZM172.791 200.604L120.836 169.68L172.791 139.16L224.747 169.68L172.791 200.604ZM175.902 65.8933V8.12L224.747 36.8044L175.902 65.8933ZM169.68 8.12V65.8933L120.836 36.8044L169.68 8.12ZM119.342 43.1511L169.68 73.1111V133.747L119.342 163.302V43.1511ZM119.342 176.027L169.68 205.987V374.858L119.342 404.413V176.027ZM169.68 382.076V439.849L120.836 410.76L169.68 382.076ZM175.902 439.849V382.076L224.747 410.76L175.902 439.849ZM226.24 404.413L175.902 374.858V205.987L226.24 176.027V404.413ZM289.022 74.5733L337.867 103.258L289.022 132.347V74.5733ZM282.8 74.5733V132.347L233.956 103.258L282.8 74.5733ZM232.462 109.604L282.8 139.564V308.436L232.462 337.991V109.604ZM285.911 375.293L233.956 344.338L285.911 313.818L337.867 344.338L285.911 375.293ZM339.36 337.991L289.022 308.436V139.564L339.36 109.604V337.991Z"
className={variant === 'monochrome' ? 'fill-foreground' : 'fill-primary'}
/>
</g>
<defs>
<clipPath id="greyhaven-logo-clip">
<rect width="1817.73" height="448" fill="white" />
</clipPath>
</defs>
</svg>
)
}
export { Logo, logoVariants }

131
components/ui/navbar.tsx Normal file
View File

@@ -0,0 +1,131 @@
import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { MenuIcon, XIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
const navbarVariants = cva(
'fixed top-0 left-0 right-0 z-50 h-16 font-sans',
{
variants: {
variant: {
solid: 'bg-background border-b border-border',
transparent: 'bg-transparent',
minimal: 'bg-background/80 backdrop-blur-sm border-b border-border/50',
},
},
defaultVariants: {
variant: 'solid',
},
},
)
interface NavbarProps
extends React.ComponentProps<'header'>,
VariantProps<typeof navbarVariants> {
logo?: React.ReactNode
actions?: React.ReactNode
}
function Navbar({
className,
variant,
logo,
actions,
children,
...props
}: NavbarProps) {
const [mobileOpen, setMobileOpen] = React.useState(false)
return (
<header
data-slot="navbar"
className={cn(navbarVariants({ variant, className }))}
{...props}
>
<div className="container mx-auto px-6 h-full flex items-center justify-between">
{/* Logo slot — left */}
{logo && (
<div data-slot="navbar-logo" className="flex-shrink-0">
{logo}
</div>
)}
{/* Desktop nav — center */}
<nav
data-slot="navbar-nav"
className="hidden md:flex items-center gap-1 text-sm font-medium"
>
{children}
</nav>
{/* Actions slot — right */}
<div className="flex items-center gap-2">
{actions && (
<div
data-slot="navbar-actions"
className="hidden md:flex items-center gap-2"
>
{actions}
</div>
)}
{/* Mobile menu toggle */}
<Button
variant="ghost"
size="icon"
className="md:hidden"
onClick={() => setMobileOpen(!mobileOpen)}
aria-label={mobileOpen ? 'Close menu' : 'Open menu'}
aria-expanded={mobileOpen}
>
{mobileOpen ? (
<XIcon className="size-5" />
) : (
<MenuIcon className="size-5" />
)}
</Button>
</div>
</div>
{/* Mobile nav */}
{mobileOpen && (
<div
data-slot="navbar-mobile"
className="md:hidden border-b border-border bg-background"
>
<nav className="container mx-auto px-6 py-4 flex flex-col gap-2 text-sm font-medium">
{children}
</nav>
{actions && (
<div className="container mx-auto px-6 pb-4 flex flex-col gap-2">
{actions}
</div>
)}
</div>
)}
</header>
)
}
function NavbarLink({
className,
active,
...props
}: React.ComponentProps<'a'> & { active?: boolean }) {
return (
<a
data-slot="navbar-link"
data-active={active || undefined}
className={cn(
'px-3 py-2 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors',
'data-[active]:text-foreground data-[active]:font-semibold',
className,
)}
{...props}
/>
)
}
export { Navbar, NavbarLink, navbarVariants }

View File

@@ -0,0 +1,52 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
interface PageLayoutProps extends React.ComponentProps<'div'> {
navbar?: React.ReactNode
sidebar?: React.ReactNode
footer?: React.ReactNode
}
function PageLayout({
className,
navbar,
sidebar,
footer,
children,
...props
}: PageLayoutProps) {
return (
<div
data-slot="page-layout"
className={cn('min-h-screen flex flex-col bg-background text-foreground', className)}
{...props}
>
{navbar}
<div
className={cn(
'flex flex-1',
navbar && 'pt-16', // offset for fixed navbar
)}
>
{sidebar && (
<aside
data-slot="page-layout-sidebar"
className="hidden lg:block w-64 border-r border-border bg-background flex-shrink-0"
>
{sidebar}
</aside>
)}
<main
data-slot="page-layout-main"
className="flex-1 min-w-0"
>
{children}
</main>
</div>
{footer}
</div>
)
}
export { PageLayout }

69
components/ui/section.tsx Normal file
View File

@@ -0,0 +1,69 @@
import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const sectionVariants = cva('py-16', {
variants: {
variant: {
default: '',
highlighted: 'bg-muted',
accent: 'bg-primary/5',
},
width: {
narrow: 'max-w-3xl mx-auto',
default: 'max-w-5xl mx-auto',
wide: 'max-w-7xl mx-auto',
full: 'w-full',
},
},
defaultVariants: {
variant: 'default',
width: 'default',
},
})
interface SectionProps
extends React.ComponentProps<'section'>,
VariantProps<typeof sectionVariants> {
title?: string
description?: string
}
function Section({
className,
variant,
width,
title,
description,
children,
...props
}: SectionProps) {
return (
<section
data-slot="section"
className={cn(sectionVariants({ variant, width, className }))}
{...props}
>
<div className="px-6">
{(title || description) && (
<div className="mb-8">
{title && (
<h2 className="font-serif text-3xl font-semibold tracking-tight mb-3">
{title}
</h2>
)}
{description && (
<p className="text-muted-foreground font-sans text-base max-w-2xl">
{description}
</p>
)}
</div>
)}
{children}
</div>
</section>
)
}
export { Section, sectionVariants }

View File

@@ -1,14 +1,14 @@
'use client'
import { useTheme } from 'next-themes'
import { useTheme } from '@/components/theme-provider'
import { Toaster as Sonner, ToasterProps } from 'sonner'
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = 'system' } = useTheme()
const { resolvedTheme } = useTheme()
return (
<Sonner
theme={theme as ToasterProps['theme']}
theme={resolvedTheme as ToasterProps['theme']}
className="toaster group"
style={
{