From edfdf2ff7c9cdbe66e7e50361b169226a74e7338 Mon Sep 17 00:00:00 2001 From: Baskarayelu Date: Mon, 29 Jun 2026 13:18:18 +0530 Subject: [PATCH] feat: Add per-component design-system docs page (#289) Add DesignSystemDocs page at /design-system/docs listing all UI components with live examples, design tokens, and prop tables. Includes expand/collapse controls and WCAG 2.1 AA accessibility. Also register the route in App.tsx and add 4 focused Vitest tests. Co-Authored-By: Claude Sonnet 4.6 --- src/App.tsx | 8 + src/pages/DesignSystemDocs.test.tsx | 44 ++++ src/pages/DesignSystemDocs.tsx | 365 ++++++++++++++++++++++++++++ 3 files changed, 417 insertions(+) create mode 100644 src/pages/DesignSystemDocs.test.tsx create mode 100644 src/pages/DesignSystemDocs.tsx diff --git a/src/App.tsx b/src/App.tsx index f53678f..e622aec 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import RouteProgressBar from './components/RouteProgressBar'; import ServerError from './components/ServerError'; import useDocumentTitle from "./hooks/useDocumentTitle"; import NotFound from './components/NotFound'; +import DesignSystemDocs from './pages/DesignSystemDocs'; import { startRouteLoading, stopRouteLoading } from './hooks/useRouteLoading'; import useDocumentTitle from './hooks/useDocumentTitle'; import { formatUsdc, formatUsdShortcut } from './utils/format'; @@ -109,6 +110,7 @@ const APP_ROUTES = { documentation: "/documentation", status: "/status", themePlayground: "/theme-playground", + designSystem: "/design-system/docs", serverError: "/500", } as const; @@ -550,6 +552,7 @@ function App() { isActive ? "link-nav active" : "link-nav"}>Marketplace isActive ? "link-nav active" : "link-nav"}>Billing isActive ? "link-nav active" : "link-nav"}>Theme Playground + isActive ? "link-nav active" : "link-nav"}>Design System @@ -709,6 +712,11 @@ function App() { } /> + } + /> + { + afterEach(() => { + cleanup(); + }); + + it("renders the page heading", () => { + render(); + expect(screen.getByRole("heading", { name: "Design System" })).toBeTruthy(); + }); + + it("shows the first component expanded by default and the rest collapsed", () => { + render(); + // First component section (Primary Button) is open by default + expect(screen.getByText("Live example")).toBeTruthy(); + // Verify the collapse/expand toggle buttons are present + const expandAllBtn = screen.getByRole("button", { name: "Expand all" }); + expect(expandAllBtn).toBeTruthy(); + }); + + it("collapses all sections when 'Collapse all' is clicked", () => { + render(); + fireEvent.click(screen.getByRole("button", { name: "Collapse all" })); + // All component headers remain but no prop tables are shown + expect(screen.queryAllByText("Live example")).toHaveLength(0); + }); + + it("expands all sections when 'Expand all' is clicked", () => { + render(); + // Collapse everything first + fireEvent.click(screen.getByRole("button", { name: "Collapse all" })); + expect(screen.queryAllByText("Live example")).toHaveLength(0); + // Then expand all + fireEvent.click(screen.getByRole("button", { name: "Expand all" })); + // Every component section should now show a "Live example" label + const liveExampleLabels = screen.queryAllByText("Live example"); + expect(liveExampleLabels.length).toBeGreaterThan(1); + }); +}); diff --git a/src/pages/DesignSystemDocs.tsx b/src/pages/DesignSystemDocs.tsx new file mode 100644 index 0000000..0861bfa --- /dev/null +++ b/src/pages/DesignSystemDocs.tsx @@ -0,0 +1,365 @@ +/** + * DesignSystemDocs – internal /design-system/docs page. + * + * Lists every documented UI component with a live example, usage notes, and + * the design tokens it consumes. Intended for contributors and designers so + * the entire component library is discoverable in one place. + */ + +import { useState } from "react"; +import useDocumentTitle from "../hooks/useDocumentTitle"; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +interface PropDoc { + name: string; + type: string; + description: string; +} + +interface ComponentDoc { + id: string; + name: string; + description: string; + tokens: string[]; + props: PropDoc[]; + /** Inline render of a live example */ + example: () => JSX.Element; +} + +// --------------------------------------------------------------------------- +// Component catalogue +// --------------------------------------------------------------------------- + +const COMPONENT_DOCS: ComponentDoc[] = [ + { + id: "button-primary", + name: "Primary Button", + description: + "The main call-to-action button. Uses --theme-primary and should be used sparingly – once per view.", + tokens: ["--theme-primary", "--radius-md", "--space-3"], + props: [ + { name: "onClick", type: "() => void", description: "Click handler." }, + { name: "disabled", type: "boolean", description: "Disables interaction and reduces opacity." }, + { name: "children", type: "ReactNode", description: "Button label." }, + ], + example: () => ( + + ), + }, + { + id: "button-secondary", + name: "Secondary Button", + description: + "Ghost-style button for secondary or cancel actions. Never used as the sole action on a page.", + tokens: ["--color-border", "--radius-md", "--space-3"], + props: [ + { name: "onClick", type: "() => void", description: "Click handler." }, + { name: "disabled", type: "boolean", description: "Disables interaction." }, + { name: "children", type: "ReactNode", description: "Button label." }, + ], + example: () => ( + + ), + }, + { + id: "surface-card", + name: "Surface Card", + description: + "Elevated container used for dashboard tiles, vault balance cards, and info panels.", + tokens: ["--color-surface", "--color-surface-raised", "--radius-lg", "--shadow-md"], + props: [ + { name: "children", type: "ReactNode", description: "Card body content." }, + { name: "className", type: "string", description: "Additional CSS classes." }, + ], + example: () => ( +
+ Vault balance + 284.62 USDC +
+ ), + }, + { + id: "eyebrow", + name: "Eyebrow Label", + description: + "Small uppercase label used above headings to establish section context.", + tokens: ["--color-accent", "--font-size-xs", "--letter-spacing-wide"], + props: [ + { name: "children", type: "ReactNode", description: "Label text." }, + ], + example: () =>

Core capabilities

, + }, + { + id: "status-chip", + name: "Status Chip", + description: + "Inline badge that surfaces transaction or system state at a glance.", + tokens: ["--color-success", "--color-error", "--color-warn", "--radius-full"], + props: [ + { + name: "status", + type: "'input' | 'approving' | 'pending' | 'confirmed' | 'failed'", + description: "Controls colour and label.", + }, + ], + example: () => ( +
+ {(["input", "approving", "pending", "confirmed", "failed"] as const).map( + (s) => ( + + {s} + + ), + )} +
+ ), + }, + { + id: "skip-link", + name: "Skip Link", + description: + "Visually hidden anchor that becomes visible on focus, allowing keyboard users to bypass the navigation and jump straight to main content. Required for WCAG 2.1 AA compliance.", + tokens: ["--color-surface", "--theme-primary", "--z-skip-link"], + props: [ + { name: "href", type: "string", description: "Target element id, e.g. #main-content." }, + { name: "children", type: "ReactNode", description: "Link text shown on focus." }, + ], + example: () => ( + + Skip to main content + + ), + }, +]; + +// --------------------------------------------------------------------------- +// Sub-components +// --------------------------------------------------------------------------- + +function PropTable({ props }: { props: PropDoc[] }) { + return ( + + + + {["Prop", "Type", "Description"].map((h) => ( + + ))} + + + + {props.map((p) => ( + + + + + + ))} + +
+ {h} +
{p.name} + {p.type} + {p.description}
+ ); +} + +function TokenList({ tokens }: { tokens: string[] }) { + return ( +
    + {tokens.map((t) => ( +
  • + {t} +
  • + ))} +
+ ); +} + +interface ComponentSectionProps { + doc: ComponentDoc; + isOpen: boolean; + onToggle: () => void; +} + +function ComponentSection({ doc, isOpen, onToggle }: ComponentSectionProps) { + return ( +
+ {/* Header row – always visible */} + + + {/* Collapsible body */} + {isOpen && ( +
+

{doc.description}

+ + {/* Live example */} +
+

+ Live example +

+
+ +
+
+ + {/* Design tokens */} +
+

+ Design tokens +

+ +
+ + {/* Props table */} +
+

+ Props +

+ +
+
+ )} +
+ ); +} + +// --------------------------------------------------------------------------- +// Page +// --------------------------------------------------------------------------- + +export default function DesignSystemDocs(): JSX.Element { + useDocumentTitle( + "Design System – Callora", + "Internal design-system documentation listing all UI components with live examples, props, and design tokens.", + ); + + const [openIds, setOpenIds] = useState>( + new Set([COMPONENT_DOCS[0].id]), + ); + + const toggle = (id: string) => { + setOpenIds((prev) => { + const next = new Set(prev); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } + return next; + }); + }; + + const expandAll = () => + setOpenIds(new Set(COMPONENT_DOCS.map((d) => d.id))); + const collapseAll = () => setOpenIds(new Set()); + + return ( +
+ {/* Page header */} +
+

Internal reference

+

Design System

+

+ Per-component documentation with live examples, design tokens, and + prop definitions. All components adhere to WCAG 2.1 AA and use + dark-mode-consistent design tokens. +

+ +
+ + +
+
+ + {/* Component list */} +
+ {COMPONENT_DOCS.map((doc) => ( + toggle(doc.id)} + /> + ))} +
+
+ ); +}