6967f3c7cd
- Grouped component imports into categories for better organization. - Updated the layout of the index page to include a gallery structure. - Added new sections for Button, Badge, Selection Controls, TextField, Select, Numeric Stepper, Avatar, Loading Bar, List Item, Link, Tabs, Breadcrumb, Pagination, Notification, Tooltip, Card, Navbar, Sidebar, Modal, and Icons. - Improved styling for better visual hierarchy and spacing. - Implemented a theme toggle feature with persistent state using localStorage.
1145 lines
35 KiB
Plaintext
1145 lines
35 KiB
Plaintext
---
|
||
import Layout from "../layouts/Layout.astro";
|
||
|
||
import Button from "../components/Button/button.astro";
|
||
import Badge from "../components/Badge/badge.astro";
|
||
import Avatar from "../components/Avatar/avatar.astro";
|
||
import Toggle from "../components/Toggle/toggle.astro";
|
||
import Checkbox from "../components/Checkbox/checkbox.astro";
|
||
import Radio from "../components/Radio/radio.astro";
|
||
import TextField from "../components/textField/textField.astro";
|
||
import Select from "../components/Select/select.astro";
|
||
import SelectOption from "../components/Select/selectOption.astro";
|
||
import NumericStepper from "../components/numericStepper/numericStepper.astro";
|
||
import Tab from "../components/Tabs/tab.astro";
|
||
import TabItem from "../components/Tabs/tabItem.astro";
|
||
import TabContent from "../components/Tabs/tabContent.astro";
|
||
import Link from "../components/Link/link.astro";
|
||
import Breadcrumb from "../components/Breadcrumb/breadcrumb.astro";
|
||
import BreadcrumbItem from "../components/Breadcrumb/breadcrumbItem.astro";
|
||
import Pagination from "../components/pagination/pagination.astro";
|
||
import PaginationNumber from "../components/pagination/paginationNumber.astro";
|
||
import Navbar from "../components/Navbar/navbar.astro";
|
||
import Sidebar from "../components/Sidebar/sidebar.astro";
|
||
import SidebarItem from "../components/Sidebar/sidebarItem.astro";
|
||
import Card from "../components/Card/card.astro";
|
||
import ListItem from "../components/ListItem/listItem.astro";
|
||
import ListItemTitle from "../components/ListItem/listItemTitle.astro";
|
||
import ListItemSubtitle from "../components/ListItem/listItemSubtitle.astro";
|
||
import Notification from "../components/Notifications/notification.astro";
|
||
import LoadingBar from "../components/LoadingBar/loadingBar.astro";
|
||
import Tooltip from "../components/Tooltip/tooltip.astro";
|
||
import Modal from "../components/Modal/modal.astro";
|
||
|
||
import {
|
||
Arrow2Icon,
|
||
BinIcon,
|
||
BurgerIcon,
|
||
CalendarIcon,
|
||
CheckIcon,
|
||
CloseIcon,
|
||
CodeIcon,
|
||
CubeIcon,
|
||
DownloadIcon,
|
||
FilterIcon,
|
||
HelpIcon,
|
||
HomeIcon,
|
||
LinkIcon,
|
||
MinusIcon,
|
||
MoreIcon,
|
||
OverviewIcon,
|
||
PlusIcon,
|
||
ProfileIcon,
|
||
SearchIcon,
|
||
SettingsIcon,
|
||
ShareIcon,
|
||
ShieldIcon,
|
||
SortIcon,
|
||
StatsIcon,
|
||
UploadIcon,
|
||
} from "../components/Icons/index.ts";
|
||
|
||
const tones = [
|
||
"primary",
|
||
"neutral",
|
||
"success",
|
||
"warning",
|
||
"error",
|
||
"info",
|
||
] as const;
|
||
const btnTypes = ["primary", "secondary", "ghost", "danger"] as const;
|
||
const sizes = ["sm", "md", "lg"] as const;
|
||
const notifTypes = ["info", "success", "warning", "error"] as const;
|
||
const notifMessages: Record<string, string> = {
|
||
info: "Information about the operation.",
|
||
success: "The operation completed successfully.",
|
||
warning: "A warning occurred. Please check the details.",
|
||
error: "An error occurred. Please try again.",
|
||
};
|
||
const tooltipPositions = ["top", "bottom", "left", "right"] as const;
|
||
const iconLib = [
|
||
{ name: "Arrow2", Icon: Arrow2Icon },
|
||
{ name: "Bin", Icon: BinIcon },
|
||
{ name: "Burger", Icon: BurgerIcon },
|
||
{ name: "Calendar", Icon: CalendarIcon },
|
||
{ name: "Check", Icon: CheckIcon },
|
||
{ name: "Close", Icon: CloseIcon },
|
||
{ name: "Code", Icon: CodeIcon },
|
||
{ name: "Cube", Icon: CubeIcon },
|
||
{ name: "Download", Icon: DownloadIcon },
|
||
{ name: "Filter", Icon: FilterIcon },
|
||
{ name: "Help", Icon: HelpIcon },
|
||
{ name: "Home", Icon: HomeIcon },
|
||
{ name: "Link", Icon: LinkIcon },
|
||
{ name: "Minus", Icon: MinusIcon },
|
||
{ name: "More", Icon: MoreIcon },
|
||
{ name: "Overview", Icon: OverviewIcon },
|
||
{ name: "Plus", Icon: PlusIcon },
|
||
{ name: "Profile", Icon: ProfileIcon },
|
||
{ name: "Search", Icon: SearchIcon },
|
||
{ name: "Settings", Icon: SettingsIcon },
|
||
{ name: "Share", Icon: ShareIcon },
|
||
{ name: "Shield", Icon: ShieldIcon },
|
||
{ name: "Sort", Icon: SortIcon },
|
||
{ name: "Stats", Icon: StatsIcon },
|
||
{ name: "Upload", Icon: UploadIcon },
|
||
] as const;
|
||
|
||
const tokenColors = [
|
||
"text",
|
||
"neutral",
|
||
"background",
|
||
"surface",
|
||
"surface-2",
|
||
"border",
|
||
"primary",
|
||
"secondary",
|
||
"accent",
|
||
"primary-soft",
|
||
"success-medium",
|
||
"warning-medium",
|
||
"error-medium",
|
||
"info-medium",
|
||
];
|
||
const spacingScale = [
|
||
["3xs", "2"],
|
||
["2xs", "4"],
|
||
["xs", "8"],
|
||
["sm", "12"],
|
||
["md", "16"],
|
||
["lg", "24"],
|
||
["xl", "32"],
|
||
["2xl", "48"],
|
||
["3xl", "64"],
|
||
];
|
||
const radiusScale = ["xs", "sm", "md", "lg", "xl", "2xl", "full"];
|
||
---
|
||
|
||
<Layout title="Nova Design System — Gallery">
|
||
<div class="g-page">
|
||
<header class="g-topbar">
|
||
<div class="g-brand">
|
||
<span class="g-mark"></span>
|
||
<span class="g-name">Nova Design System</span>
|
||
<Badge type="primary" variant="soft">v0.06</Badge>
|
||
</div>
|
||
<Button type="secondary" size="sm" id="theme-btn">Toggle theme</Button>
|
||
</header>
|
||
|
||
<div class="g-layout">
|
||
<nav class="g-rail">
|
||
<p class="g-rail__head">Nova DS</p>
|
||
<a href="#foundations">Foundations</a>
|
||
<a href="#buttons">Button</a>
|
||
<a href="#badges">Badge</a>
|
||
<a href="#controls">Toggle · Check · Radio</a>
|
||
<a href="#textfield">TextField</a>
|
||
<a href="#select">Select</a>
|
||
<a href="#stepper">Numeric stepper</a>
|
||
<a href="#avatar">Avatar</a>
|
||
<a href="#loading">Loading bar</a>
|
||
<a href="#listitem">List item</a>
|
||
<a href="#link">Link</a>
|
||
<a href="#tabs">Tabs</a>
|
||
<a href="#breadcrumb">Breadcrumb</a>
|
||
<a href="#pagination">Pagination</a>
|
||
<a href="#notification">Notification</a>
|
||
<a href="#tooltip">Tooltip</a>
|
||
<a href="#card">Card</a>
|
||
<a href="#chrome">Navbar · Sidebar</a>
|
||
<a href="#modal">Modal</a>
|
||
<a href="#icons">Icons</a>
|
||
</nav>
|
||
|
||
<main class="g-content">
|
||
<!-- ── FOUNDATIONS ── -->
|
||
<section id="foundations" class="blk">
|
||
<h2>Foundations</h2>
|
||
<p class="blk__desc">
|
||
Design tokens — type, color, spacing, radius, elevation.
|
||
</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl"
|
||
>Type scale — Intel One Mono (display) · Geist (UI)</span
|
||
>
|
||
<div class="vg__body type-scale">
|
||
<div class="type-row">
|
||
<span class="type-tag">text-5xl</span><span class="disp t5xl"
|
||
>Nova 52px</span>
|
||
</div>
|
||
<div class="type-row">
|
||
<span class="type-tag">text-4xl</span><span class="disp t4xl"
|
||
>Nova 40px</span>
|
||
</div>
|
||
<div class="type-row">
|
||
<span class="type-tag">text-3xl</span><span class="disp t3xl"
|
||
>Nova 30px</span>
|
||
</div>
|
||
<div class="type-row">
|
||
<span class="type-tag">text-2xl</span><span class="disp t2xl"
|
||
>Nova 22px</span>
|
||
</div>
|
||
<div class="type-row">
|
||
<span class="type-tag">text-xl</span><span class="disp txl"
|
||
>Nova 18px</span>
|
||
</div>
|
||
<div class="type-row">
|
||
<span class="type-tag">text-lg</span><span class="sans tlg"
|
||
>Nova 16px</span>
|
||
</div>
|
||
<div class="type-row">
|
||
<span class="type-tag">text-base</span><span class="sans tbase"
|
||
>Nova 14px</span>
|
||
</div>
|
||
<div class="type-row">
|
||
<span class="type-tag">text-label</span><span
|
||
class="sans tlabel">Nova 13px 600</span>
|
||
</div>
|
||
<div class="type-row">
|
||
<span class="type-tag">text-sm</span><span class="sans tsm"
|
||
>Nova 12px</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Color tokens (adapt to theme)</span>
|
||
<div class="vg__body swatch-row">
|
||
{
|
||
tokenColors.map((t) => (
|
||
<div class="swatch">
|
||
<div
|
||
class="swatch-chip"
|
||
style={`background:var(--nds-${t})`}
|
||
/>
|
||
<span class="swatch-name">{t}</span>
|
||
</div>
|
||
))
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Spacing scale</span>
|
||
<div
|
||
class="vg__body"
|
||
style="flex-direction:column;align-items:flex-start;gap:8px;"
|
||
>
|
||
{
|
||
spacingScale.map(([n, px]) => (
|
||
<div class="sp-row">
|
||
<span class="sp-label">
|
||
spacing-{n} · {px}px
|
||
</span>
|
||
<div class="sp-bar" style={`width:${px}px`} />
|
||
</div>
|
||
))
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Radius scale</span>
|
||
<div class="vg__body">
|
||
{
|
||
radiusScale.map((n) => (
|
||
<div
|
||
class="rad-chip"
|
||
style={`border-radius:var(--nds-radius-${n})`}
|
||
>
|
||
<span>{n}</span>
|
||
</div>
|
||
))
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Elevation</span>
|
||
<div class="vg__body">
|
||
{
|
||
(["sm", "md", "lg"] as const).map((n) => (
|
||
<div
|
||
class="elev-chip"
|
||
style={`box-shadow:var(--nds-shadow-${n})`}
|
||
>
|
||
shadow-{n}
|
||
</div>
|
||
))
|
||
}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── BUTTON ── -->
|
||
<section id="buttons" class="blk">
|
||
<h2>Button</h2>
|
||
<p class="blk__desc">Variants, sizes, icon and disabled states.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Variants × md</span>
|
||
<div class="vg__body">
|
||
{
|
||
btnTypes.map((t) => (
|
||
<Button type={t} size="md">
|
||
{t[0].toUpperCase() + t.slice(1)}
|
||
</Button>
|
||
))
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Sizes (primary)</span>
|
||
<div class="vg__body">
|
||
{
|
||
sizes.map((s) => (
|
||
<Button type="primary" size={s}>
|
||
Size {s}
|
||
</Button>
|
||
))
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">With icon · icon-only</span>
|
||
<div class="vg__body">
|
||
<Button type="primary" size="md"
|
||
><PlusIcon slot="icon-left" size={16} />New service</Button
|
||
>
|
||
<Button type="secondary" size="md"
|
||
>Filter<FilterIcon slot="icon-right" size={16} /></Button
|
||
>
|
||
<Button type="ghost" size="md" icon={true}
|
||
><MoreIcon size={18} /></Button
|
||
>
|
||
<Button type="primary" size="md" icon={true}
|
||
><DownloadIcon size={18} /></Button
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Disabled</span>
|
||
<div class="vg__body">
|
||
{
|
||
btnTypes.map((t) => (
|
||
<Button type={t} size="md" disabled>
|
||
{t}
|
||
</Button>
|
||
))
|
||
}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── BADGE ── -->
|
||
<section id="badges" class="blk">
|
||
<h2>Badge</h2>
|
||
<p class="blk__desc">Tones × soft / solid.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Soft</span>
|
||
<div class="vg__body">
|
||
{
|
||
tones.map((t) => (
|
||
<Badge type={t} variant="soft">
|
||
{t}
|
||
</Badge>
|
||
))
|
||
}
|
||
</div>
|
||
</div>
|
||
<div class="vg">
|
||
<span class="vg__lbl">Solid</span>
|
||
<div class="vg__body">
|
||
{
|
||
tones.map((t) => (
|
||
<Badge type={t} variant="solid">
|
||
{t}
|
||
</Badge>
|
||
))
|
||
}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── CONTROLS ── -->
|
||
<section id="controls" class="blk">
|
||
<h2>Toggle · Checkbox · Radio</h2>
|
||
<p class="blk__desc">Selection controls in every state.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Toggle — off / on</span>
|
||
<div class="vg__body">
|
||
<Toggle id="tg-off" />
|
||
<Toggle id="tg-on" data-checked={true} />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl"
|
||
>Checkbox — unchecked / checked / disabled</span
|
||
>
|
||
<div class="vg__body">
|
||
<Checkbox id="cb1">Unchecked</Checkbox>
|
||
<Checkbox id="cb2" checked>Checked</Checkbox>
|
||
<Checkbox id="cb3" checked disabled>Disabled</Checkbox>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Radio group</span>
|
||
<div
|
||
class="vg__body"
|
||
style="flex-direction:column;align-items:flex-start;"
|
||
>
|
||
<Radio id="rb1" name="plan" checked>On-demand</Radio>
|
||
<Radio id="rb2" name="plan">Reserved</Radio>
|
||
<Radio id="rb3" name="plan" disabled>Spot</Radio>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── TEXTFIELD ── -->
|
||
<section id="textfield" class="blk">
|
||
<h2>TextField</h2>
|
||
<p class="blk__desc">Text, email, password, textarea.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Fields</span>
|
||
<div
|
||
class="vg__body"
|
||
style="flex-direction:column;align-items:stretch;max-width:380px;"
|
||
>
|
||
<TextField label="Service name" placeholder="api-gateway" />
|
||
<TextField
|
||
label="Email"
|
||
type="email"
|
||
placeholder="you@nova.dev"
|
||
/>
|
||
<TextField
|
||
label="Password"
|
||
type="password"
|
||
placeholder="••••••••"
|
||
/>
|
||
<TextField
|
||
label="Message"
|
||
type="textarea"
|
||
placeholder="Type here…"
|
||
max={120}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── SELECT ── -->
|
||
<section id="select" class="blk">
|
||
<h2>Select</h2>
|
||
<p class="blk__desc">Default and disabled.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Select</span>
|
||
<div class="vg__body">
|
||
<Select default="option3">
|
||
<SelectOption value="option1">Option 1</SelectOption>
|
||
<SelectOption value="option2">Option 2</SelectOption>
|
||
<SelectOption value="option3">Option 3</SelectOption>
|
||
<SelectOption value="option4">Option 4</SelectOption>
|
||
<SelectOption value="option5" disabled>Option 5</SelectOption>
|
||
</Select>
|
||
<Select disabled>
|
||
<SelectOption value="d">Disabled</SelectOption>
|
||
</Select>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── NUMERIC STEPPER ── -->
|
||
<section id="stepper" class="blk">
|
||
<h2>Numeric stepper</h2>
|
||
<p class="blk__desc">Increment / decrement control.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Stepper</span>
|
||
<div class="vg__body">
|
||
<NumericStepper min={-15} max={10} value={5} step={5} id="ns1" />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── AVATAR ── -->
|
||
<section id="avatar" class="blk">
|
||
<h2>Avatar</h2>
|
||
<p class="blk__desc">Photo and initials.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Types</span>
|
||
<div class="vg__body">
|
||
<Avatar
|
||
type="photo"
|
||
name="John Doe"
|
||
image="https://i.pravatar.cc/80?img=12"
|
||
/>
|
||
<Avatar type="initials" name="Ava Stone" />
|
||
<Avatar type="initials" name="Jane Smith" />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── LOADING BAR ── -->
|
||
<section id="loading" class="blk">
|
||
<h2>Loading bar</h2>
|
||
<p class="blk__desc">Determinate and indeterminate.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Known 75% · indeterminate</span>
|
||
<div
|
||
class="vg__body"
|
||
style="flex-direction:column;align-items:stretch;width:100%"
|
||
>
|
||
<LoadingBar type="known" percentage={75} width="100%" />
|
||
<LoadingBar type="unknown" width="100%" />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── LIST ITEM ── -->
|
||
<section id="listitem" class="blk">
|
||
<h2>List item</h2>
|
||
<p class="blk__desc">Title and subtitle rows.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">List</span>
|
||
<div
|
||
class="vg__body"
|
||
style="flex-direction:column;align-items:stretch;padding:0;gap:0;"
|
||
>
|
||
<ListItem>
|
||
<ListItemTitle>api-gateway</ListItemTitle>
|
||
<ListItemSubtitle>Edge routing · eu-west-1</ListItemSubtitle>
|
||
</ListItem>
|
||
<ListItem>
|
||
<ListItemTitle>postgres-main</ListItemTitle>
|
||
<ListItemSubtitle>Database · eu-west-1</ListItemSubtitle>
|
||
</ListItem>
|
||
<ListItem>
|
||
<ListItemTitle>redis-cache</ListItemTitle>
|
||
<ListItemSubtitle>Cache · eu-west-1</ListItemSubtitle>
|
||
</ListItem>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── LINK ── -->
|
||
<section id="link" class="blk">
|
||
<h2>Link</h2>
|
||
<p class="blk__desc">Inline and external.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Links</span>
|
||
<div class="vg__body">
|
||
<Link url="https://example.com">Documentation</Link>
|
||
<Link url="https://example.com" blank={true}
|
||
>Open dashboard ↗</Link
|
||
>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── TABS ── -->
|
||
<section id="tabs" class="blk">
|
||
<h2>Tabs</h2>
|
||
<p class="blk__desc">Underline tabs with panels (interactive).</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Tabs + content</span>
|
||
<div
|
||
class="vg__body"
|
||
style="flex-direction:column;align-items:stretch;"
|
||
>
|
||
<Tab id="g-tab" defaultActive="images">
|
||
<TabItem id="images">Images</TabItem>
|
||
<TabItem id="videos">Videos</TabItem>
|
||
<TabItem id="docs">Documents</TabItem>
|
||
</Tab>
|
||
<TabContent id="g-tab" itemId="images"
|
||
><p>🖼️ Image gallery content.</p></TabContent
|
||
>
|
||
<TabContent id="g-tab" itemId="videos"
|
||
><p>🎬 Video player here.</p></TabContent
|
||
>
|
||
<TabContent id="g-tab" itemId="docs"
|
||
><p>📄 Document list here.</p></TabContent
|
||
>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── BREADCRUMB ── -->
|
||
<section id="breadcrumb" class="blk">
|
||
<h2>Breadcrumb</h2>
|
||
<p class="blk__desc">Hierarchical navigation.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Breadcrumb</span>
|
||
<div class="vg__body">
|
||
<Breadcrumb>
|
||
<BreadcrumbItem href="/">Home</BreadcrumbItem>
|
||
<BreadcrumbItem href="/services">Services</BreadcrumbItem>
|
||
<BreadcrumbItem current>api-gateway</BreadcrumbItem>
|
||
</Breadcrumb>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── PAGINATION ── -->
|
||
<section id="pagination" class="blk">
|
||
<h2>Pagination</h2>
|
||
<p class="blk__desc">Page numbers with disabled edges.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Pagination</span>
|
||
<div class="vg__body">
|
||
<Pagination defaultPage={3}>
|
||
<PaginationNumber>1</PaginationNumber>
|
||
<PaginationNumber>2</PaginationNumber>
|
||
<PaginationNumber>3</PaginationNumber>
|
||
<PaginationNumber>4</PaginationNumber>
|
||
<PaginationNumber disabled>5</PaginationNumber>
|
||
</Pagination>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── NOTIFICATION ── -->
|
||
<section id="notification" class="blk">
|
||
<h2>Notification</h2>
|
||
<p class="blk__desc">Info, success, warning and error.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">All types</span>
|
||
<div
|
||
class="vg__body"
|
||
style="flex-direction:column;align-items:stretch;max-width:460px;"
|
||
>
|
||
{
|
||
notifTypes.map((t) => (
|
||
<Notification type={t}>{notifMessages[t]}</Notification>
|
||
))
|
||
}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── TOOLTIP ── -->
|
||
<section id="tooltip" class="blk">
|
||
<h2>Tooltip</h2>
|
||
<p class="blk__desc">Four positions — hover the buttons.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Positions</span>
|
||
<div class="vg__body">
|
||
{
|
||
tooltipPositions.map((pos) => (
|
||
<Tooltip label={`Tooltip ${pos}`} position={pos}>
|
||
<Button type="secondary" size="md">
|
||
{pos[0].toUpperCase() + pos.slice(1)}
|
||
</Button>
|
||
</Tooltip>
|
||
))
|
||
}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── CARD ── -->
|
||
<section id="card" class="blk">
|
||
<h2>Card</h2>
|
||
<p class="blk__desc">With cover, plain and with footer actions.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Cover · plain · with footer</span>
|
||
<div class="vg__body" style="align-items:flex-start;">
|
||
<Card
|
||
title="api-gateway"
|
||
subtitle="Edge routing · 99.98% uptime"
|
||
cover="/peak.jpg"
|
||
>
|
||
<div slot="footer">
|
||
<Badge type="success" variant="soft">Live</Badge>
|
||
</div>
|
||
</Card>
|
||
<Card
|
||
title="Quick note"
|
||
subtitle="A plain card without a cover image."
|
||
/>
|
||
<Card title="Deploy" subtitle="Ship build 4821 to production.">
|
||
<div slot="footer" style="display:flex;gap:8px;">
|
||
<Button type="ghost" size="sm">Cancel</Button>
|
||
<Button type="primary" size="sm">Deploy</Button>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── APP CHROME ── -->
|
||
<section id="chrome" class="blk">
|
||
<h2>Navbar · Sidebar</h2>
|
||
<p class="blk__desc">App chrome.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Navbar</span>
|
||
<div class="vg__body" style="align-items:stretch;padding:0;">
|
||
<Navbar brand="Nova">
|
||
<a class="navbar__link navbar__link--active" href="#"
|
||
>Services</a
|
||
>
|
||
<a class="navbar__link" href="#">Incidents</a>
|
||
<a class="navbar__link" href="#">Settings</a>
|
||
<Avatar slot="right" type="initials" name="Ava Stone" />
|
||
</Navbar>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Sidebar</span>
|
||
<div class="vg__body">
|
||
<Sidebar header="Nova">
|
||
<SidebarItem href="#" active
|
||
><OverviewIcon slot="icon" size={18} />Overview</SidebarItem
|
||
>
|
||
<SidebarItem href="#"
|
||
><StatsIcon slot="icon" size={18} />Metrics</SidebarItem
|
||
>
|
||
<SidebarItem href="#"
|
||
><ShieldIcon slot="icon" size={18} />Security</SidebarItem
|
||
>
|
||
<SidebarItem href="#"
|
||
><SettingsIcon slot="icon" size={18} />Settings</SidebarItem
|
||
>
|
||
</Sidebar>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── MODAL ── -->
|
||
<section id="modal" class="blk">
|
||
<h2>Modal</h2>
|
||
<p class="blk__desc">
|
||
Overlay dialog — click to open, Escape or overlay to close.
|
||
</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Dialog</span>
|
||
<div class="vg__body">
|
||
<Button type="primary" size="md" data-modal-open="gallery-modal"
|
||
>Open modal</Button
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<Modal id="gallery-modal" title="Confirm deploy">
|
||
Ship build 4821 to production? This rolls out to all edge regions.
|
||
<Fragment slot="footer">
|
||
<Button type="ghost" size="md" data-modal-close>Cancel</Button>
|
||
<Button type="primary" size="md" data-modal-close>Deploy</Button>
|
||
</Fragment>
|
||
</Modal>
|
||
</section>
|
||
|
||
<!-- ── ICONS ── -->
|
||
<section id="icons" class="blk">
|
||
<h2>Icons</h2>
|
||
<p class="blk__desc">25 line icons at 16 / 24 / 32 px.</p>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Arrow2 — orientations × 16 / 24 / 32</span>
|
||
<div class="vg__body">
|
||
{
|
||
([16, 24, 32] as const).map((s) => (
|
||
<span class="icon-line">
|
||
<Arrow2Icon size={s} orientation="up" />
|
||
<Arrow2Icon size={s} orientation="right" />
|
||
<Arrow2Icon size={s} orientation="down" />
|
||
<Arrow2Icon size={s} orientation="left" />
|
||
</span>
|
||
))
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Library — 25 icons at 24px</span>
|
||
<div class="vg__body" style="display:block;">
|
||
<div class="icon-grid">
|
||
{
|
||
iconLib.map(({ name, Icon }) => (
|
||
<div class="icon-cell">
|
||
<Icon size={24} />
|
||
<span>{name}</span>
|
||
</div>
|
||
))
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vg">
|
||
<span class="vg__lbl">Sizes — 16 / 24 / 32</span>
|
||
<div class="vg__body">
|
||
{
|
||
[SearchIcon, SettingsIcon, ShareIcon, DownloadIcon].map(
|
||
(Icon) => (
|
||
<span class="icon-line">
|
||
<>
|
||
<Icon size={16} />
|
||
<Icon size={24} />
|
||
<Icon size={32} />
|
||
</>
|
||
</span>
|
||
),
|
||
)
|
||
}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
</Layout>
|
||
|
||
<script>
|
||
// Persisted theme toggle
|
||
const KEY = "nova-theme";
|
||
const saved = localStorage.getItem(KEY);
|
||
if (saved) document.documentElement.setAttribute("data-theme", saved);
|
||
document.getElementById("theme-btn")?.addEventListener("click", () => {
|
||
const dark = document.documentElement.getAttribute("data-theme") === "dark";
|
||
const next = dark ? "light" : "dark";
|
||
document.documentElement.setAttribute("data-theme", next);
|
||
localStorage.setItem(KEY, next);
|
||
});
|
||
|
||
// Smooth rail nav
|
||
document.querySelectorAll(".g-rail a").forEach((a) => {
|
||
a.addEventListener("click", (e) => {
|
||
e.preventDefault();
|
||
const target = document.querySelector(
|
||
(a as HTMLAnchorElement).getAttribute("href")!,
|
||
);
|
||
target?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
@use "../styles/tokens/typography" as *;
|
||
|
||
.g-page {
|
||
min-height: 100vh;
|
||
background-color: var(--nds-background);
|
||
color: var(--nds-text);
|
||
}
|
||
|
||
// ── Topbar ──
|
||
.g-topbar {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 50;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: var(--nds-spacing-md);
|
||
padding: 12px var(--nds-spacing-lg);
|
||
border-bottom: var(--nds-border-width-thin) solid var(--nds-border);
|
||
background: color-mix(in srgb, var(--nds-background) 85%, transparent);
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
.g-brand {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--nds-spacing-xs);
|
||
}
|
||
.g-mark {
|
||
width: 26px;
|
||
height: 26px;
|
||
border-radius: var(--nds-radius-md);
|
||
background: linear-gradient(135deg, var(--nds-primary), var(--nds-accent));
|
||
}
|
||
.g-name {
|
||
@include text-xl;
|
||
}
|
||
|
||
// ── Layout ──
|
||
.g-layout {
|
||
display: grid;
|
||
grid-template-columns: 200px 1fr;
|
||
}
|
||
|
||
// ── Rail ──
|
||
.g-rail {
|
||
position: sticky;
|
||
top: 53px;
|
||
align-self: start;
|
||
height: calc(100vh - 53px);
|
||
overflow-y: auto;
|
||
padding: var(--nds-spacing-md) var(--nds-spacing-sm);
|
||
border-right: var(--nds-border-width-thin) solid var(--nds-border);
|
||
background: var(--nds-surface);
|
||
&__head {
|
||
@include text-label;
|
||
color: var(--nds-text);
|
||
padding: var(--nds-spacing-xs) var(--nds-spacing-xs) var(--nds-spacing-md);
|
||
margin: 0;
|
||
}
|
||
a {
|
||
display: block;
|
||
padding: 5px var(--nds-spacing-xs);
|
||
border-radius: var(--nds-radius-sm);
|
||
color: var(--nds-neutral);
|
||
text-decoration: none;
|
||
@include text-sm;
|
||
transition:
|
||
background-color 120ms ease,
|
||
color 120ms ease;
|
||
&:hover {
|
||
background: var(--nds-surface-hover);
|
||
color: var(--nds-text);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ── Content ──
|
||
.g-content {
|
||
padding: var(--nds-spacing-2xl) var(--nds-spacing-2xl)
|
||
var(--nds-spacing-3xl);
|
||
max-width: 880px;
|
||
}
|
||
|
||
// ── Block ──
|
||
.blk {
|
||
margin-bottom: var(--nds-spacing-2xl);
|
||
scroll-margin-top: var(--nds-spacing-lg);
|
||
> h2 {
|
||
@include text-2xl;
|
||
margin: 0 0 var(--nds-spacing-3xs);
|
||
}
|
||
&__desc {
|
||
@include text-base;
|
||
color: var(--nds-neutral);
|
||
margin: 0 0 var(--nds-spacing-lg);
|
||
}
|
||
}
|
||
|
||
// ── Variant group ──
|
||
.vg {
|
||
border: var(--nds-border-width-thin) solid var(--nds-border);
|
||
border-radius: var(--nds-radius-lg);
|
||
overflow: hidden;
|
||
background: var(--nds-surface);
|
||
& + & {
|
||
margin-top: var(--nds-spacing-md);
|
||
}
|
||
|
||
&__lbl {
|
||
display: block;
|
||
@include text-sm;
|
||
font-weight: 600;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
color: var(--nds-neutral);
|
||
padding: var(--nds-spacing-xs) var(--nds-spacing-md);
|
||
border-bottom: var(--nds-border-width-thin) solid var(--nds-border);
|
||
background: color-mix(in srgb, var(--nds-text) 2.5%, var(--nds-surface));
|
||
}
|
||
&__body {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: var(--nds-spacing-md);
|
||
padding: var(--nds-spacing-lg) var(--nds-spacing-md);
|
||
}
|
||
}
|
||
|
||
// ── Foundations specimens ──
|
||
.type-scale {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
padding: 0 !important;
|
||
gap: 0 !important;
|
||
}
|
||
.type-row {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: var(--nds-spacing-md);
|
||
padding: var(--nds-spacing-sm) var(--nds-spacing-md);
|
||
border-bottom: var(--nds-border-width-thin) solid var(--nds-border);
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
}
|
||
.type-tag {
|
||
@include text-sm;
|
||
font-family: var(--nds-font-mono);
|
||
color: var(--nds-neutral);
|
||
width: 80px;
|
||
flex-shrink: 0;
|
||
}
|
||
.disp {
|
||
font-family: var(--nds-font-mono);
|
||
font-weight: 700;
|
||
}
|
||
.sans {
|
||
font-family: var(--nds-font-sans);
|
||
}
|
||
.t5xl {
|
||
font-size: 52px;
|
||
}
|
||
.t4xl {
|
||
font-size: 40px;
|
||
}
|
||
.t3xl {
|
||
font-size: 30px;
|
||
}
|
||
.t2xl {
|
||
font-size: 22px;
|
||
}
|
||
.txl {
|
||
font-size: 18px;
|
||
}
|
||
.tlg {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
}
|
||
.tbase {
|
||
font-size: 14px;
|
||
}
|
||
.tlabel {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
}
|
||
.tsm {
|
||
font-size: 12px;
|
||
}
|
||
|
||
.swatch-row {
|
||
gap: var(--nds-spacing-sm) !important;
|
||
flex-wrap: wrap;
|
||
}
|
||
.swatch {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
.swatch-chip {
|
||
width: 80px;
|
||
height: 44px;
|
||
border-radius: var(--nds-radius-md);
|
||
border: var(--nds-border-width-thin) solid var(--nds-border);
|
||
}
|
||
.swatch-name {
|
||
@include text-sm;
|
||
color: var(--nds-neutral);
|
||
font-family: var(--nds-font-mono);
|
||
}
|
||
|
||
.sp-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--nds-spacing-md);
|
||
}
|
||
.sp-label {
|
||
@include text-sm;
|
||
color: var(--nds-neutral);
|
||
font-family: var(--nds-font-mono);
|
||
width: 160px;
|
||
flex-shrink: 0;
|
||
}
|
||
.sp-bar {
|
||
height: 14px;
|
||
background: var(--nds-primary);
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.rad-chip {
|
||
width: 72px;
|
||
height: 72px;
|
||
background: var(--nds-primary-soft);
|
||
border: var(--nds-border-width-thin) solid var(--nds-primary);
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: center;
|
||
padding: 6px;
|
||
span {
|
||
@include text-sm;
|
||
color: var(--nds-primary);
|
||
}
|
||
}
|
||
.elev-chip {
|
||
width: 130px;
|
||
height: 72px;
|
||
background: var(--nds-surface);
|
||
border: var(--nds-border-width-thin) solid var(--nds-border);
|
||
border-radius: var(--nds-radius-md);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
@include text-sm;
|
||
color: var(--nds-neutral);
|
||
font-family: var(--nds-font-mono);
|
||
}
|
||
|
||
// ── Icons ──
|
||
.icon-line {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: var(--nds-spacing-md);
|
||
color: var(--nds-text);
|
||
}
|
||
.icon-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
|
||
gap: var(--nds-spacing-xs);
|
||
width: 100%;
|
||
}
|
||
.icon-cell {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: var(--nds-spacing-xs);
|
||
padding: var(--nds-spacing-md) var(--nds-spacing-xs);
|
||
border: var(--nds-border-width-thin) solid var(--nds-border);
|
||
border-radius: var(--nds-radius-md);
|
||
color: var(--nds-text);
|
||
span {
|
||
@include text-sm;
|
||
color: var(--nds-neutral);
|
||
font-family: var(--nds-font-mono);
|
||
}
|
||
}
|
||
</style>
|