feat(ui): add comprehensive design system with 14 new components

New UI Components:
- Modal/Dialog with sizes, focus trap, backdrop
- Toast/Notification with variants, stacking, actions
- Drawer/Sheet with directions and widths
- Popover with triggers and positioning
- Tooltip with delays and placements
- DropdownMenu with items, dividers, keyboard nav
- Breadcrumbs with separators and collapsing
- Pagination with full/simple/minimal variants
- Accordion with single/multi expand modes
- Skeleton with text/avatar/card/table variants
- InstallSnippet with package manager toggle
- QualityScore with tier badges
- ToolCard for registry display

Design System Enhancements:
- Complete token specification (shadows, radius, z-index, opacity)
- Full dark mode palette in globals.css
- Updated component variants for dark mode support

Style Guide Expansion:
- Split into 21 modular section files
- Pattern library: navigation, forms, feedback, tables, search
- Governance: a11y checklists, content guidelines, icon system
- Interactive examples throughout
This commit is contained in:
Ajax Davis 2026-01-16 05:51:12 +10:00
parent 46b212e65a
commit 066e599293
77 changed files with 12045 additions and 195 deletions

View file

@ -3,161 +3,393 @@
@tailwind utilities;
@layer base {
/* Light mode (default) */
/* Light mode (default) - Warm earthy palette with copper accent */
:root {
/* Backgrounds & Surfaces - DRAMATIC CONTRAST */
--background: 220 15% 96%; /* Light blue-gray background */
--surface: 0 0% 100%; /* Pure white - cards really pop! */
--surface-secondary: 220 15% 94%; /* Subtle gray for secondary surfaces */
--surface-elevated: 0 0% 100%; /* White (elevated) */
--surface-overlay: 0 0% 100%; /* White overlays */
/* ===========================================
COMPREHENSIVE COLOR PALETTE
Warm, earthy tones with copper accent
=========================================== */
/* Foreground (Text) */
--foreground: 222 47% 11%; /* Almost black */
--foreground-secondary: 215 25% 35%; /* Much darker for readability */
--foreground-tertiary: 215 20% 50%; /* Medium gray */
--foreground-muted: 215 16% 65%; /* Light gray */
/* Backgrounds - Layered surface system */
--background: 30 15% 95%; /* #F5F3F0 - Warm off-white base */
--surface: 0 0% 100%; /* #FFFFFF - Cards, panels */
--surface-2: 30 20% 97%; /* #FAF8F6 - Elevated surfaces */
--surface-3: 25 12% 93%; /* #F0EDEA - Highest elevation */
--surface-secondary: 30 10% 95%; /* Legacy alias */
--surface-elevated: 30 20% 97%; /* Legacy alias */
--surface-overlay: 0 0% 100%; /* Overlays, modals */
/* Borders - MUCH MORE VISIBLE */
--border: 214 25% 80%; /* Strong medium gray */
--border-strong: 214 30% 60%; /* Dark gray for emphasis */
--border-subtle: 214 20% 88%; /* Subtle but visible */
/* Text - Clear hierarchy */
--foreground: 25 15% 10%; /* #1A1715 - Primary text */
--foreground-secondary: 25 8% 35%; /* #5C564F - Secondary text */
--foreground-tertiary: 25 6% 50%; /* #817B74 - Tertiary text */
--foreground-muted: 25 5% 60%; /* #9A958F - Muted/hints */
/* Interactive States - MODERN & REFINED */
--primary: 221 83% 53%; /* Sophisticated blue */
--primary-foreground: 0 0% 100%; /* White text */
--secondary: 220 15% 90%; /* Subtle gray-blue bg */
--secondary-foreground: 222 47% 11%; /* Dark text */
--accent: 221 75% 95%; /* Soft blue tint */
--accent-foreground: 221 70% 35%; /* Rich blue text */
--muted: 220 15% 92%; /* Subtle muted bg */
--muted-foreground: 215 25% 40%; /* Darker muted text */
/* Borders - Warm grays */
--border: 30 8% 80%; /* #CEC9C3 - Default borders */
--border-strong: 25 8% 50%; /* #857F77 - Emphasized borders */
--border-subtle: 30 6% 88%; /* #E3E0DC - Subtle borders */
/* Status Colors - Modern Editorial Palette */
--success: 152 57% 45%; /* Refined emerald green */
--success-foreground: 0 0% 100%; /* White text on success */
--error: 0 65% 51%; /* Sophisticated red, less harsh */
--error-foreground: 0 0% 100%; /* White text on error */
--warning: 36 100% 50%; /* Warm sophisticated amber */
--warning-foreground: 0 0% 100%; /* White text on warning */
--info: 210 100% 56%; /* Cool modern blue */
--info-foreground: 0 0% 100%; /* White text on info */
/* Primary - Copper accent #A6592D */
--primary: 22 57% 41%; /* #A6592D - Brand copper */
--primary-hover: 22 57% 35%; /* #8B4A26 - Hover state */
--primary-active: 22 57% 30%; /* #753F20 - Active/pressed */
--primary-foreground: 0 0% 100%; /* White text on primary */
/* Destructive (legacy) */
--destructive: 0 65% 51%; /* Match error color */
/* Accent - Copper variations */
--accent: 22 57% 41%; /* #A6592D - Same as primary */
--accent-strong: 22 60% 35%; /* #8F4722 - Darker/hover */
--accent-muted: 22 30% 90%; /* #EDE5DF - Soft copper tint */
--accent-foreground: 0 0% 100%; /* White text */
/* Secondary - Warm neutrals */
--secondary: 30 10% 92%; /* #EDEBE8 - Soft warm bg */
--secondary-hover: 30 10% 88%; /* #E3E0DC - Hover */
--secondary-foreground: 25 15% 10%; /* Dark text */
/* Muted - Subtle backgrounds */
--muted: 30 8% 94%; /* #F2F0ED - Muted background */
--muted-foreground: 25 8% 40%; /* #6B655E - Muted text */
/* Status Colors - Earthy, warm variants */
--success: 145 45% 36%; /* #327D52 - Forest green */
--success-light: 145 35% 92%; /* #E5F3EC - Light bg */
--success-foreground: 0 0% 100%;
--warning: 38 75% 50%; /* #D9A020 - Warm amber */
--warning-light: 38 60% 92%; /* #F8F2E0 - Light bg */
--warning-foreground: 25 15% 10%;
--error: 0 55% 50%; /* #C44545 - Warm red */
--error-light: 0 50% 95%; /* #F9EDED - Light bg */
--error-foreground: 0 0% 100%;
--info: 210 60% 50%; /* #3380CC - Cool blue */
--info-light: 210 50% 95%; /* #EDF4FB - Light bg */
--info-foreground: 0 0% 100%;
/* Destructive (alias for error) */
--destructive: 0 55% 50%;
--destructive-foreground: 0 0% 100%;
/* Form Elements */
--input: 214 32% 91%;
--ring: 222 84% 5%;
--input: 30 8% 80%; /* Input borders */
--input-focus: 22 57% 41%; /* Copper focus */
--ring: 22 57% 41%; /* Focus ring - copper */
--ring-offset: 0 0% 100%;
/* Grid/Blueprint Pattern */
--grid-color: 214 32% 90%;
--grid-size: 24px; /* Grid cell size */
--grid-color: 25 8% 88%;
--grid-size: 24px;
/* Card */
--card: 0 0% 100%; /* Pure white - stands out dramatically on blue-gray background */
--card-foreground: 222 47% 11%;
--card: 0 0% 100%;
--card-foreground: 25 15% 10%;
/* Border Radii */
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius: var(--radius-md);
/* Border Radii - SHARP CORNERS (0) */
--radius-sm: 0;
--radius-md: 0;
--radius-lg: 0;
--radius-xl: 0;
--radius: 0;
/* Spacing - Editorial scale for airy layouts */
--spacing-section: 6rem; /* 96px - Between major sections */
--spacing-component: 3rem; /* 48px - Between components */
--spacing-element: 1.5rem; /* 24px - Between related elements */
--spacing-tight: 0.75rem; /* 12px - Tight groupings */
--spacing-comfortable: 2rem; /* 32px - Comfortable breathing room */
/* Spacing - Generous whitespace */
--spacing-section: 8rem;
--spacing-component: 4rem;
--spacing-element: 2rem;
--spacing-tight: 1rem;
--spacing-comfortable: 3rem;
/* Typography */
--font-sans: var(--font-sans), -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
--font-mono: var(--font-mono), "SF Mono", Consolas, monospace;
--font-sans: var(--font-inter), -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
--font-mono: var(--font-jetbrains), "JetBrains Mono", "SF Mono", Consolas, monospace;
/* Editorial Line Heights - Airy, readable */
--leading-tight: 1.25;
--leading-snug: 1.375;
--leading-normal: 1.5;
--leading-relaxed: 1.625;
--leading-loose: 1.75;
--leading-editorial: 1.8; /* Extra generous for body copy */
/* Line Heights */
--leading-tight: 1.2;
--leading-snug: 1.4;
--leading-normal: 1.6;
--leading-relaxed: 1.7;
--leading-loose: 1.8;
--leading-editorial: 1.9;
/* Letter Spacing - Technical precision */
--tracking-tighter: -0.05em;
--tracking-tight: -0.025em;
/* Letter Spacing */
--tracking-tighter: -0.03em;
--tracking-tight: -0.015em;
--tracking-normal: 0;
--tracking-wide: 0.025em;
--tracking-wider: 0.05em;
--tracking-widest: 0.1em;
/* Brutalist Accent Colors - Modern Editorial */
--brutalist-accent: 221 83% 53%; /* Sophisticated blue */
--brutalist-accent-hover: 221 83% 45%; /* Slightly deeper on hover */
/* Brutalist Accents */
--brutalist-accent: 22 57% 41%;
--brutalist-accent-hover: 22 57% 35%;
/* Gradients */
--gradient-start: 24 70% 50%;
--gradient-end: 15 60% 20%;
/* ===========================================
MOTION & ANIMATION TOKENS
Motion communicates state, not decoration
Fast by default (150-200ms)
=========================================== */
/* Duration tokens */
--motion-instant: 0ms;
--motion-fast: 150ms;
--motion-base: 200ms;
--motion-slow: 300ms;
--motion-slower: 500ms;
/* Easing tokens */
--easing-standard: cubic-bezier(0.4, 0, 0.2, 1);
--easing-decelerate: cubic-bezier(0, 0, 0.2, 1);
--easing-accelerate: cubic-bezier(0.4, 0, 1, 1);
--easing-linear: linear;
--easing-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
/* ===========================================
TYPOGRAPHY SCALE
Modular scale with 1.25 ratio
Base: 16px
=========================================== */
/* Type scale */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 2rem; /* 32px */
--text-4xl: 2.5rem; /* 40px */
--text-5xl: 3rem; /* 48px */
--text-6xl: 4rem; /* 64px */
/* Max line widths for readability */
--prose-width: 65ch;
--prose-width-narrow: 45ch;
--prose-width-wide: 80ch;
/* ===========================================
BREAKPOINTS
Mobile-first responsive design
=========================================== */
/* Breakpoint values (for reference, Tailwind handles classes) */
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
--breakpoint-2xl: 1536px;
/* ===========================================
DENSITY MODES
Compact / Comfortable / Spacious
=========================================== */
/* Default is comfortable */
--density-row-height: 48px;
--density-cell-padding-x: 16px;
--density-cell-padding-y: 12px;
--density-icon-size: 20px;
--density-font-size: var(--text-sm);
/* ===========================================
Z-INDEX SCALE
Consistent layering
=========================================== */
--z-base: 0;
--z-dropdown: 100;
--z-sticky: 200;
--z-fixed: 300;
--z-modal-backdrop: 400;
--z-modal: 500;
--z-popover: 600;
--z-tooltip: 700;
--z-toast: 800;
/* ===========================================
FOCUS & ACCESSIBILITY
Never remove focus indicators
=========================================== */
--focus-ring-width: 2px;
--focus-ring-offset: 2px;
--focus-ring-color: var(--ring);
/* ===========================================
ELEVATION & SHADOWS
Subtle depth, prefer borders for emphasis
=========================================== */
--shadow-none: none;
--shadow-xs: 0 1px 2px 0 hsl(var(--foreground) / 0.05);
--shadow-sm: 0 1px 3px 0 hsl(var(--foreground) / 0.08), 0 1px 2px -1px hsl(var(--foreground) / 0.08);
--shadow-md: 0 4px 6px -1px hsl(var(--foreground) / 0.08), 0 2px 4px -2px hsl(var(--foreground) / 0.08);
--shadow-lg: 0 10px 15px -3px hsl(var(--foreground) / 0.08), 0 4px 6px -4px hsl(var(--foreground) / 0.08);
--shadow-xl: 0 20px 25px -5px hsl(var(--foreground) / 0.08), 0 8px 10px -6px hsl(var(--foreground) / 0.08);
--shadow-2xl: 0 25px 50px -12px hsl(var(--foreground) / 0.15);
--shadow-inner: inset 0 2px 4px 0 hsl(var(--foreground) / 0.05);
/* ===========================================
RADIUS SCALE
Sharp corners by default (brutalist)
Explicit tokens for rare roundness
=========================================== */
--radius-none: 0;
--radius-xs: 2px;
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-2xl: 16px;
--radius-full: 9999px;
/* ===========================================
BORDER WIDTH SCALE
=========================================== */
--border-hairline: 0.5px;
--border-thin: 1px;
--border-medium: 2px;
--border-thick: 4px;
--border-heavy: 6px;
--border-brutalist: 8px;
/* ===========================================
OPACITY SCALE
For disabled, subtle, overlay states
=========================================== */
--opacity-0: 0;
--opacity-disabled: 0.5;
--opacity-subtle: 0.6;
--opacity-muted: 0.4;
--opacity-overlay: 0.8;
--opacity-overlay-heavy: 0.9;
--opacity-hover: 0.9;
--opacity-full: 1;
/* ===========================================
ADDITIONAL Z-INDEX LAYERS
Extended from base scale
=========================================== */
--z-drawer: 350;
--z-command-palette: 900;
--z-notification: 850;
}
/* Dark mode (opt-in) - Vercel/Cursor/Perplexity aesthetic */
/* Dark mode (opt-in) - Warm dark with copper accent */
.dark {
/* Backgrounds & Surfaces */
--background: 210 10% 5%; /* #0d0d0f - Almost black */
--surface: 210 10% 8%; /* #14141a - Slightly lighter */
--surface-secondary: 210 10% 10%; /* Secondary surfaces */
--surface-elevated: 210 10% 12%; /* #1c1c24 - Cards, modals */
--surface-overlay: 210 10% 16%; /* #25252f - Overlays, popovers */
/* ===========================================
DARK MODE - Warm charcoal with copper
=========================================== */
/* Foreground (Text) */
--foreground: 210 10% 90%; /* #e3e3e8 - Primary text */
--foreground-secondary: 210 8% 60%; /* #949499 - Secondary text */
--foreground-tertiary: 210 6% 40%; /* #64646a - Tertiary text */
--foreground-muted: 210 6% 30%; /* #4a4a50 - Muted text */
/* Backgrounds - Layered dark surfaces */
--background: 25 10% 6%; /* #0F0E0D - Warm dark base */
--surface: 25 10% 9%; /* #171514 - Cards, panels */
--surface-2: 25 10% 12%; /* #1F1D1B - Elevated */
--surface-3: 25 10% 16%; /* #2A2725 - Highest elevation */
--surface-secondary: 25 10% 10%;
--surface-elevated: 25 10% 12%;
--surface-overlay: 25 10% 16%;
/* Borders (1px, subtle) */
--border: 210 10% 20%; /* #30303a - Default border */
--border-strong: 210 12% 30%; /* #494954 - Emphasized border */
--border-subtle: 210 8% 15%; /* #22222c - Subtle border */
/* Text - Clear hierarchy in dark */
--foreground: 30 10% 90%; /* #E8E5E2 - Primary text */
--foreground-secondary: 25 6% 60%; /* #9A9592 - Secondary */
--foreground-tertiary: 25 5% 45%; /* #757270 - Tertiary */
--foreground-muted: 25 4% 35%; /* #5A5856 - Muted */
/* Interactive States */
--primary: 210 10% 98%; /* White-ish for dark mode */
--primary-foreground: 210 10% 5%; /* Dark text on light bg */
--secondary: 210 10% 15%; /* Dark secondary */
--secondary-foreground: 210 10% 90%; /* Light text on dark bg */
--accent: 210 10% 18%; /* Accent background */
--accent-foreground: 210 10% 95%; /* Accent text */
--muted: 210 10% 12%; /* Muted background */
--muted-foreground: 210 8% 60%; /* Muted text */
/* Borders - Subtle in dark */
--border: 25 8% 20%; /* #363230 - Default */
--border-strong: 25 8% 35%; /* #5C5854 - Emphasized */
--border-subtle: 25 6% 14%; /* #252321 - Subtle */
/* Status Colors - Modern Editorial (Dark Mode) */
--success: 152 57% 50%; /* Refined emerald - slightly brighter for dark */
--success-foreground: 0 0% 100%; /* White text */
--error: 0 65% 58%; /* Sophisticated red - brighter for dark */
--error-foreground: 0 0% 100%; /* White text */
--warning: 36 100% 55%; /* Warm amber - brighter for dark */
--warning-foreground: 0 0% 100%; /* White text */
--info: 210 100% 60%; /* Cool blue - brighter for dark */
--info-foreground: 0 0% 100%; /* White text */
/* Primary - Copper, brighter for dark mode */
--primary: 22 57% 50%; /* #C96A38 - Brighter copper */
--primary-hover: 22 57% 55%; /* #D47942 - Hover */
--primary-active: 22 57% 45%; /* #B55E2E - Active */
--primary-foreground: 0 0% 100%;
/* Destructive (legacy support) */
--destructive: 0 65% 58%;
/* Accent - Copper variations */
--accent: 22 57% 50%;
--accent-strong: 22 60% 55%;
--accent-muted: 22 20% 15%;
--accent-foreground: 0 0% 100%;
/* Secondary - Warm dark */
--secondary: 25 10% 15%;
--secondary-hover: 25 10% 20%;
--secondary-foreground: 30 10% 90%;
/* Muted - Subtle dark */
--muted: 25 8% 12%;
--muted-foreground: 25 6% 55%;
/* Status Colors - Brighter for dark mode */
--success: 145 50% 45%; /* Brighter green */
--success-light: 145 30% 15%;
--success-foreground: 0 0% 100%;
--warning: 38 80% 55%; /* Brighter amber */
--warning-light: 38 40% 15%;
--warning-foreground: 25 15% 10%;
--error: 0 60% 55%; /* Brighter red */
--error-light: 0 40% 15%;
--error-foreground: 0 0% 100%;
--info: 210 70% 55%; /* Brighter blue */
--info-light: 210 40% 15%;
--info-foreground: 0 0% 100%;
/* Destructive */
--destructive: 0 60% 55%;
--destructive-foreground: 0 0% 100%;
/* Form Elements */
--input: 210 10% 20%; /* Input border */
--ring: 210 10% 90%; /* Focus ring */
--ring-offset: 210 10% 5%; /* Focus ring offset */
--input: 25 8% 20%;
--input-focus: 22 57% 50%;
--ring: 22 57% 50%;
--ring-offset: 25 10% 6%;
/* Grid/Blueprint Pattern */
--grid-color: 210 10% 15%; /* Grid line color */
/* Grid */
--grid-color: 25 8% 15%;
/* Card */
--card: 210 10% 8%;
--card-foreground: 210 10% 90%;
--card: 25 10% 9%;
--card-foreground: 30 10% 90%;
/* Brutalist Accent Colors - Modern Editorial */
--brutalist-accent: 210 100% 60%; /* Cool sophisticated blue for dark mode */
--brutalist-accent-hover: 210 100% 65%; /* Slightly brighter on hover */
/* Brutalist Accents */
--brutalist-accent: 22 57% 50%;
--brutalist-accent-hover: 22 57% 55%;
/* Border Radii - SHARP CORNERS (0) */
--radius-sm: 0;
--radius-md: 0;
--radius-lg: 0;
--radius-xl: 0;
--radius: 0;
/* ===========================================
DARK MODE SHADOWS
Darker, more prominent for visibility
=========================================== */
--shadow-none: none;
--shadow-xs: 0 1px 2px 0 hsl(0 0% 0% / 0.2);
--shadow-sm: 0 1px 3px 0 hsl(0 0% 0% / 0.25), 0 1px 2px -1px hsl(0 0% 0% / 0.25);
--shadow-md: 0 4px 6px -1px hsl(0 0% 0% / 0.25), 0 2px 4px -2px hsl(0 0% 0% / 0.25);
--shadow-lg: 0 10px 15px -3px hsl(0 0% 0% / 0.25), 0 4px 6px -4px hsl(0 0% 0% / 0.25);
--shadow-xl: 0 20px 25px -5px hsl(0 0% 0% / 0.3), 0 8px 10px -6px hsl(0 0% 0% / 0.3);
--shadow-2xl: 0 25px 50px -12px hsl(0 0% 0% / 0.4);
--shadow-inner: inset 0 2px 4px 0 hsl(0 0% 0% / 0.2);
/* Radius, border, opacity tokens inherit from light mode */
}
/* Base element styles */
@ -518,4 +750,96 @@
transition-duration: 0.01ms !important;
}
}
/* ===========================================
DENSITY MODES
Use data-density attribute on container
=========================================== */
/* Compact density */
[data-density="compact"] {
--density-row-height: 36px;
--density-cell-padding-x: 8px;
--density-cell-padding-y: 6px;
--density-icon-size: 16px;
--density-font-size: var(--text-xs);
}
/* Comfortable density (default) */
[data-density="comfortable"] {
--density-row-height: 48px;
--density-cell-padding-x: 16px;
--density-cell-padding-y: 12px;
--density-icon-size: 20px;
--density-font-size: var(--text-sm);
}
/* Spacious density */
[data-density="spacious"] {
--density-row-height: 64px;
--density-cell-padding-x: 24px;
--density-cell-padding-y: 20px;
--density-icon-size: 24px;
--density-font-size: var(--text-base);
}
/* Density utility classes */
.density-row {
height: var(--density-row-height);
font-size: var(--density-font-size);
}
.density-cell {
padding: var(--density-cell-padding-y) var(--density-cell-padding-x);
font-size: var(--density-font-size);
}
.density-icon {
width: var(--density-icon-size);
height: var(--density-icon-size);
}
/* ===========================================
PROSE WIDTHS FOR READABILITY
=========================================== */
.prose-width {
max-width: var(--prose-width);
}
.prose-width-narrow {
max-width: var(--prose-width-narrow);
}
.prose-width-wide {
max-width: var(--prose-width-wide);
}
/* ===========================================
MOTION UTILITIES
Using CSS custom properties
=========================================== */
.motion-fast {
transition-duration: var(--motion-fast);
transition-timing-function: var(--easing-standard);
}
.motion-base {
transition-duration: var(--motion-base);
transition-timing-function: var(--easing-standard);
}
.motion-slow {
transition-duration: var(--motion-slow);
transition-timing-function: var(--easing-standard);
}
.motion-linear {
transition-timing-function: var(--easing-linear);
}
.motion-spring {
transition-timing-function: var(--easing-spring);
}
}

View file

@ -0,0 +1,153 @@
'use client';
import { Badge } from '@tpmjs/ui/Badge/Badge';
import { Container } from '@tpmjs/ui/Container/Container';
import { useState } from 'react';
import { AppHeader } from '~/components/AppHeader';
import {
FieldsetSection,
NavItem,
SectionA11yChecklists,
SectionAccessibility,
SectionColors,
SectionComponentAPIs,
SectionComponents,
SectionContent,
SectionContentGuidelines,
SectionDataViz,
SectionIconSystem,
SectionIcons,
SectionLayout,
SectionMotion,
SectionPatternFeedback,
SectionPatternForms,
SectionPatternNavigation,
SectionPatternSearch,
SectionPatternTables,
SectionPrinciples,
SectionSpacing,
SectionTheming,
SectionTypography,
} from '~/components/style-guide';
export default function StyleGuidePage(): React.ReactElement {
const [activeTab, setActiveTab] = useState('all');
const [radioValue, setRadioValue] = useState('option1');
const [counterValue, setCounterValue] = useState(1234);
const [density, setDensity] = useState<'compact' | 'comfortable' | 'spacious'>('comfortable');
return (
<div className="min-h-screen flex flex-col bg-background">
<AppHeader />
<main className="flex-1 py-24">
<Container size="lg" padding="lg">
{/* Hero Section */}
<div className="mb-24">
<h1 className="font-mono text-4xl md:text-5xl lg:text-6xl font-semibold mb-6 text-foreground lowercase tracking-tight">
tpmjs design system
</h1>
<p className="font-sans text-lg text-foreground-secondary max-w-2xl leading-relaxed mb-8">
The comprehensive design system for TPMJS. This guide covers design principles,
foundations, components, and governance rules that scale across teams, AI agents,
and external contributors.
</p>
<div className="flex flex-wrap gap-4">
<Badge variant="default">v2.0</Badge>
<Badge variant="outline">wcag aa</Badge>
<Badge variant="outline">dark mode</Badge>
</div>
</div>
{/* Table of Contents */}
<FieldsetSection title="table of contents" id="toc">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
<div className="space-y-2">
<p className="font-mono text-xs text-foreground-tertiary uppercase tracking-wide mb-3">foundations</p>
<NavItem href="#principles">1. design principles</NavItem>
<NavItem href="#colors">2. color system</NavItem>
<NavItem href="#typography">3. typography</NavItem>
<NavItem href="#spacing">4. spacing</NavItem>
<NavItem href="#motion">5. motion</NavItem>
</div>
<div className="space-y-2">
<p className="font-mono text-xs text-foreground-tertiary uppercase tracking-wide mb-3">systems</p>
<NavItem href="#accessibility">6. accessibility</NavItem>
<NavItem href="#layout">7. layout &amp; responsiveness</NavItem>
<NavItem href="#content">8. content guidelines</NavItem>
<NavItem href="#data-viz">9. data visualization</NavItem>
<NavItem href="#icons">10. iconography</NavItem>
</div>
<div className="space-y-2">
<p className="font-mono text-xs text-foreground-tertiary uppercase tracking-wide mb-3">implementation</p>
<NavItem href="#theming">11. theming</NavItem>
<NavItem href="#components">12. components</NavItem>
<NavItem href="#api">13. component apis</NavItem>
</div>
<div className="space-y-2">
<p className="font-mono text-xs text-foreground-tertiary uppercase tracking-wide mb-3">patterns</p>
<NavItem href="#nav-patterns">14. navigation</NavItem>
<NavItem href="#form-patterns">15. forms</NavItem>
<NavItem href="#feedback-patterns">16. feedback</NavItem>
<NavItem href="#table-patterns">17. tables</NavItem>
<NavItem href="#search-patterns">18. search &amp; filtering</NavItem>
</div>
<div className="space-y-2">
<p className="font-mono text-xs text-foreground-tertiary uppercase tracking-wide mb-3">governance</p>
<NavItem href="#a11y-checklists">19. a11y checklists</NavItem>
<NavItem href="#content-guidelines">20. content &amp; writing</NavItem>
<NavItem href="#icon-system">21. icon system</NavItem>
</div>
</div>
</FieldsetSection>
{/* Foundation Sections */}
<SectionPrinciples />
<SectionColors />
<SectionTypography />
<SectionSpacing />
<SectionMotion />
{/* Systems Sections */}
<SectionAccessibility />
<SectionLayout density={density} onDensityChange={setDensity} />
<SectionContent />
<SectionDataViz counterValue={counterValue} onCounterChange={setCounterValue} />
<SectionIcons />
{/* Implementation Sections */}
<SectionTheming />
<SectionComponents
activeTab={activeTab}
onTabChange={setActiveTab}
radioValue={radioValue}
onRadioChange={setRadioValue}
/>
<SectionComponentAPIs />
{/* Pattern Library Sections */}
<SectionPatternNavigation />
<SectionPatternForms />
<SectionPatternFeedback />
<SectionPatternTables />
<SectionPatternSearch />
{/* Governance Sections */}
<SectionA11yChecklists />
<SectionContentGuidelines />
<SectionIconSystem />
{/* Footer */}
<div className="text-center py-12 border-t border-dashed border-border mt-16">
<p className="font-mono text-sm text-foreground-secondary">
tpmjs design system v2.0
</p>
<p className="font-sans text-xs text-foreground-tertiary mt-2">
built with @tpmjs/ui • inspired by turbopuffer.com
</p>
</div>
</Container>
</main>
</div>
);
}

View file

@ -0,0 +1,465 @@
'use client';
import { Badge } from '@tpmjs/ui/Badge/Badge';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@tpmjs/ui/Table/Table';
import { FieldsetSection, SubSection } from './shared';
type A11yRequirement = {
component: string;
ariaPattern: string;
focusTrap: boolean;
keyboard: string[];
reducedMotion: string;
};
const overlayComponents: A11yRequirement[] = [
{
component: 'Modal',
ariaPattern: 'dialog',
focusTrap: true,
keyboard: ['Esc (close)', 'Tab (cycle focus)'],
reducedMotion: 'Instant open/close',
},
{
component: 'Drawer',
ariaPattern: 'dialog',
focusTrap: true,
keyboard: ['Esc (close)', 'Tab (cycle focus)'],
reducedMotion: 'No slide animation',
},
{
component: 'Popover',
ariaPattern: 'dialog',
focusTrap: false,
keyboard: ['Esc (close)', 'Tab (move to next)'],
reducedMotion: 'Instant show/hide',
},
{
component: 'Tooltip',
ariaPattern: 'tooltip',
focusTrap: false,
keyboard: ['Focus trigger (show)', 'Blur (hide)'],
reducedMotion: 'Instant show/hide',
},
{
component: 'DropdownMenu',
ariaPattern: 'menu',
focusTrap: false,
keyboard: ['Enter/Space (select)', 'Arrow keys (navigate)', 'Esc (close)'],
reducedMotion: 'Instant show/hide',
},
{
component: 'Toast',
ariaPattern: 'alert / status',
focusTrap: false,
keyboard: ['Focus action button', 'Enter (action)'],
reducedMotion: 'No slide animation',
},
];
const formComponents: A11yRequirement[] = [
{
component: 'Input',
ariaPattern: 'textbox',
focusTrap: false,
keyboard: ['Tab (focus)', 'Type (input)'],
reducedMotion: 'N/A',
},
{
component: 'Select',
ariaPattern: 'listbox',
focusTrap: false,
keyboard: ['Enter/Space (open)', 'Arrow keys (navigate)', 'Esc (close)'],
reducedMotion: 'Instant show/hide',
},
{
component: 'Checkbox',
ariaPattern: 'checkbox',
focusTrap: false,
keyboard: ['Space (toggle)', 'Tab (focus)'],
reducedMotion: 'N/A',
},
{
component: 'Radio',
ariaPattern: 'radiogroup',
focusTrap: false,
keyboard: ['Arrow keys (select)', 'Tab (focus)'],
reducedMotion: 'N/A',
},
{
component: 'Switch',
ariaPattern: 'switch',
focusTrap: false,
keyboard: ['Space (toggle)', 'Tab (focus)'],
reducedMotion: 'Instant toggle',
},
{
component: 'Slider',
ariaPattern: 'slider',
focusTrap: false,
keyboard: ['Arrow keys (adjust)', 'Home/End (min/max)'],
reducedMotion: 'N/A',
},
];
const navigationComponents: A11yRequirement[] = [
{
component: 'Tabs',
ariaPattern: 'tablist',
focusTrap: false,
keyboard: ['Arrow keys (navigate)', 'Enter/Space (select)', 'Home/End (first/last)'],
reducedMotion: 'N/A',
},
{
component: 'Breadcrumbs',
ariaPattern: 'navigation',
focusTrap: false,
keyboard: ['Tab (focus links)', 'Enter (activate)'],
reducedMotion: 'N/A',
},
{
component: 'Pagination',
ariaPattern: 'navigation',
focusTrap: false,
keyboard: ['Tab (focus)', 'Enter (activate)'],
reducedMotion: 'N/A',
},
{
component: 'Accordion',
ariaPattern: 'region',
focusTrap: false,
keyboard: ['Enter/Space (toggle)', 'Tab (focus)'],
reducedMotion: 'Instant expand/collapse',
},
];
function A11yTable({ components, title }: { components: A11yRequirement[]; title: string }) {
return (
<div className="mb-8">
<h4 className="font-mono text-sm font-medium mb-4 pb-2 border-b border-dashed border-border">
{title}
</h4>
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>component</TableHead>
<TableHead>aria pattern</TableHead>
<TableHead>focus trap</TableHead>
<TableHead>keyboard</TableHead>
<TableHead>reduced motion</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{components.map((comp) => (
<TableRow key={comp.component}>
<TableCell className="font-mono text-sm">{comp.component}</TableCell>
<TableCell>
<code className="text-xs bg-surface-2 px-2 py-0.5">{comp.ariaPattern}</code>
</TableCell>
<TableCell>
{comp.focusTrap ? (
<Badge variant="success" size="sm">yes</Badge>
) : (
<Badge variant="outline" size="sm">no</Badge>
)}
</TableCell>
<TableCell>
<div className="space-y-1">
{comp.keyboard.map((k, i) => (
<div key={i} className="text-xs text-foreground-secondary">{k}</div>
))}
</div>
</TableCell>
<TableCell className="text-xs text-foreground-secondary">
{comp.reducedMotion}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
);
}
export function SectionA11yChecklists(): React.ReactElement {
return (
<FieldsetSection title="19. accessibility checklists" id="a11y-checklists">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Component-level accessibility requirements and implementation guides.
All components follow WCAG 2.1 AA standards.
</p>
<SubSection title="aria patterns & keyboard">
<p className="font-sans text-sm text-foreground-secondary mb-6">
Each component has specific ARIA patterns and keyboard interactions that must be implemented.
</p>
<A11yTable components={overlayComponents} title="overlay components" />
<A11yTable components={formComponents} title="form components" />
<A11yTable components={navigationComponents} title="navigation components" />
</SubSection>
<SubSection title="focus management">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Proper focus management is critical for keyboard and screen reader users.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4 flex items-center gap-2">
<Icon icon="check" size="sm" className="text-success" />
focus trap required
</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>Modal dialogs - trap focus inside</li>
<li>Drawer sheets - trap until closed</li>
<li>Full-screen overlays</li>
</ul>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4 flex items-center gap-2">
<Icon icon="arrowRight" size="sm" className="text-accent" />
focus restoration
</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>Return focus to trigger on close</li>
<li>Save and restore focus position</li>
<li>Skip links for long content</li>
</ul>
</div>
</div>
<div className="mt-6 bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-3">implementation pattern</h4>
<pre className="text-xs font-mono text-foreground-secondary overflow-x-auto">
{`// Focus trap implementation
const dialogRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLButtonElement>(null);
// Save trigger reference before opening
const handleOpen = () => {
triggerRef.current = document.activeElement as HTMLButtonElement;
setOpen(true);
};
// Restore focus on close
const handleClose = () => {
setOpen(false);
triggerRef.current?.focus();
};`}
</pre>
</div>
</SubSection>
<SubSection title="screen reader announcements">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Use live regions to announce dynamic content changes.
</p>
<div className="space-y-4">
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-3">aria-live regions</h4>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="p-3 bg-surface-2">
<code className="text-xs">aria-live="polite"</code>
<p className="text-xs text-foreground-secondary mt-2">
Toast notifications, status updates
</p>
</div>
<div className="p-3 bg-surface-2">
<code className="text-xs">aria-live="assertive"</code>
<p className="text-xs text-foreground-secondary mt-2">
Error messages, critical alerts
</p>
</div>
<div className="p-3 bg-surface-2">
<code className="text-xs">role="status"</code>
<p className="text-xs text-foreground-secondary mt-2">
Loading states, progress updates
</p>
</div>
</div>
</div>
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-3">component announcements</h4>
<Table>
<TableHeader>
<TableRow>
<TableHead>event</TableHead>
<TableHead>announcement</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-mono text-sm">Toast shown</TableCell>
<TableCell className="text-sm text-foreground-secondary">"[message content]"</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-mono text-sm">Modal opened</TableCell>
<TableCell className="text-sm text-foreground-secondary">"[dialog title], dialog"</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-mono text-sm">Form error</TableCell>
<TableCell className="text-sm text-foreground-secondary">"Error: [field name], [error message]"</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-mono text-sm">Loading complete</TableCell>
<TableCell className="text-sm text-foreground-secondary">"Loading complete, [N] results"</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</div>
</SubSection>
<SubSection title="color contrast requirements">
<p className="font-sans text-sm text-foreground-secondary mb-4">
All text must meet WCAG AA contrast requirements (4.5:1 for normal text, 3:1 for large text).
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-3">
<div className="flex items-center justify-between p-3 bg-background border border-border">
<span className="text-foreground">foreground on background</span>
<Badge variant="success" size="sm">9.2:1</Badge>
</div>
<div className="flex items-center justify-between p-3 bg-surface border border-border">
<span className="text-foreground-secondary">secondary on surface</span>
<Badge variant="success" size="sm">5.8:1</Badge>
</div>
<div className="flex items-center justify-between p-3 bg-surface border border-border">
<span className="text-foreground-tertiary">tertiary on surface</span>
<Badge variant="warning" size="sm">4.5:1</Badge>
</div>
</div>
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-3">contrast verification</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• Test all color combinations in both themes</li>
<li>• Verify focus indicators (3:1 minimum)</li>
<li>• Ensure error states meet requirements</li>
<li>• Check interactive state contrast changes</li>
</ul>
</div>
</div>
</SubSection>
<SubSection title="reduced motion support">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Respect the <code className="px-1 bg-surface-2 text-xs">prefers-reduced-motion</code> media query
for users who experience motion sickness or vestibular disorders.
</p>
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-3">implementation</h4>
<pre className="text-xs font-mono text-foreground-secondary overflow-x-auto">
{`// CSS approach
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
// React hook approach
import { useReducedMotion } from '@tpmjs/ui/system/hooks/useReducedMotion';
function AnimatedComponent() {
const prefersReducedMotion = useReducedMotion();
return (
<div
className={prefersReducedMotion ? 'instant' : 'animated'}
/>
);
}`}
</pre>
</div>
<div className="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="p-3 bg-success-light border border-success">
<p className="font-mono text-sm text-success mb-1">always animate</p>
<p className="text-xs text-foreground-secondary">Progress bars, loading spinners</p>
</div>
<div className="p-3 bg-warning-light border border-warning">
<p className="font-mono text-sm text-warning mb-1">respect preference</p>
<p className="text-xs text-foreground-secondary">Page transitions, modal animations</p>
</div>
<div className="p-3 bg-error-light border border-error">
<p className="font-mono text-sm text-error mb-1">never animate</p>
<p className="text-xs text-foreground-secondary">Auto-playing video, parallax</p>
</div>
</div>
</SubSection>
<SubSection title="testing checklist">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Before shipping any component, verify accessibility with these tests.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-3 flex items-center gap-2">
<Icon icon="terminal" size="sm" />
keyboard testing
</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li className="flex items-center gap-2">
<Icon icon="box" size="xs" className="text-foreground-tertiary" />
Tab through all interactive elements
</li>
<li className="flex items-center gap-2">
<Icon icon="box" size="xs" className="text-foreground-tertiary" />
Verify focus is visible at all times
</li>
<li className="flex items-center gap-2">
<Icon icon="box" size="xs" className="text-foreground-tertiary" />
Test Enter/Space activation
</li>
<li className="flex items-center gap-2">
<Icon icon="box" size="xs" className="text-foreground-tertiary" />
Verify Escape closes overlays
</li>
<li className="flex items-center gap-2">
<Icon icon="box" size="xs" className="text-foreground-tertiary" />
Test arrow key navigation
</li>
</ul>
</div>
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-3 flex items-center gap-2">
<Icon icon="search" size="sm" />
screen reader testing
</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li className="flex items-center gap-2">
<Icon icon="box" size="xs" className="text-foreground-tertiary" />
Test with VoiceOver (macOS/iOS)
</li>
<li className="flex items-center gap-2">
<Icon icon="box" size="xs" className="text-foreground-tertiary" />
Test with NVDA (Windows)
</li>
<li className="flex items-center gap-2">
<Icon icon="box" size="xs" className="text-foreground-tertiary" />
Verify all content is announced
</li>
<li className="flex items-center gap-2">
<Icon icon="box" size="xs" className="text-foreground-tertiary" />
Check landmark navigation
</li>
<li className="flex items-center gap-2">
<Icon icon="box" size="xs" className="text-foreground-tertiary" />
Test live region updates
</li>
</ul>
</div>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,99 @@
'use client';
import { Button } from '@tpmjs/ui/Button/Button';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Input } from '@tpmjs/ui/Input/Input';
import { DoDontCard, FieldsetSection, SubSection } from './shared';
export function SectionAccessibility(): React.ReactElement {
return (
<FieldsetSection title="6. accessibility" id="accessibility">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
TPMJS targets WCAG 2.1 AA compliance. Accessibility is not optional—it's
a core requirement for every component.
</p>
<SubSection title="standards">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div className="bg-surface p-6 border border-dashed border-border text-center">
<p className="font-mono text-4xl font-bold text-accent mb-2">AA</p>
<p className="font-mono text-sm text-foreground-secondary">WCAG 2.1 target</p>
</div>
<div className="bg-surface p-6 border border-dashed border-border text-center">
<p className="font-mono text-4xl font-bold text-foreground mb-2">4.5:1</p>
<p className="font-mono text-sm text-foreground-secondary">min contrast (text)</p>
</div>
<div className="bg-surface p-6 border border-dashed border-border text-center">
<p className="font-mono text-4xl font-bold text-foreground mb-2">3:1</p>
<p className="font-mono text-sm text-foreground-secondary">min contrast (UI)</p>
</div>
</div>
</SubSection>
<SubSection title="focus management">
<div className="space-y-6">
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">focus ring design</h4>
<p className="font-sans text-sm text-foreground-secondary mb-4">
Focus rings use the copper accent color with 2px width and 2px offset.
They are <strong>never removed</strong> from interactive elements.
</p>
<div className="flex gap-4">
<Button className="focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
tab to me
</Button>
<Input placeholder="or me" className="max-w-xs" />
</div>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">keyboard navigation</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• <kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">Tab</kbd> moves focus forward through interactive elements</li>
<li>• <kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">Shift+Tab</kbd> moves focus backward</li>
<li>• <kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">Enter</kbd> / <kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">Space</kbd> activates buttons and links</li>
<li>• <kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">Esc</kbd> closes modals and dropdowns</li>
<li>• Arrow keys navigate within components (tabs, radios, menus)</li>
</ul>
</div>
</div>
</SubSection>
<SubSection title="color independence">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<DoDontCard type="do" title="Use icons + color for states">
<div className="flex gap-4 items-center">
<div className="flex items-center gap-2 text-success">
<Icon icon="check" size="sm" />
<span className="text-sm">Published</span>
</div>
<div className="flex items-center gap-2 text-error">
<Icon icon="x" size="sm" />
<span className="text-sm">Failed</span>
</div>
</div>
</DoDontCard>
<DoDontCard type="dont" title="Don't rely on color alone">
<div className="flex gap-4 items-center">
<span className="text-sm text-success">Published</span>
<span className="text-sm text-error">Failed</span>
</div>
</DoDontCard>
</div>
</SubSection>
<SubSection title="screen reader support">
<div className="bg-surface p-6 border border-dashed border-border">
<ul className="space-y-3 text-sm text-foreground-secondary font-sans">
<li>• All interactive elements have accessible names (aria-label or visible text)</li>
<li>• Images include alt text describing content</li>
<li>• Form inputs are associated with labels</li>
<li>• Error messages are announced via aria-live regions</li>
<li>• Loading states communicate progress to screen readers</li>
<li>• Decorative elements are hidden from assistive technology</li>
</ul>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,178 @@
'use client';
import { Badge } from '@tpmjs/ui/Badge/Badge';
import { Button } from '@tpmjs/ui/Button/Button';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@tpmjs/ui/Table/Table';
import { ColorCard, DoDontCard, FieldsetSection, SubSection } from './shared';
export function SectionColors(): React.ReactElement {
return (
<FieldsetSection title="2. color system" id="colors">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
A warm earthy palette with copper accent. Colors are designed for clarity,
hierarchy, and accessibility.
</p>
{/* Color Palette Display */}
<SubSection title="backgrounds">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<ColorCard name="bg" color="bg-background" hex="#F5F3F0" desc="base background" />
<ColorCard name="surface" color="bg-surface" hex="#FFFFFF" desc="cards, panels" />
<ColorCard name="surface-2" color="bg-surface-2" hex="#FAF8F6" desc="elevated surfaces" />
<ColorCard name="surface-3" color="bg-surface-3" hex="#F0EDEA" desc="highest elevation" />
</div>
</SubSection>
<SubSection title="borders">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<ColorCard name="border" color="bg-border" hex="#CEC9C3" desc="default borders" />
<ColorCard name="border-strong" color="bg-border-strong" hex="#857F77" desc="emphasized borders" />
<ColorCard name="border-subtle" color="bg-border-subtle" hex="#E3E0DC" desc="subtle borders" />
</div>
</SubSection>
<SubSection title="text">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<ColorCard name="text" color="bg-foreground" hex="#1A1715" desc="primary text" textLight />
<ColorCard name="text-secondary" color="bg-foreground-secondary" hex="#5C564F" desc="secondary text" textLight />
<ColorCard name="text-tertiary" color="bg-foreground-tertiary" hex="#817B74" desc="tertiary text" textLight />
<ColorCard name="text-muted" color="bg-foreground-muted" hex="#9A958F" desc="muted text" textLight />
</div>
</SubSection>
<SubSection title="accent (copper)">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<ColorCard name="accent" color="bg-accent" hex="#A6592D" desc="brand accent" textLight />
<ColorCard name="accent-strong" color="bg-accent-strong" hex="#8F4722" desc="hover accent" textLight />
<ColorCard name="accent-muted" color="bg-accent-muted" hex="#EDE5DF" desc="soft tint" />
</div>
</SubSection>
<SubSection title="status">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<ColorCard name="success" color="bg-success" hex="#327D52" desc="success states" textLight />
<ColorCard name="warning" color="bg-warning" hex="#D9A020" desc="warning states" />
<ColorCard name="error" color="bg-error" hex="#C44545" desc="error states" textLight />
<ColorCard name="info" color="bg-info" hex="#3380CC" desc="info states" textLight />
</div>
</SubSection>
<SubSection title="status (light backgrounds)">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<ColorCard name="success-light" color="bg-success-light" hex="#E5F3EC" desc="success bg" />
<ColorCard name="warning-light" color="bg-warning-light" hex="#F8F2E0" desc="warning bg" />
<ColorCard name="error-light" color="bg-error-light" hex="#F9EDED" desc="error bg" />
<ColorCard name="info-light" color="bg-info-light" hex="#EDF4FB" desc="info bg" />
</div>
</SubSection>
{/* Color Usage Rules */}
<SubSection title="color usage rules">
<div className="space-y-6 mb-8">
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">when to use copper vs neutral</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• <strong>Copper:</strong> Primary actions, active states, links, key metrics</li>
<li>• <strong>Neutral:</strong> Secondary actions, borders, backgrounds, body text</li>
<li>• <strong>Rule:</strong> Copper should be max ~10% of visible screen area</li>
</ul>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">semantic color priority</h4>
<div className="flex gap-2 items-center">
<Badge variant="error">error</Badge>
<span className="text-foreground-tertiary">{'>'}</span>
<Badge variant="warning">warning</Badge>
<span className="text-foreground-tertiary">{'>'}</span>
<Badge variant="success">success</Badge>
<span className="text-foreground-tertiary">{'>'}</span>
<Badge variant="info">info</Badge>
</div>
<p className="text-xs text-foreground-tertiary mt-3 font-sans">
When multiple states apply, show the highest priority color.
</p>
</div>
</div>
{/* Do/Don't Examples */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<DoDontCard type="do" title="Use copper for primary CTAs">
<div className="flex gap-3">
<Button>publish tool</Button>
<Button variant="secondary">cancel</Button>
</div>
</DoDontCard>
<DoDontCard type="dont" title="Don't use copper for everything">
<div className="flex gap-3">
<Button>publish</Button>
<Button>cancel</Button>
<Button>back</Button>
</div>
</DoDontCard>
<DoDontCard type="do" title="Use status colors with meaning">
<div className="flex gap-3 items-center">
<Badge variant="success">published</Badge>
<Badge variant="warning">pending</Badge>
<Badge variant="error">failed</Badge>
</div>
</DoDontCard>
<DoDontCard type="dont" title="Don't use colors decoratively">
<div className="flex gap-3 items-center">
<Badge variant="success">tools</Badge>
<Badge variant="warning">agents</Badge>
<Badge variant="error">users</Badge>
</div>
</DoDontCard>
</div>
</SubSection>
<SubSection title="contrast ratios (wcag aa)">
<div className="overflow-x-auto">
<Table variant="bordered">
<TableHeader>
<TableRow>
<TableHead>combination</TableHead>
<TableHead>ratio</TableHead>
<TableHead>normal text</TableHead>
<TableHead>large text</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-mono">foreground / background</TableCell>
<TableCell className="font-mono">12.5:1</TableCell>
<TableCell><Badge variant="success" size="sm">pass</Badge></TableCell>
<TableCell><Badge variant="success" size="sm">pass</Badge></TableCell>
</TableRow>
<TableRow>
<TableCell className="font-mono">accent / background</TableCell>
<TableCell className="font-mono">5.2:1</TableCell>
<TableCell><Badge variant="success" size="sm">pass</Badge></TableCell>
<TableCell><Badge variant="success" size="sm">pass</Badge></TableCell>
</TableRow>
<TableRow>
<TableCell className="font-mono">foreground-secondary / background</TableCell>
<TableCell className="font-mono">5.8:1</TableCell>
<TableCell><Badge variant="success" size="sm">pass</Badge></TableCell>
<TableCell><Badge variant="success" size="sm">pass</Badge></TableCell>
</TableRow>
<TableRow>
<TableCell className="font-mono">foreground-tertiary / background</TableCell>
<TableCell className="font-mono">3.9:1</TableCell>
<TableCell><Badge variant="warning" size="sm">large only</Badge></TableCell>
<TableCell><Badge variant="success" size="sm">pass</Badge></TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,178 @@
'use client';
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@tpmjs/ui/Table/Table';
import { FieldsetSection, SubSection } from './shared';
export function SectionComponentAPIs(): React.ReactElement {
return (
<FieldsetSection title="13. component apis" id="api">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Complete API documentation for each component with props, types,
and usage examples.
</p>
<SubSection title="button api">
<div className="space-y-6">
<Table variant="bordered">
<TableHeader>
<TableRow>
<TableHead>prop</TableHead>
<TableHead>type</TableHead>
<TableHead>default</TableHead>
<TableHead>description</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-mono">variant</TableCell>
<TableCell className="font-mono text-xs">&apos;default&apos; | &apos;secondary&apos; | &apos;destructive&apos; | &apos;outline&apos; | &apos;ghost&apos; | &apos;link&apos;</TableCell>
<TableCell className="font-mono">&apos;default&apos;</TableCell>
<TableCell>visual style</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-mono">size</TableCell>
<TableCell className="font-mono text-xs">&apos;sm&apos; | &apos;md&apos; | &apos;lg&apos; | &apos;icon&apos;</TableCell>
<TableCell className="font-mono">&apos;md&apos;</TableCell>
<TableCell>button size</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-mono">loading</TableCell>
<TableCell className="font-mono text-xs">boolean</TableCell>
<TableCell className="font-mono">false</TableCell>
<TableCell>show spinner</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-mono">disabled</TableCell>
<TableCell className="font-mono text-xs">boolean</TableCell>
<TableCell className="font-mono">false</TableCell>
<TableCell>disable interaction</TableCell>
</TableRow>
</TableBody>
</Table>
<CodeBlock
code={`// Basic usage
<Button>click me</Button>
// With variant and size
<Button variant="destructive" size="lg">
delete tool
</Button>
// Loading state
<Button loading>publishing...</Button>
// With icon
<Button>
<Icon icon="plus" size="sm" className="mr-2" />
add tool
</Button>`}
language="tsx"
/>
</div>
</SubSection>
<SubSection title="input api">
<div className="space-y-6">
<Table variant="bordered">
<TableHeader>
<TableRow>
<TableHead>prop</TableHead>
<TableHead>type</TableHead>
<TableHead>default</TableHead>
<TableHead>description</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-mono">state</TableCell>
<TableCell className="font-mono text-xs">&apos;default&apos; | &apos;error&apos; | &apos;success&apos;</TableCell>
<TableCell className="font-mono">&apos;default&apos;</TableCell>
<TableCell>visual state</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-mono">size</TableCell>
<TableCell className="font-mono text-xs">&apos;sm&apos; | &apos;md&apos; | &apos;lg&apos;</TableCell>
<TableCell className="font-mono">&apos;md&apos;</TableCell>
<TableCell>input size</TableCell>
</TableRow>
</TableBody>
</Table>
<CodeBlock
code={`// Basic usage
<Input placeholder="enter text..." />
// With error state
<Input
state="error"
placeholder="invalid input"
/>
// With FormField wrapper
<FormField
label="Email"
error="Invalid email address"
>
<Input state="error" />
</FormField>`}
language="tsx"
/>
</div>
</SubSection>
<SubSection title="anti-patterns">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-error-light p-6 border border-error">
<h4 className="font-mono text-sm font-medium mb-4 text-error">don&apos;t do this</h4>
<CodeBlock
code={`// ❌ Using inline styles
<Button style={{ backgroundColor: 'red' }}>
delete
</Button>
// ❌ Nesting buttons
<Button>
<Button>nested</Button>
</Button>
// ❌ Missing accessible name
<Button size="icon">
<Icon icon="plus" />
</Button>`}
language="tsx"
size="sm"
/>
</div>
<div className="bg-success-light p-6 border border-success">
<h4 className="font-mono text-sm font-medium mb-4 text-success">do this instead</h4>
<CodeBlock
code={`// ✅ Using variants
<Button variant="destructive">
delete
</Button>
// ✅ Single button
<Button>action</Button>
// ✅ With aria-label
<Button size="icon" aria-label="Add item">
<Icon icon="plus" />
</Button>`}
language="tsx"
size="sm"
/>
</div>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,286 @@
'use client';
import { Badge } from '@tpmjs/ui/Badge/Badge';
import { Button } from '@tpmjs/ui/Button/Button';
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@tpmjs/ui/Card/Card';
import { Checkbox } from '@tpmjs/ui/Checkbox/Checkbox';
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
import { FormField } from '@tpmjs/ui/FormField/FormField';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Input } from '@tpmjs/ui/Input/Input';
import { Radio } from '@tpmjs/ui/Radio/Radio';
import { RadioGroup } from '@tpmjs/ui/Radio/RadioGroup';
import { Select } from '@tpmjs/ui/Select/Select';
import { Spinner } from '@tpmjs/ui/Spinner/Spinner';
import { Switch } from '@tpmjs/ui/Switch/Switch';
import { Tabs } from '@tpmjs/ui/Tabs/Tabs';
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@tpmjs/ui/Table/Table';
import { Textarea } from '@tpmjs/ui/Textarea/Textarea';
import { FieldsetSection, SubSection } from './shared';
interface SectionComponentsProps {
activeTab: string;
onTabChange: (tab: string) => void;
radioValue: string;
onRadioChange: (value: string) => void;
}
export function SectionComponents({
activeTab,
onTabChange,
radioValue,
onRadioChange
}: SectionComponentsProps): React.ReactElement {
return (
<FieldsetSection title="12. components" id="components">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Complete component catalog with all variants and states.
</p>
{/* Button */}
<SubSection title="button">
<div className="space-y-6">
<div>
<p className="font-mono text-xs text-foreground-secondary mb-3">variants</p>
<div className="flex flex-wrap gap-4 items-center">
<Button variant="default">default</Button>
<Button variant="secondary">secondary</Button>
<Button variant="destructive">destructive</Button>
<Button variant="outline">outline</Button>
<Button variant="outline-dotted">outline dotted</Button>
<Button variant="blueprint">blueprint</Button>
<Button variant="ghost">ghost</Button>
<Button variant="link">link</Button>
</div>
</div>
<div>
<p className="font-mono text-xs text-foreground-secondary mb-3">sizes</p>
<div className="flex flex-wrap gap-4 items-center">
<Button size="sm">small</Button>
<Button size="md">medium</Button>
<Button size="lg">large</Button>
<Button size="icon"><Icon icon="plus" size="sm" /></Button>
</div>
</div>
<div>
<p className="font-mono text-xs text-foreground-secondary mb-3">states</p>
<div className="flex flex-wrap gap-4 items-center">
<Button>normal</Button>
<Button disabled>disabled</Button>
<Button loading>loading</Button>
</div>
</div>
</div>
</SubSection>
{/* Badge */}
<SubSection title="badge">
<div className="space-y-6">
<div>
<p className="font-mono text-xs text-foreground-secondary mb-3">variants</p>
<div className="flex flex-wrap gap-4 items-center">
<Badge variant="default">default</Badge>
<Badge variant="secondary">secondary</Badge>
<Badge variant="outline">outline</Badge>
<Badge variant="success">success</Badge>
<Badge variant="error">error</Badge>
<Badge variant="warning">warning</Badge>
<Badge variant="info">info</Badge>
</div>
</div>
<div>
<p className="font-mono text-xs text-foreground-secondary mb-3">sizes</p>
<div className="flex flex-wrap gap-4 items-center">
<Badge size="sm">small</Badge>
<Badge size="md">medium</Badge>
<Badge size="lg">large</Badge>
</div>
</div>
</div>
</SubSection>
{/* Card */}
<SubSection title="card">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{(['default', 'elevated', 'outline', 'blueprint', 'featured', 'brutalist'] as const).map((variant) => (
<Card key={variant} variant={variant}>
<CardHeader>
<CardTitle>{variant} card</CardTitle>
<CardDescription>card variant example</CardDescription>
</CardHeader>
<CardContent>
<p className="text-foreground-secondary text-sm">card content goes here.</p>
</CardContent>
<CardFooter>
<Button size="sm">action</Button>
</CardFooter>
</Card>
))}
</div>
</SubSection>
{/* Form Elements */}
<SubSection title="form elements">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="space-y-6">
<FormField label="input">
<Input placeholder="enter text..." />
</FormField>
<FormField label="textarea">
<Textarea placeholder="enter text..." rows={3} />
</FormField>
<FormField label="select">
<Select
placeholder="select option..."
options={[
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
]}
/>
</FormField>
</div>
<div className="space-y-6">
<div>
<p className="font-mono text-xs text-foreground-secondary mb-3">checkbox</p>
<div className="flex gap-6">
<Checkbox label="unchecked" />
<Checkbox label="checked" checked onChange={() => {}} />
<Checkbox label="disabled" disabled />
</div>
</div>
<div>
<p className="font-mono text-xs text-foreground-secondary mb-3">switch</p>
<div className="flex gap-6">
<Switch label="off" />
<Switch label="on" checked onChange={() => {}} />
<Switch label="disabled" disabled />
</div>
</div>
<div>
<p className="font-mono text-xs text-foreground-secondary mb-3">radio group</p>
<RadioGroup
name="demo-radio"
value={radioValue}
onChange={onRadioChange}
orientation="horizontal"
>
<Radio value="option1" label="option 1" />
<Radio value="option2" label="option 2" />
<Radio value="option3" label="option 3" />
</RadioGroup>
</div>
</div>
</div>
</SubSection>
{/* Tabs */}
<SubSection title="tabs">
<div className="space-y-6">
<Tabs
tabs={[
{ id: 'all', label: 'all items', count: 128 },
{ id: 'active', label: 'active', count: 42 },
{ id: 'archived', label: 'archived', count: 86 },
]}
activeTab={activeTab}
onTabChange={onTabChange}
variant="default"
/>
</div>
</SubSection>
{/* Table */}
<SubSection title="table">
<Table>
<TableCaption>example data table</TableCaption>
<TableHeader>
<TableRow>
<TableHead>name</TableHead>
<TableHead>category</TableHead>
<TableHead className="text-right">downloads</TableHead>
<TableHead className="text-right">score</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-mono">@tpmjs/parser</TableCell>
<TableCell>utility</TableCell>
<TableCell className="text-right font-mono">125,432</TableCell>
<TableCell className="text-right font-mono">0.92</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-mono">@tpmjs/validator</TableCell>
<TableCell>validation</TableCell>
<TableCell className="text-right font-mono">89,231</TableCell>
<TableCell className="text-right font-mono">0.87</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-mono">@tpmjs/transform</TableCell>
<TableCell>data</TableCell>
<TableCell className="text-right font-mono">45,678</TableCell>
<TableCell className="text-right font-mono">0.81</TableCell>
</TableRow>
</TableBody>
</Table>
</SubSection>
{/* Spinner */}
<SubSection title="spinner">
<div className="flex flex-wrap gap-12 items-center">
<div className="text-center">
<Spinner size="xs" />
<p className="font-mono text-xs text-foreground-secondary mt-4">xs</p>
</div>
<div className="text-center">
<Spinner size="sm" />
<p className="font-mono text-xs text-foreground-secondary mt-4">sm</p>
</div>
<div className="text-center">
<Spinner size="md" />
<p className="font-mono text-xs text-foreground-secondary mt-4">md</p>
</div>
<div className="text-center">
<Spinner size="lg" />
<p className="font-mono text-xs text-foreground-secondary mt-4">lg</p>
</div>
<div className="text-center">
<Spinner size="xl" />
<p className="font-mono text-xs text-foreground-secondary mt-4">xl</p>
</div>
</div>
</SubSection>
{/* Code Block */}
<SubSection title="code block">
<CodeBlock
code={`import { Button } from '@tpmjs/ui/Button/Button';
export function MyComponent() {
return (
<Button variant="default">
click me
</Button>
);
}`}
language="typescript"
/>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,144 @@
'use client';
import { Button } from '@tpmjs/ui/Button/Button';
import {
Card,
CardContent,
} from '@tpmjs/ui/Card/Card';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Spinner } from '@tpmjs/ui/Spinner/Spinner';
import { DoDontCard, FieldsetSection, SubSection } from './shared';
export function SectionContent(): React.ReactElement {
return (
<FieldsetSection title="8. content guidelines" id="content">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Voice and tone guidelines for consistent, helpful communication
across the platform.
</p>
<SubSection title="voice & tone">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div className="bg-surface p-6 border-2 border-success">
<h4 className="font-mono text-sm font-medium mb-4 text-success">we are</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• <strong>Technical:</strong> Precise, accurate, developer-friendly</li>
<li>• <strong>Direct:</strong> Say what needs to be said, no fluff</li>
<li>• <strong>Helpful:</strong> Guide users to success</li>
<li>• <strong>Neutral:</strong> Professional, not corporate</li>
</ul>
</div>
<div className="bg-surface p-6 border-2 border-error">
<h4 className="font-mono text-sm font-medium mb-4 text-error">we are not</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• <strong>Marketing-speak:</strong> No "revolutionary" or "game-changing"</li>
<li>• <strong>Cute:</strong> No jokes, puns, or playful language</li>
<li>• <strong>Vague:</strong> No "something went wrong"</li>
<li>• <strong>Condescending:</strong> No "simply" or "just"</li>
</ul>
</div>
</div>
</SubSection>
<SubSection title="microcopy rules">
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<DoDontCard type="do" title="Use action verbs for buttons">
<div className="flex gap-3">
<Button size="sm">publish</Button>
<Button size="sm" variant="secondary">save draft</Button>
<Button size="sm" variant="destructive">delete</Button>
</div>
</DoDontCard>
<DoDontCard type="dont" title="Avoid generic labels">
<div className="flex gap-3">
<Button size="sm">submit</Button>
<Button size="sm" variant="secondary">ok</Button>
<Button size="sm" variant="destructive">yes</Button>
</div>
</DoDontCard>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">error message format</h4>
<p className="font-sans text-sm text-foreground-secondary mb-4">
Error messages should explain <strong>what happened</strong> and
<strong> what to do next</strong>.
</p>
<div className="space-y-3">
<div className="p-3 bg-error-light border border-error text-sm">
<strong>Good:</strong> "API key is invalid. Generate a new key in your dashboard settings."
</div>
<div className="p-3 bg-error-light border border-error text-sm">
<strong>Bad:</strong> "Error: Invalid credentials"
</div>
</div>
</div>
</div>
</SubSection>
<SubSection title="empty states">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card variant="outline">
<CardContent className="text-center py-12">
<Icon icon="folder" size="lg" className="text-foreground-tertiary mx-auto mb-4" />
<p className="font-mono text-sm text-foreground mb-2">no tools yet</p>
<p className="font-sans text-xs text-foreground-secondary mb-4">
Publish your first tool to get started.
</p>
<Button size="sm">publish tool</Button>
</CardContent>
</Card>
<Card variant="outline">
<CardContent className="text-center py-12">
<Icon icon="search" size="lg" className="text-foreground-tertiary mx-auto mb-4" />
<p className="font-mono text-sm text-foreground mb-2">no results found</p>
<p className="font-sans text-xs text-foreground-secondary mb-4">
Try adjusting your search or filters.
</p>
<Button size="sm" variant="outline">clear filters</Button>
</CardContent>
</Card>
<Card variant="outline">
<CardContent className="text-center py-12">
<Icon icon="alertCircle" size="lg" className="text-error mx-auto mb-4" />
<p className="font-mono text-sm text-foreground mb-2">failed to load</p>
<p className="font-sans text-xs text-foreground-secondary mb-4">
Check your connection and try again.
</p>
<Button size="sm" variant="outline">retry</Button>
</CardContent>
</Card>
</div>
</SubSection>
<SubSection title="loading states">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">skeleton loading</h4>
<p className="font-sans text-xs text-foreground-secondary mb-4">
Use for content that will load quickly (&lt;2s).
</p>
<div className="space-y-3">
<div className="h-4 bg-muted animate-pulse w-3/4" />
<div className="h-4 bg-muted animate-pulse w-1/2" />
<div className="h-4 bg-muted animate-pulse w-5/6" />
</div>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">spinner loading</h4>
<p className="font-sans text-xs text-foreground-secondary mb-4">
Use for actions or longer operations.
</p>
<div className="flex items-center gap-4">
<Spinner size="sm" />
<span className="font-mono text-sm text-foreground-secondary">publishing tool...</span>
</div>
</div>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,419 @@
'use client';
import { Badge } from '@tpmjs/ui/Badge/Badge';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@tpmjs/ui/Table/Table';
import { FieldsetSection, SubSection, DoDontCard } from './shared';
const glossaryTerms = [
{ term: 'Tool', definition: 'A reusable MCP server or utility that can be installed via npm', avoid: 'Package, Module, Plugin' },
{ term: 'Agent', definition: 'An AI-powered assistant that uses tools to complete tasks', avoid: 'Bot, Assistant, AI' },
{ term: 'Collection', definition: 'A curated group of related tools', avoid: 'Bundle, Set, Kit' },
{ term: 'Publish', definition: 'Release a new version to the registry', avoid: 'Deploy, Ship, Push' },
{ term: 'Install', definition: 'Add a tool to your project dependencies', avoid: 'Download, Get, Add' },
{ term: 'Execute', definition: 'Run a tool with specific parameters', avoid: 'Invoke, Call, Trigger' },
];
const verbConsistency = [
{ action: 'Remove permanently', use: 'Delete', avoid: 'Remove, Erase, Destroy' },
{ action: 'Remove from list', use: 'Remove', avoid: 'Delete, Drop, Clear' },
{ action: 'Cancel access', use: 'Revoke', avoid: 'Remove, Delete, Cancel' },
{ action: 'Make inactive', use: 'Disable', avoid: 'Turn off, Deactivate' },
{ action: 'Make active', use: 'Enable', avoid: 'Turn on, Activate' },
{ action: 'Start new', use: 'Create', avoid: 'Add, New, Make' },
{ action: 'Change existing', use: 'Edit', avoid: 'Modify, Update, Change' },
{ action: 'Look at details', use: 'View', avoid: 'See, Show, Open' },
];
const iconUsageRules = [
{ icon: 'check', usage: 'Success, completion, enabled', size: 'sm-md' },
{ icon: 'x', usage: 'Close, cancel, remove, disabled', size: 'sm-md' },
{ icon: 'alertCircle', usage: 'Error, critical issue', size: 'sm-md' },
{ icon: 'alertTriangle', usage: 'Warning, caution needed', size: 'sm-md' },
{ icon: 'info', usage: 'Information, help', size: 'sm-md' },
{ icon: 'loader', usage: 'Loading, processing', size: 'sm-md' },
{ icon: 'search', usage: 'Search input, find action', size: 'sm' },
{ icon: 'edit', usage: 'Edit, configuration', size: 'sm-md' },
{ icon: 'chevronRight', usage: 'Navigate forward, expand', size: 'xs-sm' },
{ icon: 'chevronDown', usage: 'Expand, dropdown', size: 'xs-sm' },
{ icon: 'plus', usage: 'Add, create new', size: 'sm-md' },
{ icon: 'moreHorizontal', usage: 'More options menu', size: 'sm' },
] as const;
export function SectionContentGuidelines(): React.ReactElement {
return (
<FieldsetSection title="20. content & writing" id="content-guidelines">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Consistent language and terminology across the platform.
These guidelines ensure clarity for users and maintainability for developers.
</p>
<SubSection title="glossary">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Use these terms consistently throughout the platform.
</p>
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>term</TableHead>
<TableHead>definition</TableHead>
<TableHead>avoid</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{glossaryTerms.map((item) => (
<TableRow key={item.term}>
<TableCell className="font-mono text-sm font-medium">{item.term}</TableCell>
<TableCell className="text-sm text-foreground-secondary">{item.definition}</TableCell>
<TableCell>
<span className="text-xs text-foreground-tertiary line-through">{item.avoid}</span>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</SubSection>
<SubSection title="verb consistency">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Use these verbs for common actions to maintain consistency.
</p>
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>action</TableHead>
<TableHead>use</TableHead>
<TableHead>avoid</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{verbConsistency.map((item) => (
<TableRow key={item.action}>
<TableCell className="text-sm text-foreground-secondary">{item.action}</TableCell>
<TableCell>
<Badge variant="default" size="sm">{item.use}</Badge>
</TableCell>
<TableCell>
<span className="text-xs text-foreground-tertiary line-through">{item.avoid}</span>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</SubSection>
<SubSection title="capitalization">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Follow these capitalization rules for UI text.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<DoDontCard type="do" title="Lowercase for UI elements">
<div className="space-y-2 text-sm font-mono">
<div className="p-2 bg-surface-2">create tool</div>
<div className="p-2 bg-surface-2">view details</div>
<div className="p-2 bg-surface-2">my collections</div>
</div>
</DoDontCard>
<DoDontCard type="dont" title="Avoid title case in buttons">
<div className="space-y-2 text-sm font-mono">
<div className="p-2 bg-surface-2">Create Tool</div>
<div className="p-2 bg-surface-2">View Details</div>
<div className="p-2 bg-surface-2">My Collections</div>
</div>
</DoDontCard>
</div>
<div className="mt-4 bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-3">exceptions</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• Proper nouns: "GitHub", "Anthropic", "Claude"</li>
<li>• Product names: "TPMJS", "MCP"</li>
<li>• Start of sentences in paragraphs</li>
</ul>
</div>
</SubSection>
<SubSection title="numbers & formatting">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Format numbers and data consistently.
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-xs text-foreground-tertiary mb-3">numbers</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-foreground-secondary">Small:</span>
<span className="font-mono">1, 42, 100</span>
</div>
<div className="flex justify-between">
<span className="text-foreground-secondary">Large:</span>
<span className="font-mono">1,234</span>
</div>
<div className="flex justify-between">
<span className="text-foreground-secondary">Abbreviated:</span>
<span className="font-mono">12.5k, 1.2M</span>
</div>
</div>
</div>
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-xs text-foreground-tertiary mb-3">dates</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-foreground-secondary">Full:</span>
<span className="font-mono">Jan 15, 2025</span>
</div>
<div className="flex justify-between">
<span className="text-foreground-secondary">Relative:</span>
<span className="font-mono">2 hours ago</span>
</div>
<div className="flex justify-between">
<span className="text-foreground-secondary">ISO:</span>
<span className="font-mono">2025-01-15</span>
</div>
</div>
</div>
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-xs text-foreground-tertiary mb-3">units</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-foreground-secondary">Bytes:</span>
<span className="font-mono">2.4 MB</span>
</div>
<div className="flex justify-between">
<span className="text-foreground-secondary">Duration:</span>
<span className="font-mono">3m 24s</span>
</div>
<div className="flex justify-between">
<span className="text-foreground-secondary">Version:</span>
<span className="font-mono">v1.2.3</span>
</div>
</div>
</div>
</div>
</SubSection>
<SubSection title="error messages">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Write error messages that are helpful and actionable.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<DoDontCard type="do" title="Helpful and specific">
<div className="space-y-3">
<div className="p-3 bg-error-light border-l-2 border-error text-sm">
<p className="font-medium">Invalid email format</p>
<p className="text-foreground-secondary text-xs mt-1">Please enter a valid email address like user@example.com</p>
</div>
<div className="p-3 bg-error-light border-l-2 border-error text-sm">
<p className="font-medium">Tool name already exists</p>
<p className="text-foreground-secondary text-xs mt-1">Choose a different name or update the existing tool</p>
</div>
</div>
</DoDontCard>
<DoDontCard type="dont" title="Vague or technical">
<div className="space-y-3">
<div className="p-3 bg-error-light border-l-2 border-error text-sm">
<p className="font-medium">Invalid input</p>
</div>
<div className="p-3 bg-error-light border-l-2 border-error text-sm">
<p className="font-medium">Error: EEXIST</p>
</div>
<div className="p-3 bg-error-light border-l-2 border-error text-sm">
<p className="font-medium">Something went wrong</p>
</div>
</div>
</DoDontCard>
</div>
</SubSection>
</FieldsetSection>
);
}
export function SectionIconSystem(): React.ReactElement {
return (
<FieldsetSection title="21. icon system" id="icon-system">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Icons from Lucide React, used consistently across components.
All icons use 2px stroke weight and are available in multiple sizes.
</p>
<SubSection title="icon library">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Source: <a href="https://lucide.dev" className="text-accent hover:underline">Lucide React</a>.
Icons should be imported from <code className="text-xs bg-surface-2 px-1">@tpmjs/ui/Icon/Icon</code>.
</p>
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">common icons</h4>
<div className="grid grid-cols-4 md:grid-cols-8 gap-4">
{iconUsageRules.map((item) => (
<div key={item.icon} className="flex flex-col items-center gap-2 p-3 bg-surface-2">
<Icon icon={item.icon} size="md" />
<span className="font-mono text-[10px] text-foreground-tertiary">{item.icon}</span>
</div>
))}
</div>
</div>
</SubSection>
<SubSection title="icon sizes">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Choose icon size based on context and surrounding content.
</p>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-surface p-4 border border-dashed border-border text-center">
<Icon icon="edit" size="xs" className="mx-auto" />
<p className="font-mono text-xs mt-2">xs (12px)</p>
<p className="text-[10px] text-foreground-tertiary mt-1">Inline, tight spaces</p>
</div>
<div className="bg-surface p-4 border border-dashed border-border text-center">
<Icon icon="edit" size="sm" className="mx-auto" />
<p className="font-mono text-xs mt-2">sm (16px)</p>
<p className="text-[10px] text-foreground-tertiary mt-1">Buttons, inputs</p>
</div>
<div className="bg-surface p-4 border border-dashed border-border text-center">
<Icon icon="edit" size="md" className="mx-auto" />
<p className="font-mono text-xs mt-2">md (20px)</p>
<p className="text-[10px] text-foreground-tertiary mt-1">Default, nav items</p>
</div>
<div className="bg-surface p-4 border border-dashed border-border text-center">
<Icon icon="edit" size="lg" className="mx-auto" />
<p className="font-mono text-xs mt-2">lg (24px)</p>
<p className="text-[10px] text-foreground-tertiary mt-1">Empty states, hero</p>
</div>
</div>
</SubSection>
<SubSection title="icon usage guidelines">
<p className="font-sans text-sm text-foreground-secondary mb-4">
When and how to use specific icons.
</p>
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>icon</TableHead>
<TableHead>usage</TableHead>
<TableHead>recommended size</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{iconUsageRules.map((item) => (
<TableRow key={item.icon}>
<TableCell>
<div className="flex items-center gap-2">
<Icon icon={item.icon} size="sm" />
<code className="text-xs">{item.icon}</code>
</div>
</TableCell>
<TableCell className="text-sm text-foreground-secondary">{item.usage}</TableCell>
<TableCell>
<Badge variant="outline" size="sm">{item.size}</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</SubSection>
<SubSection title="icon styling">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Consistent styling rules for icons in different contexts.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">colors</h4>
<div className="space-y-3">
<div className="flex items-center gap-3">
<Icon icon="check" size="sm" className="text-success" />
<span className="text-sm text-foreground-secondary">Success states</span>
</div>
<div className="flex items-center gap-3">
<Icon icon="alertCircle" size="sm" className="text-error" />
<span className="text-sm text-foreground-secondary">Error states</span>
</div>
<div className="flex items-center gap-3">
<Icon icon="alertTriangle" size="sm" className="text-warning" />
<span className="text-sm text-foreground-secondary">Warning states</span>
</div>
<div className="flex items-center gap-3">
<Icon icon="info" size="sm" className="text-accent" />
<span className="text-sm text-foreground-secondary">Information</span>
</div>
<div className="flex items-center gap-3">
<Icon icon="edit" size="sm" className="text-foreground-secondary" />
<span className="text-sm text-foreground-secondary">Neutral/default</span>
</div>
</div>
</div>
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">with text</h4>
<div className="space-y-3">
<button className="flex items-center gap-2 px-3 py-2 bg-accent text-white">
<Icon icon="plus" size="sm" />
<span className="font-mono text-sm">create tool</span>
</button>
<button className="flex items-center gap-2 px-3 py-2 border border-border">
<span className="font-mono text-sm">settings</span>
<Icon icon="chevronRight" size="xs" />
</button>
<div className="flex items-center gap-2 text-foreground-secondary">
<Icon icon="clock" size="xs" />
<span className="text-xs">2 hours ago</span>
</div>
</div>
</div>
</div>
</SubSection>
<SubSection title="adding new icons">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Process for adding new icons to the system.
</p>
<div className="bg-surface p-4 border border-dashed border-border">
<ol className="space-y-3 text-sm text-foreground-secondary font-sans">
<li className="flex gap-3">
<span className="font-mono text-accent">1.</span>
Check if a suitable icon exists in <a href="https://lucide.dev/icons" className="text-accent hover:underline">Lucide</a>
</li>
<li className="flex gap-3">
<span className="font-mono text-accent">2.</span>
Add the icon import to <code className="text-xs bg-surface-2 px-1">packages/ui/src/Icon/icons.ts</code>
</li>
<li className="flex gap-3">
<span className="font-mono text-accent">3.</span>
Add the icon name to the <code className="text-xs bg-surface-2 px-1">IconName</code> type
</li>
<li className="flex gap-3">
<span className="font-mono text-accent">4.</span>
Document the usage in this style guide
</li>
</ol>
<div className="mt-4 p-3 bg-surface-2">
<p className="font-mono text-xs text-foreground-tertiary mb-2">example: adding a new icon</p>
<pre className="text-xs font-mono overflow-x-auto">
{`// icons.ts
import { Heart } from 'lucide-react';
export const icons = {
// ...existing icons
heart: Heart,
};
export type IconName = keyof typeof icons;`}
</pre>
</div>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,142 @@
'use client';
import { AnimatedCounter } from '@tpmjs/ui/AnimatedCounter/AnimatedCounter';
import { Button } from '@tpmjs/ui/Button/Button';
import { ProgressBar } from '@tpmjs/ui/ProgressBar/ProgressBar';
import { StatCard } from '@tpmjs/ui/StatCard/StatCard';
import { FieldsetSection, SubSection } from './shared';
interface SectionDataVizProps {
counterValue: number;
onCounterChange: (value: number) => void;
}
export function SectionDataViz({ counterValue, onCounterChange }: SectionDataVizProps): React.ReactElement {
return (
<FieldsetSection title="9. data visualization" id="data-viz">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Guidelines for charts, metrics, and data displays. Semantic colors only—
no rainbow charts.
</p>
<SubSection title="data color palette">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
<div className="bg-accent p-4 text-center">
<p className="font-mono text-sm text-white">primary data</p>
</div>
<div className="bg-foreground-secondary p-4 text-center">
<p className="font-mono text-sm text-white">secondary data</p>
</div>
<div className="bg-foreground-tertiary p-4 text-center">
<p className="font-mono text-sm text-white">tertiary data</p>
</div>
<div className="bg-border-strong p-4 text-center">
<p className="font-mono text-sm text-white">baseline</p>
</div>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">color rules for data</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• Use copper for primary/highlighted data series</li>
<li>• Use grayscale for comparison and secondary series</li>
<li>• Reserve semantic colors (success/warning/error) for threshold indicators</li>
<li>• Create emphasis through opacity variation, not hue changes</li>
<li>• Maximum 4 colors in any single visualization</li>
</ul>
</div>
</SubSection>
<SubSection title="quality score visualization">
<p className="font-sans text-sm text-foreground-secondary mb-6">
Tool quality scores use a consistent visual language across the platform.
</p>
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<StatCard
label="excellent"
value={95}
suffix="%"
showBar
barProgress={95}
/>
<StatCard
label="good"
value={75}
suffix="%"
showBar
barProgress={75}
/>
<StatCard
label="fair"
value={50}
suffix="%"
showBar
barProgress={50}
/>
<StatCard
label="poor"
value={25}
suffix="%"
showBar
barProgress={25}
/>
</div>
</SubSection>
<SubSection title="progress indicators">
<div className="space-y-6 max-w-xl">
<div>
<p className="font-mono text-sm text-foreground-secondary mb-2">primary (default)</p>
<ProgressBar value={75} variant="primary" />
</div>
<div>
<p className="font-mono text-sm text-foreground-secondary mb-2">success (completion)</p>
<ProgressBar value={100} variant="success" />
</div>
<div>
<p className="font-mono text-sm text-foreground-secondary mb-2">warning (approaching limit)</p>
<ProgressBar value={85} variant="warning" />
</div>
<div>
<p className="font-mono text-sm text-foreground-secondary mb-2">danger (at limit)</p>
<ProgressBar value={98} variant="danger" />
</div>
</div>
</SubSection>
<SubSection title="animated counters">
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<div className="text-center">
<div className="font-mono text-3xl font-semibold">
<AnimatedCounter value={counterValue} />
</div>
<p className="font-mono text-xs text-foreground-secondary mt-2">downloads</p>
</div>
<div className="text-center">
<div className="font-mono text-3xl font-semibold">
<AnimatedCounter value={Math.floor(counterValue * 0.8)} />
</div>
<p className="font-mono text-xs text-foreground-secondary mt-2">users</p>
</div>
<div className="text-center">
<div className="font-mono text-3xl font-semibold">
<AnimatedCounter value={Math.floor(counterValue * 0.05)} />
</div>
<p className="font-mono text-xs text-foreground-secondary mt-2">tools</p>
</div>
<div className="text-center">
<div className="font-mono text-3xl font-semibold">
<AnimatedCounter value={Math.floor(counterValue * 0.01)} />%
</div>
<p className="font-mono text-xs text-foreground-secondary mt-2">uptime</p>
</div>
</div>
<div className="flex gap-2 mt-6 justify-center">
<Button size="sm" onClick={() => onCounterChange(counterValue + 1000)}>+1000</Button>
<Button size="sm" variant="outline" onClick={() => onCounterChange(Math.max(0, counterValue - 1000))}>-1000</Button>
<Button size="sm" variant="secondary" onClick={() => onCounterChange(Math.floor(Math.random() * 100000))}>random</Button>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,102 @@
'use client';
import { Button } from '@tpmjs/ui/Button/Button';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { FieldsetSection, SubSection } from './shared';
export function SectionIcons(): React.ReactElement {
return (
<FieldsetSection title="10. iconography" id="icons">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Consistent icon usage for clear visual communication.
</p>
<SubSection title="icon sizes">
<div className="flex flex-wrap gap-8 items-end mb-8">
<div className="text-center">
<Icon icon="github" size="xs" />
<p className="font-mono text-xs text-foreground-secondary mt-3">xs (12px)</p>
</div>
<div className="text-center">
<Icon icon="github" size="sm" />
<p className="font-mono text-xs text-foreground-secondary mt-3">sm (16px)</p>
</div>
<div className="text-center">
<Icon icon="github" size="md" />
<p className="font-mono text-xs text-foreground-secondary mt-3">md (20px)</p>
</div>
<div className="text-center">
<Icon icon="github" size="lg" />
<p className="font-mono text-xs text-foreground-secondary mt-3">lg (24px)</p>
</div>
</div>
</SubSection>
<SubSection title="usage rules">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">icon-only buttons</h4>
<p className="font-sans text-sm text-foreground-secondary mb-4">
Only allowed when the action is universally understood
(close, search, menu) AND space is limited.
</p>
<div className="flex gap-2">
<Button size="icon" variant="ghost"><Icon icon="x" size="sm" /></Button>
<Button size="icon" variant="ghost"><Icon icon="search" size="sm" /></Button>
<Button size="icon" variant="ghost"><Icon icon="menu" size="sm" /></Button>
</div>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">icon + label (preferred)</h4>
<p className="font-sans text-sm text-foreground-secondary mb-4">
Always include text labels when space permits for clarity.
</p>
<div className="flex gap-2">
<Button size="sm"><Icon icon="plus" size="sm" className="mr-2" /> add tool</Button>
<Button size="sm" variant="outline"><Icon icon="upload" size="sm" className="mr-2" /> upload</Button>
</div>
</div>
</div>
</SubSection>
<SubSection title="semantic icons">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="bg-surface p-4 border border-dashed border-border flex items-center gap-3">
<Icon icon="check" size="md" className="text-success" />
<span className="font-mono text-sm">success</span>
</div>
<div className="bg-surface p-4 border border-dashed border-border flex items-center gap-3">
<Icon icon="alertCircle" size="md" className="text-warning" />
<span className="font-mono text-sm">warning</span>
</div>
<div className="bg-surface p-4 border border-dashed border-border flex items-center gap-3">
<Icon icon="x" size="md" className="text-error" />
<span className="font-mono text-sm">error</span>
</div>
<div className="bg-surface p-4 border border-dashed border-border flex items-center gap-3">
<Icon icon="info" size="md" className="text-info" />
<span className="font-mono text-sm">info</span>
</div>
</div>
</SubSection>
<SubSection title="all icons">
<div className="grid grid-cols-6 md:grid-cols-8 lg:grid-cols-12 gap-6">
{[
'copy', 'github', 'check', 'x', 'chevronDown', 'chevronRight',
'clock', 'link', 'sun', 'moon', 'discord', 'menu',
'folder', 'plus', 'trash', 'edit', 'search', 'loader',
'upload', 'alertCircle', 'globe', 'terminal', 'puzzle', 'message',
'key', 'info', 'send', 'home', 'user', 'heart',
'star', 'externalLink', 'arrowLeft', 'box', 'alertTriangle',
].map((iconName) => (
<div key={iconName} className="flex flex-col items-center gap-2" title={iconName}>
<Icon icon={iconName as Parameters<typeof Icon>[0]['icon']} size="md" className="text-foreground" />
<span className="font-mono text-[10px] text-foreground-tertiary truncate max-w-full">{iconName}</span>
</div>
))}
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,151 @@
'use client';
import { Badge } from '@tpmjs/ui/Badge/Badge';
import { Button } from '@tpmjs/ui/Button/Button';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@tpmjs/ui/Table/Table';
import { FieldsetSection, SubSection, TokenRow } from './shared';
interface SectionLayoutProps {
density: 'compact' | 'comfortable' | 'spacious';
onDensityChange: (density: 'compact' | 'comfortable' | 'spacious') => void;
}
export function SectionLayout({ density, onDensityChange }: SectionLayoutProps): React.ReactElement {
return (
<FieldsetSection title="7. layout & responsiveness" id="layout">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Mobile-first responsive design with clear breakpoints and density modes
for different use cases.
</p>
<SubSection title="breakpoints">
<div className="space-y-4 mb-8">
<TokenRow name="sm" value="640px" preview={<Badge size="sm">mobile landscape</Badge>} />
<TokenRow name="md" value="768px" preview={<Badge size="sm">tablet</Badge>} />
<TokenRow name="lg" value="1024px" preview={<Badge size="sm">desktop</Badge>} />
<TokenRow name="xl" value="1280px" preview={<Badge size="sm">wide</Badge>} />
<TokenRow name="2xl" value="1536px" preview={<Badge size="sm">ultrawide</Badge>} />
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">responsive behaviors</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• <strong>Sidebar:</strong> Collapsible on mobile, visible on lg+</li>
<li>• <strong>Tables:</strong> Horizontal scroll on mobile, full width on lg+</li>
<li>• <strong>Cards:</strong> Single column on mobile, grid on md+</li>
<li>• <strong>Navigation:</strong> Hamburger menu on mobile, horizontal on lg+</li>
</ul>
</div>
</SubSection>
<SubSection title="density modes">
<p className="font-sans text-sm text-foreground-secondary mb-6">
Density modes adjust spacing, font size, and row heights for different contexts.
Essential for data-dense developer tools.
</p>
<div className="flex gap-4 mb-6">
<Button
variant={density === 'compact' ? 'default' : 'outline'}
size="sm"
onClick={() => onDensityChange('compact')}
>
compact
</Button>
<Button
variant={density === 'comfortable' ? 'default' : 'outline'}
size="sm"
onClick={() => onDensityChange('comfortable')}
>
comfortable
</Button>
<Button
variant={density === 'spacious' ? 'default' : 'outline'}
size="sm"
onClick={() => onDensityChange('spacious')}
>
spacious
</Button>
</div>
<div data-density={density} className="border border-dashed border-border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="density-cell">name</TableHead>
<TableHead className="density-cell">downloads</TableHead>
<TableHead className="density-cell">status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="density-cell font-mono">@tpmjs/parser</TableCell>
<TableCell className="density-cell font-mono">125,432</TableCell>
<TableCell className="density-cell"><Badge variant="success" size="sm">active</Badge></TableCell>
</TableRow>
<TableRow>
<TableCell className="density-cell font-mono">@tpmjs/validator</TableCell>
<TableCell className="density-cell font-mono">89,231</TableCell>
<TableCell className="density-cell"><Badge variant="success" size="sm">active</Badge></TableCell>
</TableRow>
<TableRow>
<TableCell className="density-cell font-mono">@tpmjs/transform</TableCell>
<TableCell className="density-cell font-mono">45,678</TableCell>
<TableCell className="density-cell"><Badge variant="warning" size="sm">beta</Badge></TableCell>
</TableRow>
</TableBody>
</Table>
</div>
<div className="mt-6 grid grid-cols-3 gap-4">
<div className="bg-surface p-4 border border-dashed border-border text-center">
<p className="font-mono text-xs text-foreground-secondary mb-1">compact</p>
<p className="font-mono text-lg">36px rows</p>
</div>
<div className="bg-surface p-4 border border-dashed border-border text-center">
<p className="font-mono text-xs text-foreground-secondary mb-1">comfortable</p>
<p className="font-mono text-lg">48px rows</p>
</div>
<div className="bg-surface p-4 border border-dashed border-border text-center">
<p className="font-mono text-xs text-foreground-secondary mb-1">spacious</p>
<p className="font-mono text-lg">64px rows</p>
</div>
</div>
</SubSection>
<SubSection title="grid system">
<div className="space-y-6">
<div>
<p className="font-mono text-xs text-foreground-secondary mb-3">12-column grid</p>
<div className="grid grid-cols-12 gap-2">
{[...Array(12)].map((_, i) => (
<div key={i} className="bg-surface border border-dashed border-border p-2 text-center">
<span className="font-mono text-[10px] text-foreground-tertiary">{i + 1}</span>
</div>
))}
</div>
</div>
<div>
<p className="font-mono text-xs text-foreground-secondary mb-3">sidebar + content (3 + 9)</p>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-3 bg-surface border border-dashed border-border p-4">
<span className="font-mono text-xs">sidebar</span>
</div>
<div className="col-span-9 bg-surface border border-dashed border-border p-4">
<span className="font-mono text-xs">main content</span>
</div>
</div>
</div>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,82 @@
'use client';
import { Button } from '@tpmjs/ui/Button/Button';
import { FieldsetSection, SubSection, TokenRow } from './shared';
export function SectionMotion(): React.ReactElement {
return (
<FieldsetSection title="5. motion" id="motion">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Motion communicates state changes, not decoration. Animations are fast
by default and respect user preferences.
</p>
<SubSection title="motion principles">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">fast by default</h4>
<p className="font-sans text-sm text-foreground-secondary">
Most transitions complete in 150-200ms. Users should never wait
for animations to finish before interacting.
</p>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">state communication</h4>
<p className="font-sans text-sm text-foreground-secondary">
Motion indicates something changed: a panel opened, an item was
selected, data updated. Never animate just for visual interest.
</p>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">linear for data</h4>
<p className="font-sans text-sm text-foreground-secondary">
Data updates (counters, progress bars, charts) use linear easing.
This feels more precise and mechanical.
</p>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">respect preferences</h4>
<p className="font-sans text-sm text-foreground-secondary">
Honor prefers-reduced-motion. All animations disable when the
user has requested reduced motion.
</p>
</div>
</div>
</SubSection>
<SubSection title="duration tokens">
<div className="space-y-4">
<TokenRow name="--motion-instant" value="0ms" preview={<div className="w-full h-2 bg-accent" />} />
<TokenRow name="--motion-fast" value="150ms" preview={<div className="w-full h-2 bg-accent motion-fast" />} />
<TokenRow name="--motion-base" value="200ms" preview={<div className="w-full h-2 bg-accent motion-base" />} />
<TokenRow name="--motion-slow" value="300ms" preview={<div className="w-full h-2 bg-accent motion-slow" />} />
<TokenRow name="--motion-slower" value="500ms" preview={<div className="w-full h-2 bg-accent" />} />
</div>
</SubSection>
<SubSection title="easing tokens">
<div className="space-y-4">
<TokenRow name="--easing-standard" value="cubic-bezier(0.4, 0, 0.2, 1)" />
<TokenRow name="--easing-decelerate" value="cubic-bezier(0, 0, 0.2, 1)" />
<TokenRow name="--easing-accelerate" value="cubic-bezier(0.4, 0, 1, 1)" />
<TokenRow name="--easing-linear" value="linear" />
<TokenRow name="--easing-spring" value="cubic-bezier(0.175, 0.885, 0.32, 1.275)" />
</div>
</SubSection>
<SubSection title="interactive demo">
<div className="flex flex-wrap gap-4">
<Button className="motion-fast transition-all hover:scale-105">
fast (150ms)
</Button>
<Button variant="secondary" className="motion-base transition-all hover:scale-105">
base (200ms)
</Button>
<Button variant="outline" className="motion-slow transition-all hover:scale-105">
slow (300ms)
</Button>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,253 @@
'use client';
import { Badge } from '@tpmjs/ui/Badge/Badge';
import { Button } from '@tpmjs/ui/Button/Button';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { ProgressBar } from '@tpmjs/ui/ProgressBar/ProgressBar';
import { Spinner } from '@tpmjs/ui/Spinner/Spinner';
import { FieldsetSection, SubSection } from './shared';
export function SectionPatternFeedback(): React.ReactElement {
return (
<FieldsetSection title="16. feedback patterns" id="feedback-patterns">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Feedback patterns communicate status, progress, and system responses.
Choose the right pattern based on context and urgency.
</p>
<SubSection title="feedback decision tree">
<div className="bg-surface p-6 border border-dashed border-border mb-6">
<div className="space-y-4">
<div className="flex items-start gap-4">
<Badge variant="info" className="mt-1">Q1</Badge>
<div>
<p className="font-mono text-sm mb-2">Is this a page-level system message?</p>
<p className="font-sans text-xs text-foreground-secondary">
Yes → Use <strong>Banner</strong> (maintenance, outage, announcement)
</p>
</div>
</div>
<div className="flex items-start gap-4">
<Badge variant="info" className="mt-1">Q2</Badge>
<div>
<p className="font-mono text-sm mb-2">Is it a response to a user action?</p>
<p className="font-sans text-xs text-foreground-secondary">
Yes → Use <strong>Toast</strong> (save, submit, delete confirmation)
</p>
</div>
</div>
<div className="flex items-start gap-4">
<Badge variant="info" className="mt-1">Q3</Badge>
<div>
<p className="font-mono text-sm mb-2">Is it contextual to a specific element?</p>
<p className="font-sans text-xs text-foreground-secondary">
Yes → Use <strong>Inline Alert</strong> (form errors, field hints)
</p>
</div>
</div>
</div>
</div>
</SubSection>
<SubSection title="toast notifications">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Ephemeral messages that appear briefly and auto-dismiss.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Success toast */}
<div className="bg-surface p-4 border-l-4 border-success flex items-start gap-3">
<Icon icon="check" size="sm" className="text-success mt-0.5" />
<div>
<p className="font-mono text-sm font-medium">tool published</p>
<p className="font-sans text-xs text-foreground-secondary">
@tpmjs/parser v1.0.0 is now live
</p>
</div>
</div>
{/* Error toast */}
<div className="bg-surface p-4 border-l-4 border-error flex items-start gap-3">
<Icon icon="x" size="sm" className="text-error mt-0.5" />
<div>
<p className="font-mono text-sm font-medium">publish failed</p>
<p className="font-sans text-xs text-foreground-secondary">
Package validation errors. Check your config.
</p>
</div>
</div>
{/* Warning toast */}
<div className="bg-surface p-4 border-l-4 border-warning flex items-start gap-3">
<Icon icon="alertTriangle" size="sm" className="text-warning mt-0.5" />
<div>
<p className="font-mono text-sm font-medium">api rate limit</p>
<p className="font-sans text-xs text-foreground-secondary">
You've used 90% of your monthly quota
</p>
</div>
</div>
{/* Info toast */}
<div className="bg-surface p-4 border-l-4 border-info flex items-start gap-3">
<Icon icon="info" size="sm" className="text-info mt-0.5" />
<div>
<p className="font-mono text-sm font-medium">new version available</p>
<p className="font-sans text-xs text-foreground-secondary">
Refresh to get the latest features
</p>
</div>
</div>
</div>
<div className="mt-4 bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-3">toast guidelines</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• Auto-dismiss after 5 seconds (except errors)</li>
<li>• Errors should require manual dismissal</li>
<li>• Stack from bottom-right, newest at bottom</li>
<li>• Maximum 3 visible toasts at once</li>
</ul>
</div>
</SubSection>
<SubSection title="inline alerts">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Persistent messages within content flow.
</p>
<div className="space-y-4">
<div className="bg-success-light border border-success p-4 flex items-start gap-3">
<Icon icon="check" size="sm" className="text-success mt-0.5" />
<div>
<p className="font-mono text-sm font-medium text-success">verification complete</p>
<p className="font-sans text-xs text-foreground-secondary">
Your email has been verified. You can now publish tools.
</p>
</div>
</div>
<div className="bg-warning-light border border-warning p-4 flex items-start gap-3">
<Icon icon="alertTriangle" size="sm" className="text-warning mt-0.5" />
<div>
<p className="font-mono text-sm font-medium text-warning">deprecated package</p>
<p className="font-sans text-xs text-foreground-secondary">
This package is deprecated. Consider migrating to @tpmjs/v2.
</p>
</div>
</div>
<div className="bg-error-light border border-error p-4 flex items-start gap-3">
<Icon icon="alertCircle" size="sm" className="text-error mt-0.5" />
<div>
<p className="font-mono text-sm font-medium text-error">critical security issue</p>
<p className="font-sans text-xs text-foreground-secondary">
This version has known vulnerabilities. Update immediately.
</p>
<Button size="sm" variant="destructive" className="mt-3">update now</Button>
</div>
</div>
</div>
</SubSection>
<SubSection title="global banners">
<p className="font-sans text-sm text-foreground-secondary mb-4">
System-wide announcements that span the full width.
</p>
<div className="space-y-4">
{/* Maintenance banner */}
<div className="bg-warning text-foreground px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-3">
<Icon icon="alertTriangle" size="sm" />
<span className="font-mono text-sm">
Scheduled maintenance: Jan 15, 2-4am UTC. Some services may be unavailable.
</span>
</div>
<Button variant="ghost" size="sm" className="text-foreground hover:bg-warning-light">
<Icon icon="x" size="sm" />
</Button>
</div>
{/* Outage banner */}
<div className="bg-error text-white px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-3">
<Icon icon="alertCircle" size="sm" />
<span className="font-mono text-sm">
Service degradation detected. Some API calls may fail. We are investigating.
</span>
</div>
<a href="#" className="font-mono text-sm underline hover:no-underline">status page</a>
</div>
{/* Announcement banner */}
<div className="bg-accent text-white px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-3">
<Icon icon="star" size="sm" />
<span className="font-mono text-sm">
New: AI-powered code review is now available for all tools!
</span>
</div>
<Button variant="secondary" size="sm">learn more</Button>
</div>
</div>
</SubSection>
<SubSection title="progress indicators">
<p className="font-sans text-sm text-foreground-secondary mb-4">
For long-running operations, show determinate progress when possible.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Determinate progress */}
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">determinate progress</h4>
<p className="font-sans text-xs text-foreground-secondary mb-4">
Use when you can calculate progress (file upload, batch processing).
</p>
<div className="space-y-2">
<div className="flex justify-between text-xs font-mono">
<span>uploading package.tgz</span>
<span>67%</span>
</div>
<ProgressBar value={67} variant="primary" />
</div>
</div>
{/* Indeterminate progress */}
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">indeterminate progress</h4>
<p className="font-sans text-xs text-foreground-secondary mb-4">
Use when duration is unknown (API calls, processing).
</p>
<div className="flex items-center gap-4">
<Spinner size="sm" />
<span className="font-mono text-sm text-foreground-secondary">
validating package...
</span>
</div>
</div>
</div>
</SubSection>
<SubSection title="retry patterns">
<p className="font-sans text-sm text-foreground-secondary mb-4">
When operations fail, provide clear retry options.
</p>
<div className="bg-surface p-6 border border-dashed border-border">
<div className="flex items-start gap-4">
<div className="w-12 h-12 bg-error-light flex items-center justify-center flex-shrink-0">
<Icon icon="alertCircle" size="md" className="text-error" />
</div>
<div className="flex-1">
<p className="font-mono text-sm font-medium mb-1">failed to load tools</p>
<p className="font-sans text-xs text-foreground-secondary mb-4">
We couldn't connect to the server. This could be a network issue or the service may be temporarily unavailable.
</p>
<div className="flex gap-2">
<Button size="sm">retry</Button>
<Button size="sm" variant="ghost">view details</Button>
</div>
</div>
</div>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,231 @@
'use client';
import { Button } from '@tpmjs/ui/Button/Button';
import { FormField } from '@tpmjs/ui/FormField/FormField';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Input } from '@tpmjs/ui/Input/Input';
import { Label } from '@tpmjs/ui/Label/Label';
import { Select } from '@tpmjs/ui/Select/Select';
import { Textarea } from '@tpmjs/ui/Textarea/Textarea';
import { DoDontCard, FieldsetSection, SubSection } from './shared';
export function SectionPatternForms(): React.ReactElement {
return (
<FieldsetSection title="15. form patterns" id="form-patterns">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Consistent form patterns for data entry, validation, and submission.
Forms should guide users through tasks with clear feedback.
</p>
<SubSection title="validation timing">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Choose validation timing based on the field type and user expectations.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">validate on blur</h4>
<p className="font-sans text-xs text-foreground-secondary mb-4">
Best for: format validation, required fields
</p>
<FormField label="email" error="Please enter a valid email address">
<Input placeholder="user@example.com" state="error" />
</FormField>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">validate on submit</h4>
<p className="font-sans text-xs text-foreground-secondary mb-4">
Best for: async validation, complex rules
</p>
<FormField label="package name" helperText="We'll check if this name is available">
<Input placeholder="@scope/package-name" />
</FormField>
</div>
</div>
</SubSection>
<SubSection title="error summary">
<p className="font-sans text-sm text-foreground-secondary mb-4">
For forms with multiple errors, show a summary at the top.
</p>
<div className="bg-surface border border-dashed border-border p-6">
{/* Error summary */}
<div className="bg-error-light border border-error p-4 mb-6">
<div className="flex items-center gap-2 mb-3">
<Icon icon="alertCircle" size="sm" className="text-error" />
<span className="font-mono text-sm font-medium text-error">please fix 3 errors</span>
</div>
<ul className="space-y-1 text-sm text-foreground-secondary font-sans">
<li><a href="#name" className="text-error hover:underline">Name is required</a></li>
<li><a href="#email" className="text-error hover:underline">Email format is invalid</a></li>
<li><a href="#description" className="text-error hover:underline">Description must be at least 50 characters</a></li>
</ul>
</div>
{/* Form fields */}
<div className="space-y-4">
<FormField label="name" error="Name is required" required>
<Input placeholder="Enter name" state="error" />
</FormField>
<FormField label="email" error="Email format is invalid" required>
<Input placeholder="user@example.com" state="error" />
</FormField>
<FormField label="description" error="Description must be at least 50 characters">
<Textarea placeholder="Describe your tool..." rows={3} />
</FormField>
</div>
</div>
</SubSection>
<SubSection title="help text placement">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<DoDontCard type="do" title="Help text below the input">
<FormField
label="api key"
helperText="You can find this in your dashboard settings"
>
<Input placeholder="sk-xxxx..." />
</FormField>
</DoDontCard>
<DoDontCard type="dont" title="Don't put help text above">
<div>
<Label>api key</Label>
<p className="text-xs text-foreground-secondary mb-2">
You can find this in your dashboard settings
</p>
<Input placeholder="sk-xxxx..." />
</div>
</DoDontCard>
</div>
</SubSection>
<SubSection title="required vs optional">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-4">
<div className="bg-surface p-6 border border-dashed border-border">
<p className="font-mono text-xs text-foreground-tertiary mb-4">when most fields are required</p>
<div className="space-y-4">
<FormField label="name" required>
<Input placeholder="Tool name" />
</FormField>
<FormField label="version" required>
<Input placeholder="1.0.0" />
</FormField>
<FormField label="description" helperText="(optional)">
<Textarea placeholder="Describe your tool..." rows={2} />
</FormField>
</div>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<p className="font-mono text-xs text-foreground-tertiary mb-4">when most fields are optional</p>
<div className="space-y-4">
<FormField label="website">
<Input placeholder="https://example.com" />
</FormField>
<FormField label="twitter handle">
<Input placeholder="@username" />
</FormField>
<FormField label="bio" helperText="required">
<Textarea placeholder="Tell us about yourself..." rows={2} />
</FormField>
</div>
</div>
</div>
<div className="bg-surface p-4 border border-dashed border-border">
<p className="font-sans text-sm text-foreground-secondary">
<strong>Rule:</strong> Mark the minority. If most fields are required, mark optional fields.
If most are optional, mark required fields.
</p>
</div>
</SubSection>
<SubSection title="field grouping">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Group related fields with visual hierarchy.
</p>
<div className="bg-surface border border-dashed border-border p-6">
<div className="space-y-8">
{/* Group 1 */}
<div>
<h4 className="font-mono text-sm font-medium mb-4 pb-2 border-b border-dashed border-border">
package details
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormField label="package name" required>
<Input placeholder="@scope/package" />
</FormField>
<FormField label="version" required>
<Input placeholder="1.0.0" />
</FormField>
</div>
</div>
{/* Group 2 */}
<div>
<h4 className="font-mono text-sm font-medium mb-4 pb-2 border-b border-dashed border-border">
author information
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormField label="author name" required>
<Input placeholder="Your name" />
</FormField>
<FormField label="email" required>
<Input placeholder="author@example.com" />
</FormField>
</div>
</div>
{/* Group 3 */}
<div>
<h4 className="font-mono text-sm font-medium mb-4 pb-2 border-b border-dashed border-border">
repository
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormField label="repository type">
<Select
placeholder="Select type..."
options={[
{ value: 'git', label: 'Git' },
{ value: 'svn', label: 'SVN' },
]}
/>
</FormField>
<FormField label="repository url">
<Input placeholder="https://github.com/..." />
</FormField>
</div>
</div>
</div>
</div>
</SubSection>
<SubSection title="danger zone">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Destructive actions require explicit confirmation.
</p>
<div className="bg-surface border border-error border-dashed p-6">
<div className="flex items-center gap-2 mb-4">
<Icon icon="alertTriangle" size="sm" className="text-error" />
<h4 className="font-mono text-sm font-medium text-error">danger zone</h4>
</div>
<div className="flex items-center justify-between py-4 border-t border-dashed border-error">
<div>
<p className="font-mono text-sm font-medium">delete this tool</p>
<p className="font-sans text-xs text-foreground-secondary">
Once deleted, this tool cannot be recovered.
</p>
</div>
<Button variant="destructive" size="sm">delete tool</Button>
</div>
<div className="flex items-center justify-between py-4 border-t border-dashed border-error">
<div>
<p className="font-mono text-sm font-medium">transfer ownership</p>
<p className="font-sans text-xs text-foreground-secondary">
Transfer this tool to another user or organization.
</p>
</div>
<Button variant="outline" size="sm">transfer</Button>
</div>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,222 @@
'use client';
import { Badge } from '@tpmjs/ui/Badge/Badge';
import { Breadcrumbs, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator, BreadcrumbPage } from '@tpmjs/ui/Breadcrumbs/Breadcrumbs';
import { Button } from '@tpmjs/ui/Button/Button';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Tabs } from '@tpmjs/ui/Tabs/Tabs';
import { useState } from 'react';
import { FieldsetSection, SubSection } from './shared';
export function SectionPatternNavigation(): React.ReactElement {
const [activeTab, setActiveTab] = useState('overview');
return (
<FieldsetSection title="14. navigation patterns" id="nav-patterns">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Navigation patterns for consistent wayfinding across the platform. Each pattern
has specific use cases and accessibility requirements.
</p>
<SubSection title="global navigation (header)">
<p className="font-sans text-sm text-foreground-secondary mb-4">
The primary header navigation appears on every page and provides access to main sections.
</p>
<div className="bg-surface border border-dashed border-border">
{/* Mock header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-border">
<div className="flex items-center gap-8">
<span className="font-mono text-lg font-semibold">tpmjs</span>
<nav className="flex items-center gap-6">
<a href="#" className="font-mono text-sm text-foreground hover:text-accent transition-colors">tools</a>
<a href="#" className="font-mono text-sm text-foreground-secondary hover:text-accent transition-colors">agents</a>
<a href="#" className="font-mono text-sm text-foreground-secondary hover:text-accent transition-colors">docs</a>
<a href="#" className="font-mono text-sm text-foreground-secondary hover:text-accent transition-colors">pricing</a>
</nav>
</div>
<div className="flex items-center gap-4">
<Button variant="ghost" size="sm">sign in</Button>
<Button size="sm">get started</Button>
</div>
</div>
</div>
<div className="mt-4 bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-3">guidelines</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• Maximum 5-6 top-level items to avoid cognitive overload</li>
<li>• Use visible active states (underline or background)</li>
<li>• Logo always links to home</li>
<li>• Auth actions stay in top-right corner</li>
</ul>
</div>
</SubSection>
<SubSection title="sidebar navigation">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Vertical sidebar for section-level navigation within the app.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Expanded sidebar */}
<div className="bg-surface border border-dashed border-border p-4">
<p className="font-mono text-xs text-foreground-tertiary mb-4">expanded state</p>
<nav className="space-y-1">
<a href="#" className="flex items-center gap-3 px-3 py-2 bg-accent/10 text-accent font-mono text-sm">
<Icon icon="home" size="sm" />
<span>dashboard</span>
</a>
<a href="#" className="flex items-center gap-3 px-3 py-2 text-foreground-secondary hover:bg-surface-2 font-mono text-sm">
<Icon icon="puzzle" size="sm" />
<span>tools</span>
<Badge size="sm" variant="outline" className="ml-auto">12</Badge>
</a>
<a href="#" className="flex items-center gap-3 px-3 py-2 text-foreground-secondary hover:bg-surface-2 font-mono text-sm">
<Icon icon="key" size="sm" />
<span>api keys</span>
</a>
<a href="#" className="flex items-center gap-3 px-3 py-2 text-foreground-secondary hover:bg-surface-2 font-mono text-sm">
<Icon icon="user" size="sm" />
<span>settings</span>
</a>
</nav>
</div>
{/* Collapsed sidebar */}
<div className="bg-surface border border-dashed border-border p-4">
<p className="font-mono text-xs text-foreground-tertiary mb-4">collapsed state</p>
<nav className="flex flex-col items-center space-y-2">
<button className="p-3 bg-accent/10 text-accent" title="Dashboard">
<Icon icon="home" size="sm" />
</button>
<button className="p-3 text-foreground-secondary hover:bg-surface-2" title="Tools">
<Icon icon="puzzle" size="sm" />
</button>
<button className="p-3 text-foreground-secondary hover:bg-surface-2" title="API Keys">
<Icon icon="key" size="sm" />
</button>
<button className="p-3 text-foreground-secondary hover:bg-surface-2" title="Settings">
<Icon icon="user" size="sm" />
</button>
</nav>
</div>
</div>
<div className="mt-4 bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-3">keyboard shortcuts</h4>
<div className="grid grid-cols-2 gap-4 text-sm text-foreground-secondary font-sans">
<div><kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">[</kbd> collapse/expand sidebar</div>
<div><kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">g then h</kbd> go to home</div>
<div><kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">g then t</kbd> go to tools</div>
<div><kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">g then s</kbd> go to settings</div>
</div>
</div>
</SubSection>
<SubSection title="breadcrumbs">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Show hierarchical location and enable navigation back to parent pages.
</p>
<div className="space-y-4">
{/* Simple breadcrumb */}
<div className="bg-surface p-4 border border-dashed border-border">
<p className="font-mono text-xs text-foreground-tertiary mb-3">default</p>
<Breadcrumbs>
<BreadcrumbItem>
<BreadcrumbLink href="#">home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="#">tools</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>@tpmjs/parser</BreadcrumbPage>
</BreadcrumbItem>
</Breadcrumbs>
</div>
{/* With icons */}
<div className="bg-surface p-4 border border-dashed border-border">
<p className="font-mono text-xs text-foreground-tertiary mb-3">with icons</p>
<Breadcrumbs>
<BreadcrumbItem>
<BreadcrumbLink href="#" className="flex items-center gap-1">
<Icon icon="home" size="xs" />
<span>home</span>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="#" className="flex items-center gap-1">
<Icon icon="folder" size="xs" />
<span>projects</span>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>my-tool</BreadcrumbPage>
</BreadcrumbItem>
</Breadcrumbs>
</div>
</div>
</SubSection>
<SubSection title="tab navigation">
<p className="font-sans text-sm text-foreground-secondary mb-4">
For switching between views within a page context.
</p>
<div className="space-y-4">
<div className="bg-surface p-4 border border-dashed border-border">
<Tabs
tabs={[
{ id: 'overview', label: 'overview' },
{ id: 'readme', label: 'readme' },
{ id: 'versions', label: 'versions', count: 24 },
{ id: 'dependencies', label: 'dependencies', count: 3 },
]}
activeTab={activeTab}
onTabChange={setActiveTab}
variant="default"
/>
</div>
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-3">keyboard behavior</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• <kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">←</kbd> / <kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">→</kbd> navigate between tabs</li>
<li>• <kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">Home</kbd> focus first tab</li>
<li>• <kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">End</kbd> focus last tab</li>
<li>• <kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">Enter</kbd> / <kbd className="px-2 py-1 bg-surface-2 font-mono text-xs">Space</kbd> activate focused tab</li>
</ul>
</div>
</div>
</SubSection>
<SubSection title="mobile navigation">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Hamburger menu pattern for narrow viewports.
</p>
<div className="max-w-sm">
<div className="bg-surface border border-dashed border-border">
{/* Mock mobile header */}
<div className="flex items-center justify-between px-4 py-3 border-b border-border">
<span className="font-mono text-sm font-semibold">tpmjs</span>
<Button variant="ghost" size="icon">
<Icon icon="menu" size="sm" />
</Button>
</div>
{/* Expanded menu preview */}
<div className="p-4 space-y-2">
<a href="#" className="block px-3 py-2 font-mono text-sm text-foreground bg-accent/10">tools</a>
<a href="#" className="block px-3 py-2 font-mono text-sm text-foreground-secondary">agents</a>
<a href="#" className="block px-3 py-2 font-mono text-sm text-foreground-secondary">docs</a>
<a href="#" className="block px-3 py-2 font-mono text-sm text-foreground-secondary">pricing</a>
<div className="pt-4 border-t border-border space-y-2">
<Button variant="outline" size="sm" className="w-full">sign in</Button>
<Button size="sm" className="w-full">get started</Button>
</div>
</div>
</div>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,288 @@
'use client';
import { Badge } from '@tpmjs/ui/Badge/Badge';
import { Button } from '@tpmjs/ui/Button/Button';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Input } from '@tpmjs/ui/Input/Input';
import { Select } from '@tpmjs/ui/Select/Select';
import { FieldsetSection, SubSection } from './shared';
export function SectionPatternSearch(): React.ReactElement {
return (
<FieldsetSection title="18. search & filtering" id="search-patterns">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Search and filter patterns help users find content quickly.
Design for progressive disclosure and instant feedback.
</p>
<SubSection title="search box states">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Search boxes should show clear states and provide helpful feedback.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Default state */}
<div className="bg-surface p-4 border border-dashed border-border">
<p className="font-mono text-xs text-foreground-tertiary mb-3">default</p>
<div className="relative">
<Icon icon="search" size="sm" className="absolute left-3 top-1/2 -translate-y-1/2 text-foreground-tertiary" />
<Input placeholder="search tools..." className="pl-10" />
</div>
</div>
{/* Active state */}
<div className="bg-surface p-4 border border-dashed border-border">
<p className="font-mono text-xs text-foreground-tertiary mb-3">active / has value</p>
<div className="relative">
<Icon icon="search" size="sm" className="absolute left-3 top-1/2 -translate-y-1/2 text-foreground-tertiary" />
<Input defaultValue="parser" className="pl-10 pr-10" />
<button className="absolute right-3 top-1/2 -translate-y-1/2 text-foreground-tertiary hover:text-foreground">
<Icon icon="x" size="sm" />
</button>
</div>
</div>
{/* Loading state */}
<div className="bg-surface p-4 border border-dashed border-border">
<p className="font-mono text-xs text-foreground-tertiary mb-3">loading</p>
<div className="relative">
<Icon icon="loader" size="sm" className="absolute left-3 top-1/2 -translate-y-1/2 text-foreground-tertiary animate-spin" />
<Input defaultValue="validator" className="pl-10" readOnly />
</div>
</div>
{/* No results state */}
<div className="bg-surface p-4 border border-dashed border-border">
<p className="font-mono text-xs text-foreground-tertiary mb-3">no results</p>
<div className="relative">
<Icon icon="search" size="sm" className="absolute left-3 top-1/2 -translate-y-1/2 text-foreground-tertiary" />
<Input defaultValue="xyzabc123" className="pl-10 pr-10" />
<button className="absolute right-3 top-1/2 -translate-y-1/2 text-foreground-tertiary hover:text-foreground">
<Icon icon="x" size="sm" />
</button>
</div>
<p className="text-xs text-foreground-tertiary mt-2">No results found</p>
</div>
</div>
</SubSection>
<SubSection title="filter chips">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Use chips to show active filters with easy removal.
</p>
<div className="bg-surface p-4 border border-dashed border-border">
<div className="flex flex-wrap gap-2 mb-4">
<Badge variant="default" className="pr-1 flex items-center gap-1">
category: utility
<button className="ml-1 hover:bg-accent-strong rounded">
<Icon icon="x" size="xs" />
</button>
</Badge>
<Badge variant="default" className="pr-1 flex items-center gap-1">
status: active
<button className="ml-1 hover:bg-accent-strong rounded">
<Icon icon="x" size="xs" />
</button>
</Badge>
<Badge variant="default" className="pr-1 flex items-center gap-1">
downloads: &gt;10k
<button className="ml-1 hover:bg-accent-strong rounded">
<Icon icon="x" size="xs" />
</button>
</Badge>
<button className="font-mono text-xs text-accent hover:underline">clear all</button>
</div>
<p className="font-mono text-xs text-foreground-secondary">
showing 42 of 128 tools
</p>
</div>
</SubSection>
<SubSection title="filter panel">
<p className="font-sans text-sm text-foreground-secondary mb-4">
For complex filtering, use a dedicated panel with grouped options.
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Filter panel */}
<div className="bg-surface border border-dashed border-border p-4">
<h4 className="font-mono text-sm font-medium mb-4">filters</h4>
<div className="space-y-4">
<div>
<label className="font-mono text-xs text-foreground-secondary block mb-2">category</label>
<Select
placeholder="all categories"
options={[
{ value: 'utility', label: 'Utility' },
{ value: 'validation', label: 'Validation' },
{ value: 'data', label: 'Data' },
]}
/>
</div>
<div>
<label className="font-mono text-xs text-foreground-secondary block mb-2">status</label>
<Select
placeholder="all statuses"
options={[
{ value: 'active', label: 'Active' },
{ value: 'beta', label: 'Beta' },
{ value: 'deprecated', label: 'Deprecated' },
]}
/>
</div>
<div>
<label className="font-mono text-xs text-foreground-secondary block mb-2">min downloads</label>
<Input type="number" placeholder="0" />
</div>
<div className="pt-4 border-t border-dashed border-border">
<Button size="sm" className="w-full">apply filters</Button>
</div>
</div>
</div>
{/* Results preview */}
<div className="md:col-span-2 bg-surface border border-dashed border-border p-4">
<p className="font-mono text-xs text-foreground-tertiary mb-4">results area</p>
<div className="space-y-2">
{['@tpmjs/parser', '@tpmjs/validator', '@tpmjs/transform'].map((name) => (
<div key={name} className="p-3 border border-dashed border-border flex items-center justify-between">
<span className="font-mono text-sm">{name}</span>
<Badge variant="success" size="sm">active</Badge>
</div>
))}
</div>
</div>
</div>
</SubSection>
<SubSection title="saved views">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Allow users to save and recall filter combinations.
</p>
<div className="bg-surface p-4 border border-dashed border-border">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<span className="font-mono text-sm font-medium">saved views</span>
<Badge variant="outline" size="sm">3</Badge>
</div>
<Button size="sm" variant="ghost">
<Icon icon="plus" size="sm" className="mr-2" />
save current
</Button>
</div>
<div className="space-y-2">
<button className="w-full text-left px-3 py-2 bg-accent/10 font-mono text-sm flex items-center justify-between">
<span>my active tools</span>
<div className="flex items-center gap-2">
<Badge variant="outline" size="sm">42 results</Badge>
<Icon icon="check" size="sm" className="text-accent" />
</div>
</button>
<button className="w-full text-left px-3 py-2 hover:bg-surface-2 font-mono text-sm flex items-center justify-between">
<span>deprecated packages</span>
<Badge variant="outline" size="sm">8 results</Badge>
</button>
<button className="w-full text-left px-3 py-2 hover:bg-surface-2 font-mono text-sm flex items-center justify-between">
<span>high-download tools</span>
<Badge variant="outline" size="sm">15 results</Badge>
</button>
</div>
</div>
</SubSection>
<SubSection title="query syntax display">
<p className="font-sans text-sm text-foreground-secondary mb-4">
For power users, display the underlying query syntax.
</p>
<div className="bg-surface p-4 border border-dashed border-border">
<div className="flex items-center justify-between mb-3">
<span className="font-mono text-sm font-medium">query</span>
<button className="font-mono text-xs text-accent hover:underline flex items-center gap-1">
<Icon icon="copy" size="xs" />
copy
</button>
</div>
<div className="bg-surface-2 p-3 font-mono text-xs">
<span className="text-accent">category:</span>
<span className="text-foreground">utility</span>
<span className="text-foreground-tertiary"> AND </span>
<span className="text-accent">status:</span>
<span className="text-foreground">active</span>
<span className="text-foreground-tertiary"> AND </span>
<span className="text-accent">downloads:</span>
<span className="text-foreground">&gt;10000</span>
</div>
<p className="font-sans text-xs text-foreground-tertiary mt-3">
Tip: Use this syntax directly in the search box for quick filtering.
</p>
</div>
</SubSection>
<SubSection title="command palette">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Quick access to search and navigation via keyboard shortcut.
</p>
<div className="max-w-md mx-auto">
<div className="bg-surface border border-border shadow-lg">
{/* Search input */}
<div className="border-b border-border p-3">
<div className="relative">
<Icon icon="search" size="sm" className="absolute left-3 top-1/2 -translate-y-1/2 text-foreground-tertiary" />
<Input placeholder="search or type a command..." className="pl-10 border-0 focus:ring-0" />
</div>
</div>
{/* Results groups */}
<div className="max-h-80 overflow-y-auto">
{/* Recent */}
<div className="p-2">
<p className="font-mono text-[10px] text-foreground-tertiary uppercase tracking-wide px-2 py-1">recent</p>
<button className="w-full text-left px-3 py-2 hover:bg-surface-2 flex items-center gap-3">
<Icon icon="clock" size="sm" className="text-foreground-tertiary" />
<span className="font-mono text-sm">@tpmjs/parser</span>
</button>
</div>
{/* Actions */}
<div className="p-2 border-t border-border">
<p className="font-mono text-[10px] text-foreground-tertiary uppercase tracking-wide px-2 py-1">actions</p>
<button className="w-full text-left px-3 py-2 hover:bg-surface-2 flex items-center justify-between">
<div className="flex items-center gap-3">
<Icon icon="plus" size="sm" className="text-foreground-tertiary" />
<span className="font-mono text-sm">create new tool</span>
</div>
<kbd className="font-mono text-[10px] bg-surface-2 px-2 py-0.5">⌘N</kbd>
</button>
<button className="w-full text-left px-3 py-2 hover:bg-surface-2 flex items-center justify-between">
<div className="flex items-center gap-3">
<Icon icon="home" size="sm" className="text-foreground-tertiary" />
<span className="font-mono text-sm">go to dashboard</span>
</div>
<kbd className="font-mono text-[10px] bg-surface-2 px-2 py-0.5">⌘D</kbd>
</button>
<button className="w-full text-left px-3 py-2 hover:bg-surface-2 flex items-center justify-between">
<div className="flex items-center gap-3">
<Icon icon="user" size="sm" className="text-foreground-tertiary" />
<span className="font-mono text-sm">go to settings</span>
</div>
<kbd className="font-mono text-[10px] bg-surface-2 px-2 py-0.5">⌘,</kbd>
</button>
</div>
</div>
{/* Footer */}
<div className="border-t border-border px-3 py-2 flex items-center justify-between text-[10px] text-foreground-tertiary">
<div className="flex items-center gap-3">
<span><kbd className="bg-surface-2 px-1.5 py-0.5">↑↓</kbd> navigate</span>
<span><kbd className="bg-surface-2 px-1.5 py-0.5">↵</kbd> select</span>
<span><kbd className="bg-surface-2 px-1.5 py-0.5">esc</kbd> close</span>
</div>
</div>
</div>
</div>
<div className="mt-4 text-center">
<kbd className="font-mono text-xs bg-surface px-3 py-1.5 border border-border">⌘K</kbd>
<span className="font-sans text-xs text-foreground-secondary ml-2">to open command palette</span>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,253 @@
'use client';
import { Badge } from '@tpmjs/ui/Badge/Badge';
import { Button } from '@tpmjs/ui/Button/Button';
import { Checkbox } from '@tpmjs/ui/Checkbox/Checkbox';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Pagination } from '@tpmjs/ui/Pagination/Pagination';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@tpmjs/ui/Table/Table';
import { useState } from 'react';
import { FieldsetSection, SubSection } from './shared';
export function SectionPatternTables(): React.ReactElement {
const [page, setPage] = useState(1);
const [selectedRows, setSelectedRows] = useState<string[]>([]);
const mockData = [
{ id: '1', name: '@tpmjs/parser', category: 'utility', downloads: 125432, status: 'active', score: 0.92 },
{ id: '2', name: '@tpmjs/validator', category: 'validation', downloads: 89231, status: 'active', score: 0.87 },
{ id: '3', name: '@tpmjs/transform', category: 'data', downloads: 45678, status: 'beta', score: 0.81 },
{ id: '4', name: '@tpmjs/executor', category: 'runtime', downloads: 34521, status: 'active', score: 0.78 },
{ id: '5', name: '@tpmjs/config', category: 'utility', downloads: 23456, status: 'deprecated', score: 0.65 },
];
const toggleRow = (id: string) => {
setSelectedRows(prev =>
prev.includes(id) ? prev.filter(r => r !== id) : [...prev, id]
);
};
const toggleAll = () => {
if (selectedRows.length === mockData.length) {
setSelectedRows([]);
} else {
setSelectedRows(mockData.map(d => d.id));
}
};
return (
<FieldsetSection title="17. table patterns" id="table-patterns">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Data table patterns for displaying, sorting, filtering, and acting on tabular data.
</p>
<SubSection title="sortable columns">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Click column headers to sort. Show sort direction with icons.
</p>
<div className="bg-surface border border-dashed border-border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="cursor-pointer hover:bg-surface-2">
<div className="flex items-center gap-2">
name
<Icon icon="chevronDown" size="xs" className="text-foreground-tertiary" />
</div>
</TableHead>
<TableHead>category</TableHead>
<TableHead className="text-right cursor-pointer hover:bg-surface-2">
<div className="flex items-center gap-2 justify-end">
downloads
<Icon icon="chevronDown" size="xs" className="text-accent" />
</div>
</TableHead>
<TableHead className="text-right">score</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{mockData.slice(0, 3).map((row) => (
<TableRow key={row.id}>
<TableCell className="font-mono">{row.name}</TableCell>
<TableCell>{row.category}</TableCell>
<TableCell className="text-right font-mono">{row.downloads.toLocaleString()}</TableCell>
<TableCell className="text-right font-mono">{row.score}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</SubSection>
<SubSection title="row selection + bulk actions">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Select rows to enable bulk actions. Show action bar when rows are selected.
</p>
<div className="bg-surface border border-dashed border-border">
{/* Bulk action bar */}
{selectedRows.length > 0 && (
<div className="flex items-center justify-between px-4 py-3 bg-accent/10 border-b border-border">
<span className="font-mono text-sm">
{selectedRows.length} item{selectedRows.length !== 1 ? 's' : ''} selected
</span>
<div className="flex gap-2">
<Button size="sm" variant="outline">export</Button>
<Button size="sm" variant="destructive">delete</Button>
</div>
</div>
)}
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-12">
<Checkbox
checked={selectedRows.length === mockData.length}
onChange={toggleAll}
aria-label="Select all"
/>
</TableHead>
<TableHead>name</TableHead>
<TableHead>category</TableHead>
<TableHead>status</TableHead>
<TableHead className="text-right">downloads</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{mockData.map((row) => (
<TableRow key={row.id} className={selectedRows.includes(row.id) ? 'bg-accent/5' : ''}>
<TableCell>
<Checkbox
checked={selectedRows.includes(row.id)}
onChange={() => toggleRow(row.id)}
aria-label={`Select ${row.name}`}
/>
</TableCell>
<TableCell className="font-mono">{row.name}</TableCell>
<TableCell>{row.category}</TableCell>
<TableCell>
<Badge
variant={row.status === 'active' ? 'success' : row.status === 'deprecated' ? 'error' : 'warning'}
size="sm"
>
{row.status}
</Badge>
</TableCell>
<TableCell className="text-right font-mono">{row.downloads.toLocaleString()}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</SubSection>
<SubSection title="pagination">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Use pagination for large datasets. Show page info and navigation.
</p>
<div className="space-y-6">
{/* Full pagination */}
<div className="bg-surface p-4 border border-dashed border-border">
<p className="font-mono text-xs text-foreground-tertiary mb-4">full pagination</p>
<Pagination
page={page}
totalPages={10}
onPageChange={setPage}
showFirstLast
showPrevNext
/>
</div>
{/* Simple pagination */}
<div className="bg-surface p-4 border border-dashed border-border">
<p className="font-mono text-xs text-foreground-tertiary mb-4">simple pagination</p>
<Pagination
page={page}
totalPages={10}
onPageChange={setPage}
variant="simple"
/>
</div>
{/* Minimal pagination */}
<div className="bg-surface p-4 border border-dashed border-border">
<p className="font-mono text-xs text-foreground-tertiary mb-4">minimal pagination</p>
<Pagination
page={page}
totalPages={10}
onPageChange={setPage}
variant="minimal"
/>
</div>
</div>
</SubSection>
<SubSection title="empty & loading states">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Empty state */}
<div className="bg-surface border border-dashed border-border">
<div className="p-12 text-center">
<Icon icon="folder" size="lg" className="text-foreground-tertiary mx-auto mb-4" />
<p className="font-mono text-sm text-foreground mb-2">no tools found</p>
<p className="font-sans text-xs text-foreground-secondary mb-4">
Try adjusting your filters or create your first tool.
</p>
<Button size="sm">create tool</Button>
</div>
</div>
{/* Loading state */}
<div className="bg-surface border border-dashed border-border">
<Table>
<TableHeader>
<TableRow>
<TableHead>name</TableHead>
<TableHead>category</TableHead>
<TableHead className="text-right">downloads</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{[1, 2, 3].map((i) => (
<TableRow key={i}>
<TableCell><div className="h-4 bg-muted animate-pulse w-32" /></TableCell>
<TableCell><div className="h-4 bg-muted animate-pulse w-20" /></TableCell>
<TableCell className="text-right"><div className="h-4 bg-muted animate-pulse w-16 ml-auto" /></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</SubSection>
<SubSection title="density integration">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Tables respect the page density mode for compact views.
</p>
<div className="bg-surface p-4 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-3">row heights by density</h4>
<div className="grid grid-cols-3 gap-4">
<div className="text-center">
<p className="font-mono text-xs text-foreground-tertiary">compact</p>
<p className="font-mono text-lg">36px</p>
</div>
<div className="text-center">
<p className="font-mono text-xs text-foreground-tertiary">comfortable</p>
<p className="font-mono text-lg">48px</p>
</div>
<div className="text-center">
<p className="font-mono text-xs text-foreground-tertiary">spacious</p>
<p className="font-mono text-lg">64px</p>
</div>
</div>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,62 @@
'use client';
import { FieldsetSection, PrincipleCard, SubSection } from './shared';
export function SectionPrinciples(): React.ReactElement {
return (
<FieldsetSection title="1. design principles" id="principles">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
These principles guide every design decision in TPMJS. They ensure consistency
and prevent drift as the system grows.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-10">
<PrincipleCard
icon="terminal"
title="clarity over decoration"
description="Every element serves a purpose. Remove anything that doesn't help users accomplish their goals. Developer tools should feel efficient, not playful."
/>
<PrincipleCard
icon="box"
title="mechanical precision"
description="Sharp corners communicate precision and control. Tools feel engineered, not organic. The interface should feel like a well-built instrument."
/>
<PrincipleCard
icon="star"
title="copper = active signal"
description="The copper accent (#A6592D) indicates interactivity and importance. It's the 'hot metal' that draws attention to actions and key information."
/>
<PrincipleCard
icon="sun"
title="generous whitespace"
description="Reduce cognitive load through breathing room. Dense interfaces slow users down. Space creates hierarchy and improves scanability."
/>
<PrincipleCard
icon="key"
title="accessible by default"
description="WCAG AA compliance is the minimum. Focus states are never removed. Color is never the only indicator of meaning."
/>
<PrincipleCard
icon="globe"
title="developer-first"
description="The system is built for developers using developer tools. Technical accuracy over marketing speak. Show don't tell."
/>
</div>
<SubSection title="design philosophy">
<div className="bg-surface-2 p-6 border border-dashed border-border prose-width">
<p className="font-sans text-foreground leading-relaxed mb-4">
TPMJS is a <strong>developer platform</strong> for AI tools. The design reflects
this through industrial aesthetics: sharp edges, technical typography, and
a muted palette with copper as the signal color.
</p>
<p className="font-sans text-foreground-secondary leading-relaxed">
Unlike consumer products that aim for delight, TPMJS aims for
<strong> efficiency and trust</strong>. Users should feel confident that the
interface will behave predictably and help them accomplish tasks quickly.
</p>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,39 @@
'use client';
import { FieldsetSection, SubSection, TokenRow } from './shared';
export function SectionSpacing(): React.ReactElement {
return (
<FieldsetSection title="4. spacing" id="spacing">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Consistent spacing creates visual rhythm. Use the 4px base unit
and generous whitespace for clarity.
</p>
<SubSection title="spacing scale">
<div className="space-y-4">
{[1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 64].map((space) => (
<div key={space} className="flex items-center gap-4">
<span className="font-mono text-xs text-foreground-secondary w-12">{space}</span>
<div
className="bg-accent h-4"
style={{ width: `${space * 4}px` }}
/>
<span className="font-mono text-xs text-foreground-tertiary">{space * 4}px</span>
</div>
))}
</div>
</SubSection>
<SubSection title="semantic spacing">
<div className="space-y-4">
<TokenRow name="--spacing-tight" value="16px (4)" />
<TokenRow name="--spacing-element" value="32px (8)" />
<TokenRow name="--spacing-comfortable" value="48px (12)" />
<TokenRow name="--spacing-component" value="64px (16)" />
<TokenRow name="--spacing-section" value="128px (32)" />
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,95 @@
'use client';
import { Badge } from '@tpmjs/ui/Badge/Badge';
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
import { FieldsetSection, SubSection } from './shared';
export function SectionTheming(): React.ReactElement {
return (
<FieldsetSection title="11. theming" id="theming">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Theme architecture and customization guidelines for consistent extension.
</p>
<SubSection title="token architecture">
<div className="bg-surface p-6 border border-dashed border-border mb-8">
<div className="space-y-4">
<div className="flex items-center gap-4">
<Badge variant="outline">core</Badge>
<span className="font-mono text-sm text-foreground-secondary">
Raw values: HSL colors, pixel sizes, timing functions
</span>
</div>
<div className="flex items-center gap-4">
<Badge variant="outline">semantic</Badge>
<span className="font-mono text-sm text-foreground-secondary">
Named tokens: --primary, --background, --motion-fast
</span>
</div>
<div className="flex items-center gap-4">
<Badge variant="outline">component</Badge>
<span className="font-mono text-sm text-foreground-secondary">
Component-specific: button-bg, input-border, card-shadow
</span>
</div>
</div>
</div>
<CodeBlock
code={`/* Core tokens (don't override) */
--copper-500: 22 57% 41%;
/* Semantic tokens (safe to override) */
--primary: var(--copper-500);
--primary-hover: 22 57% 35%;
--accent: var(--primary);
/* Component tokens (for advanced customization) */
--button-bg: hsl(var(--primary));
--button-hover-bg: hsl(var(--primary-hover));`}
language="css"
/>
</SubSection>
<SubSection title="dark mode">
<p className="font-sans text-sm text-foreground-secondary mb-4">
Dark mode is opt-in via the <code className="font-mono bg-surface px-1">.dark</code> class
on the root element. All semantic tokens have dark mode variants.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-background p-6 border border-dashed border-border">
<p className="font-mono text-sm mb-4">light mode</p>
<div className="flex gap-2">
<div className="w-8 h-8 bg-background border border-border" />
<div className="w-8 h-8 bg-surface border border-border" />
<div className="w-8 h-8 bg-accent" />
<div className="w-8 h-8 bg-foreground" />
</div>
</div>
<div className="bg-[#0F0E0D] p-6 border border-[#363230]">
<p className="font-mono text-sm text-[#E8E5E2] mb-4">dark mode</p>
<div className="flex gap-2">
<div className="w-8 h-8 bg-[#0F0E0D] border border-[#363230]" />
<div className="w-8 h-8 bg-[#171514] border border-[#363230]" />
<div className="w-8 h-8 bg-[#C96A38]" />
<div className="w-8 h-8 bg-[#E8E5E2]" />
</div>
</div>
</div>
</SubSection>
<SubSection title="embedding guidance">
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">for sdk users embedding tpmjs ui</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• Import the CSS variables from <code className="font-mono bg-surface-2 px-1">@tpmjs/ui/styles</code></li>
<li>• Override semantic tokens in your own CSS to match your brand</li>
<li>• Do not override core tokens (raw values)</li>
<li>• Test both light and dark modes if supporting theme switching</li>
<li>• Use <code className="font-mono bg-surface-2 px-1">data-density</code> attribute for density control</li>
</ul>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,104 @@
'use client';
import { FieldsetSection, SubSection, TokenRow } from './shared';
export function SectionTypography(): React.ReactElement {
return (
<FieldsetSection title="3. typography" id="typography">
<p className="text-foreground-secondary mb-8 font-sans prose-width">
Two font families create clear hierarchy: monospace for headings and technical
content, sans-serif for body text and descriptions.
</p>
<SubSection title="font families">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div className="bg-surface p-6 border border-dashed border-border">
<p className="font-mono text-2xl mb-2">JetBrains Mono</p>
<p className="font-mono text-sm text-foreground-secondary mb-4">--font-mono</p>
<p className="font-mono text-sm">Used for: headings, code, data, labels, technical content</p>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<p className="font-sans text-2xl mb-2">Inter</p>
<p className="font-mono text-sm text-foreground-secondary mb-4">--font-sans</p>
<p className="font-sans text-sm">Used for: body text, descriptions, long-form content</p>
</div>
</div>
</SubSection>
<SubSection title="type scale">
<div className="space-y-4">
<TokenRow name="--text-xs" value="12px / 0.75rem" preview={<span className="text-xs font-mono">Aa</span>} />
<TokenRow name="--text-sm" value="14px / 0.875rem" preview={<span className="text-sm font-mono">Aa</span>} />
<TokenRow name="--text-base" value="16px / 1rem" preview={<span className="text-base font-mono">Aa</span>} />
<TokenRow name="--text-lg" value="18px / 1.125rem" preview={<span className="text-lg font-mono">Aa</span>} />
<TokenRow name="--text-xl" value="20px / 1.25rem" preview={<span className="text-xl font-mono">Aa</span>} />
<TokenRow name="--text-2xl" value="24px / 1.5rem" preview={<span className="text-2xl font-mono">Aa</span>} />
<TokenRow name="--text-3xl" value="32px / 2rem" preview={<span className="text-3xl font-mono">Aa</span>} />
<TokenRow name="--text-4xl" value="40px / 2.5rem" preview={<span className="text-4xl font-mono">Aa</span>} />
<TokenRow name="--text-5xl" value="48px / 3rem" preview={<span className="text-5xl font-mono">Aa</span>} />
</div>
</SubSection>
<SubSection title="line height">
<div className="space-y-4">
<TokenRow name="--leading-tight" value="1.2" preview={<div className="w-full h-3 bg-accent/20" />} />
<TokenRow name="--leading-snug" value="1.4" preview={<div className="w-full h-4 bg-accent/20" />} />
<TokenRow name="--leading-normal" value="1.6" preview={<div className="w-full h-5 bg-accent/20" />} />
<TokenRow name="--leading-relaxed" value="1.7" preview={<div className="w-full h-6 bg-accent/20" />} />
<TokenRow name="--leading-loose" value="1.8" preview={<div className="w-full h-7 bg-accent/20" />} />
</div>
</SubSection>
<SubSection title="max line width">
<div className="space-y-4 mb-8">
<TokenRow name="--prose-width-narrow" value="45ch" />
<TokenRow name="--prose-width" value="65ch (default)" />
<TokenRow name="--prose-width-wide" value="80ch" />
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<p className="font-sans text-sm text-foreground-secondary leading-relaxed prose-width">
This paragraph is constrained to 65 characters per line, the optimal width
for reading comprehension. Lines that are too long cause eye fatigue, while
lines that are too short disrupt reading rhythm.
</p>
</div>
</SubSection>
<SubSection title="usage rules">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">monospace (JetBrains Mono)</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• Page titles and section headings</li>
<li>• Code snippets and technical content</li>
<li>• Data values, metrics, timestamps</li>
<li>• Form labels and button text</li>
<li>• Table headers and numeric columns</li>
</ul>
</div>
<div className="bg-surface p-6 border border-dashed border-border">
<h4 className="font-mono text-sm font-medium mb-4">sans-serif (Inter)</h4>
<ul className="space-y-2 text-sm text-foreground-secondary font-sans">
<li>• Body paragraphs and descriptions</li>
<li>• Help text and instructions</li>
<li>• Error messages and notifications</li>
<li>• Marketing and explanatory content</li>
<li>• Long-form documentation</li>
</ul>
</div>
</div>
</SubSection>
<SubSection title="heading hierarchy">
<div className="space-y-6">
<h1 className="font-mono text-5xl font-semibold tracking-tight lowercase">heading 1 (48px)</h1>
<h2 className="font-mono text-4xl font-semibold tracking-tight lowercase">heading 2 (40px)</h2>
<h3 className="font-mono text-3xl font-semibold tracking-tight lowercase">heading 3 (32px)</h3>
<h4 className="font-mono text-2xl font-semibold tracking-tight lowercase">heading 4 (24px)</h4>
<h5 className="font-mono text-xl font-medium lowercase">heading 5 (20px)</h5>
<h6 className="font-mono text-lg font-medium lowercase">heading 6 (18px)</h6>
</div>
</SubSection>
</FieldsetSection>
);
}

View file

@ -0,0 +1,40 @@
// Shared helpers
export {
FieldsetSection,
SubSection,
ColorCard,
DoDontCard,
PrincipleCard,
TokenRow,
NavItem,
} from './shared';
// Foundation sections
export { SectionPrinciples } from './SectionPrinciples';
export { SectionColors } from './SectionColors';
export { SectionTypography } from './SectionTypography';
export { SectionSpacing } from './SectionSpacing';
export { SectionMotion } from './SectionMotion';
// Systems sections
export { SectionAccessibility } from './SectionAccessibility';
export { SectionLayout } from './SectionLayout';
export { SectionContent } from './SectionContent';
export { SectionDataViz } from './SectionDataViz';
export { SectionIcons } from './SectionIcons';
// Implementation sections
export { SectionTheming } from './SectionTheming';
export { SectionComponents } from './SectionComponents';
export { SectionComponentAPIs } from './SectionComponentAPIs';
// Pattern library sections
export { SectionPatternNavigation } from './SectionPatternNavigation';
export { SectionPatternForms } from './SectionPatternForms';
export { SectionPatternFeedback } from './SectionPatternFeedback';
export { SectionPatternTables } from './SectionPatternTables';
export { SectionPatternSearch } from './SectionPatternSearch';
// Additional sections
export { SectionA11yChecklists } from './SectionA11yChecklists';
export { SectionContentGuidelines, SectionIconSystem } from './SectionContentGuidelines';

View file

@ -0,0 +1,169 @@
'use client';
import { Icon } from '@tpmjs/ui/Icon/Icon';
/**
* Fieldset-style container with legend label
*/
export function FieldsetSection({
title,
children,
id,
}: {
title: string;
children: React.ReactNode;
id?: string;
}): React.ReactElement {
return (
<fieldset id={id} className="border border-dashed border-border p-8 mb-16 scroll-mt-24">
<legend className="font-mono text-sm text-foreground-secondary px-3 lowercase">
{title}
</legend>
{children}
</fieldset>
);
}
/**
* Sub-section with title
*/
export function SubSection({
title,
children,
}: {
title: string;
children: React.ReactNode;
}): React.ReactElement {
return (
<div className="mb-10 last:mb-0">
<h3 className="font-mono text-base font-medium mb-6 text-foreground lowercase">{title}</h3>
{children}
</div>
);
}
/**
* Color swatch card
*/
export function ColorCard({
name,
color,
hex,
desc,
textLight = false,
}: {
name: string;
color: string;
hex: string;
desc: string;
textLight?: boolean;
}): React.ReactElement {
return (
<div className={`${color} p-4 border border-dashed border-border`}>
<div className={`font-mono text-sm font-medium ${textLight ? 'text-white' : 'text-foreground'}`}>
{name}
</div>
<div className={`font-mono text-xs ${textLight ? 'text-white/80' : 'text-foreground-secondary'}`}>
{hex}
</div>
<div className={`font-mono text-xs mt-2 ${textLight ? 'text-white/60' : 'text-foreground-tertiary'}`}>
{desc}
</div>
</div>
);
}
/**
* Do/Don't example card
*/
export function DoDontCard({
type,
title,
children,
}: {
type: 'do' | 'dont';
title: string;
children: React.ReactNode;
}): React.ReactElement {
const isDo = type === 'do';
return (
<div className={`border-2 ${isDo ? 'border-success' : 'border-error'} p-4`}>
<div className={`flex items-center gap-2 mb-3 ${isDo ? 'text-success' : 'text-error'}`}>
<Icon icon={isDo ? 'check' : 'x'} size="sm" />
<span className="font-mono text-sm font-medium uppercase">{isDo ? 'do' : "don't"}</span>
</div>
<p className="font-mono text-xs text-foreground-secondary mb-3">{title}</p>
<div className="bg-surface p-3 border border-dashed border-border">
{children}
</div>
</div>
);
}
/**
* Principle card with icon
*/
export function PrincipleCard({
icon,
title,
description,
}: {
icon: string;
title: string;
description: string;
}): React.ReactElement {
return (
<div className="border border-dashed border-border p-6">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 bg-accent/10 flex items-center justify-center">
<Icon icon={icon as Parameters<typeof Icon>[0]['icon']} size="md" className="text-accent" />
</div>
<h4 className="font-mono text-base font-medium text-foreground">{title}</h4>
</div>
<p className="font-sans text-sm text-foreground-secondary leading-relaxed">{description}</p>
</div>
);
}
/**
* Token display row
*/
export function TokenRow({
name,
value,
preview,
}: {
name: string;
value: string;
preview?: React.ReactNode;
}): React.ReactElement {
return (
<div className="flex items-center gap-4 py-3 border-b border-dashed border-border last:border-0">
<code className="font-mono text-xs text-accent w-48">{name}</code>
<span className="font-mono text-xs text-foreground-secondary flex-1">{value}</span>
{preview && <div className="w-24">{preview}</div>}
</div>
);
}
/**
* Navigation item for table of contents
*/
export function NavItem({
href,
children,
indent = false,
}: {
href: string;
children: React.ReactNode;
indent?: boolean;
}): React.ReactElement {
return (
<a
href={href}
className={`block font-mono text-sm text-foreground-secondary hover:text-accent transition-colors ${indent ? 'pl-4' : ''}`}
>
{children}
</a>
);
}

View file

@ -8,5 +8,22 @@ export default {
'./src/components/**/*.{ts,tsx}',
'../../packages/ui/src/**/*.ts',
],
safelist: [
...(baseConfig.safelist || []),
// Extended color utilities for style guide
'bg-accent-strong',
'bg-accent-muted',
'bg-surface-2',
'bg-surface-3',
'bg-success-light',
'bg-warning-light',
'bg-error-light',
'bg-info-light',
'text-foreground-secondary',
'text-foreground-tertiary',
'text-foreground-muted',
'border-border-strong',
'border-border-subtle',
],
plugins: [...(baseConfig.plugins || []), require('@tailwindcss/typography')],
} satisfies Config;