import type { Meta, StoryObj } from '@storybook/nextjs-vite' import { Moon, Sun } from 'lucide-react' import React from 'react' import { Alert, AlertDescription, AlertTitle } from '../components/ui/alert' import { Badge } from '../components/ui/badge' import { Button } from '../components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../components/ui/card' import { Input } from '../components/ui/input' import { ThemeProvider, useThemeToggle } from '../components/providers/theme-provider' const meta = { title: 'Foundations/Theme', parameters: { layout: 'fullscreen', docs: { description: { component: 'Demonstrates the ODS light/dark theme system. The `ThemeProvider` (wrapping `next-themes`) sets `data-theme="light|dark"` on ``, and `src/styles/ods-colors.css` swaps the `--ods-*` primitives accordingly. Use `useThemeToggle()` to build your own toggle UI.', }, }, }, } satisfies Meta export default meta type Story = StoryObj /* ------------------------------------------------------------------ */ /* Helpers */ /* ------------------------------------------------------------------ */ function ThemeToggleButton() { const { isDark, toggle, mounted } = useThemeToggle() return ( ) } function ThemeStatusBar() { const { theme, isDark, setTheme, toggle, mounted } = useThemeToggle() return (
Current theme: {mounted ? theme : '…'}
) } interface SwatchProps { name: string /** Tailwind class that consumes the token (e.g. `bg-ods-bg`). */ className: string /** Render dark text instead of light (for very light tokens). */ darkLabel?: boolean } function Swatch({ name, className, darkLabel }: SwatchProps) { return (
{name.replace(/^bg-|^text-|^border-/, '')}
{name}
) } function TokenGrid() { return (

Surfaces

Text

text-ods-text-primary — primary text

text-ods-text-secondary — secondary text

text-ods-text-tertiary — tertiary text

text-ods-text-muted — muted text

text-ods-text-disabled — disabled text

Accent & status

Borders

border-ods-border
border-ods-border-hover
border-ods-border-focus
) } function ComponentsShowcase() { return (

Buttons

Inputs

Badges

Default Secondary Outline Success Destructive

Cards

Card title Card surface and text adapt to the active theme.

All colors come from --ods-* tokens — the same markup renders correctly in both themes.

Inputs & actions Try interacting — focus rings flip too.

Alerts

Informational Default alert surface — uses card background and primary text tokens.
) } /* ------------------------------------------------------------------ */ /* Stories */ /* ------------------------------------------------------------------ */ /** * Full showcase — toggle the theme and watch every primitive flip in place. * * The whole story is wrapped in ``. `useThemeToggle()` is used * to drive the toggle button (and `setTheme('light' | 'dark')` is wired to the * explicit "Set light / Set dark" buttons). All visible color comes from ODS * tokens, so nothing is hardcoded. */ export const Showcase: Story = { render: () => (

ODS theme switching

One data-theme attribute on{' '} <html> flips every{' '} --ods-* primitive. Components below don't know — and don't care — which theme is active; they read tokens.

), } /** * Minimal example: just the toggle button and a single card. * * Useful as a copy-paste reference for what consumer apps need to do to add a * theme switch: wrap once in ``, then build any button you like * around `useThemeToggle()`. */ export const ToggleOnly: Story = { render: () => (
Theme toggle Click the button below — the entire surface, text and border swap themes.
), } /** * Side-by-side: force light and dark on two halves of the screen at once using * the `.theme-light` / `.theme-dark` class escape hatches (no provider needed * for these — they directly scope the primitive overrides). Handy for visual * diffing without flipping the document. */ export const SideBySide: Story = { render: () => (
{(['light', 'dark'] as const).map((mode) => (
{mode} scoped via .theme-{mode}
Same component, different theme Both halves render identical JSX — only the wrapping class differs.
Default Success Error
))}
), }