Exemples Storybook

Exemples complets de Storybook pour le développement de composants, le testing, la documentation et la gestion des systèmes de design

💻 Basics de Storybook javascript

🟢 simple ⭐⭐

Configuration de base de Storybook, écriture d'histoires et patterns de documentation de composants

⏱️ 30 min 🏷️ storybook, components, documentation
Prerequisites: React, JavaScript/TypeScript, CSS, Component concepts
// Storybook Basics
// File: .storybook/main.js - Storybook Configuration

module.exports = {
  stories: [
    '../stories/**/*.stories.mdx',
    '../stories/**/*.stories.@(js|jsx|ts|tsx|mdx)',
    '../src/**/*.stories.@(js|jsx|ts|tsx|mdx)',
  ],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    '@storybook/addon-a11y',
    '@storybook/addon-docs',
    '@storybook/addon-controls',
    '@storybook/addon-backgrounds',
    '@storybook/addon-viewport',
    '@storybook/addon-toolbars',
    '@storybook/addon-design-tokens',
  ],
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
  docs: {
    autodocs: 'tag',
  },
  features: {
    buildStoriesJson: true,
    storyStoreV7: true,
  },
  typescript: {
    check: false,
    reactDocgen: 'react-docgen-typescript',
    reactDocgenTypescriptOptions: {
      shouldExtractLiteralValuesFromEnum: true,
      propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
    },
  },
}

// File: .storybook/preview.js - Global Configuration

export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
  docs: {
    toc: true,
  },
  backgrounds: {
    default: 'light',
    values: [
      {
        name: 'light',
        value: '#ffffff',
      },
      {
        name: 'dark',
        value: '#333333',
      },
      {
        name: 'gray',
        value: '#f5f5f5',
      },
    ],
  },
  viewport: {
    viewports: {
      mobile: {
        name: 'Mobile',
        styles: {
          width: '375px',
          height: '667px',
        },
      },
      tablet: {
        name: 'Tablet',
        styles: {
          width: '768px',
          height: '1024px',
        },
      },
      desktop: {
        name: 'Desktop',
        styles: {
          width: '1024px',
          height: '768px',
        },
      },
      wide: {
        name: 'Wide',
        styles: {
          width: '1440px',
          height: '900px',
        },
      },
    },
    defaultViewport: 'desktop',
  },
}

// File: .storybook/manager.js - Manager Configuration

import { addons } from '@storybook/addons'

addons.setConfig({
  theme: {
    brandTitle: 'My Component Library',
    brandUrl: 'https://example.com',
    brandImage: 'https://example.com/logo.png',
  },
  panelPosition: 'right',
  sidebar: {
    showRoots: true,
    collapsedRoots: ['examples'],
  },
})

// File: src/components/Button/Button.stories.js - Basic Component Stories

import React from 'react'
import { Button } from './Button'

// Default metadata
export default {
  title: 'Components/Button',
  component: Button,
  parameters: {
    layout: 'centered',
    docs: {
      description: {
        component: 'A versatile button component with multiple variants and sizes.',
      },
    },
  },
  argTypes: {
    variant: {
      control: {
        type: 'select',
      },
      options: ['primary', 'secondary', 'danger', 'ghost'],
      description: 'Button style variant',
    },
    size: {
      control: {
        type: 'radio',
      },
      options: ['small', 'medium', 'large'],
      description: 'Button size',
    },
    disabled: {
      control: 'boolean',
      description: 'Whether the button is disabled',
    },
    loading: {
      control: 'boolean',
      description: 'Show loading state',
    },
    onClick: {
      action: 'clicked',
      description: 'Click event handler',
    },
    children: {
      control: {
        type: 'text',
      },
      description: 'Button content',
    },
  },
}

// Template for creating stories
const Template = (args) => <Button {...args} />

// Default story
export const Default = Template.bind({})
Default.args = {
  children: 'Click me',
}

// Different variants
export const Primary = Template.bind({})
Primary.args = {
  children: 'Primary Button',
  variant: 'primary',
}

export const Secondary = Template.bind({})
Secondary.args = {
  children: 'Secondary Button',
  variant: 'secondary',
}

export const Danger = Template.bind({})
Danger.args = {
  children: 'Danger Button',
  variant: 'danger',
}

export const Ghost = Template.bind({})
Ghost.args = {
  children: 'Ghost Button',
  variant: 'ghost',
}

// Different sizes
export const Small = Template.bind({})
Small.args = {
  children: 'Small Button',
  size: 'small',
  variant: 'primary',
}

export const Medium = Template.bind({})
Medium.args = {
  children: 'Medium Button',
  size: 'medium',
  variant: 'primary',
}

export const Large = Template.bind({})
Large.args = {
  children: 'Large Button',
  size: 'large',
  variant: 'primary',
}

// States
export const Disabled = Template.bind({})
Disabled.args = {
  children: 'Disabled Button',
  disabled: true,
}

export const Loading = Template.bind({})
Loading.args = {
  children: 'Loading...',
  loading: true,
}

// Complex examples
export const WithIcon = Template.bind({})
WithIcon.args = {
  children: (
    <>
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path d="M12 2L2 7V12C2 16.55 4.84 20.74 9 22C13.16 20.74 16 16.55 16 12V7L12 2Z" fill="currentColor"/>
      </svg>
      <span style={{ marginLeft: '8px' }}>Secure Login</span>
    </>
  ),
  variant: 'primary',
}

// Interactive story
export const Interactive = () => {
  const [count, setCount] = React.useState(0)

  return (
    <div>
      <Button onClick={() => setCount(count + 1)}>
        Clicked {count} times
      </Button>
    </div>
  )
}

// File: src/components/Card/Card.stories.js - Complex Component Stories

import React from 'react'
import { Card } from './Card'

export default {
  title: 'Components/Card',
  component: Card,
  parameters: {
    layout: 'padded',
    docs: {
      description: {
        component: 'A flexible card component for displaying content in a contained format.',
      },
    },
  },
  argTypes: {
    title: {
      control: 'text',
      description: 'Card title',
    },
    subtitle: {
      control: 'text',
      description: 'Card subtitle',
    },
    image: {
      control: 'text',
      description: 'Card image URL',
    },
    footer: {
      control: 'text',
      description: 'Card footer content',
    },
    interactive: {
      control: 'boolean',
      description: 'Whether the card is interactive (hoverable)',
    },
  },
}

const Template = (args) => <Card {...args} />

export const Default = Template.bind({})
Default.args = {
  title: 'Card Title',
  children: 'This is the card content. You can put any content here.',
}

export const WithSubtitle = Template.bind({})
WithSubtitle.args = {
  title: 'Card with Subtitle',
  subtitle: 'This is a subtitle',
  children: 'Card content with subtitle above.',
}

export const WithImage = Template.bind({})
WithImage.args = {
  title: 'Card with Image',
  image: 'https://picsum.photos/400/200',
  children: 'This card has an image at the top.',
}

export const WithFooter = Template.bind({})
WithFooter.args = {
  title: 'Card with Footer',
  footer: (
    <div style={{ display: 'flex', justifyContent: 'space-between' }}>
      <span>Footer content</span>
      <Button>Action</Button>
    </div>
  ),
  children: 'This card has a footer section.',
}

export const Interactive = Template.bind({})
Interactive.args = {
  title: 'Interactive Card',
  interactive: true,
  children: 'This card is interactive and responds to hover.',
}

// File: src/stories/Introduction.stories.mdx - Documentation Stories

import { Meta, Story, Canvas, ArgsTable } from '@storybook/blocks'
import { Button, Card, Input } from '../src/components'

<Meta title="Documentation/Introduction" />

# Welcome to Our Component Library

This is the documentation for our React component library built with Storybook.

## Getting Started

Our components are designed to be:

- **Accessible**: Following WCAG guidelines
- **Responsive**: Working on all screen sizes
- **Customizable**: With consistent design tokens
- **Well-tested**: With comprehensive test coverage

## Basic Usage

<Canvas>
  <Story name="ButtonExample">
    <Button variant="primary">Hello World</Button>
  </Story>
</Canvas>

## Component Examples

### Button Component

The Button component is versatile and supports multiple variants:

<Canvas>
  <Story name="ButtonVariants">
    <div style={{ display: 'flex', gap: '1rem' }}>
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="danger">Danger</Button>
      <Button variant="ghost">Ghost</Button>
    </div>
  </Story>
</Canvas>

<ArgsTable of={Button} />

### Card Component

Cards are perfect for displaying grouped information:

<Canvas>
  <Story name="CardExample">
    <Card
      title="Sample Card"
      image="https://picsum.photos/400/200"
      footer={<Button variant="primary">Learn More</Button>}
    >
      This is an example card with an image and footer action.
    </Card>
  </Story>
</Canvas>

## Design Tokens

Our components use a consistent design token system:

```javascript
// Primary colors
colors: {
  primary: '#0070f3',
  secondary: '#6c757d',
  success: '#28a745',
  danger: '#dc3545',
  warning: '#ffc107',
}

// Spacing
spacing: {
  xs: '0.25rem',
  sm: '0.5rem',
  md: '1rem',
  lg: '1.5rem',
  xl: '2rem',
}
```

// File: src/components/ThemeProvider/ThemeProvider.stories.js - Theme Provider

import React from 'react'
import { ThemeProvider } from './ThemeProvider'
import { Button, Card, Input } from '../index'

export default {
  title: 'Design System/ThemeProvider',
  component: ThemeProvider,
  parameters: {
    docs: {
      description: {
        component: 'Theme provider for managing global styles and design tokens.',
      },
    },
  },
}

const theme = {
  colors: {
    primary: '#0070f3',
    secondary: '#6c757d',
    background: '#ffffff',
    text: '#333333',
  },
  spacing: {
    sm: '0.5rem',
    md: '1rem',
    lg: '1.5rem',
    xl: '2rem',
  },
}

export const Default = () => (
  <ThemeProvider theme={theme}>
    <div style={{ padding: '2rem' }}>
      <h1>Themed Components</h1>
      <div style={{ marginBottom: '1rem' }}>
        <Button variant="primary">Themed Button</Button>
      </div>
      <Card title="Themed Card">
        This card uses the theme provider for consistent styling.
      </Card>
    </div>
  </ThemeProvider>
)

export const DarkTheme = () => {
  const darkTheme = {
    ...theme,
    colors: {
      ...theme.colors,
      background: '#1a1a1a',
      text: '#ffffff',
    },
  }

  return (
    <ThemeProvider theme={darkTheme}>
      <div style={{ padding: '2rem', backgroundColor: '#1a1a1a', minHeight: '100vh' }}>
        <h1 style={{ color: '#ffffff' }}>Dark Theme</h1>
        <div style={{ marginBottom: '1rem' }}>
          <Button variant="primary">Dark Button</Button>
        </div>
        <Card title="Dark Card">
          This card uses the dark theme.
        </Card>
      </div>
    </ThemeProvider>
  )
}

💻 Patterns Avancés de Storybook javascript

🔴 complex ⭐⭐⭐⭐

Techniques avancées de Storybook incluant les interactions, le testing, l'automatisation et l'intégration avec les outils de design

⏱️ 45 min 🏷️ storybook, advanced, automation, testing
Prerequisites: Storybook basics, React hooks, Testing frameworks, CSS-in-JS
// Advanced Storybook Patterns
// File: .storybook/main.js - Advanced Configuration

module.exports = {
  stories: [
    '../stories/**/*.stories.@(js|jsx|ts|tsx|mdx)',
    '../src/**/*.stories.@(js|jsx|ts|tsx|mdx)',
  ],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    '@storybook/addon-a11y',
    '@storybook/addon-docs',
    '@storybook/addon-controls',
    '@storybook/addon-backgrounds',
    '@storybook/addon-viewport',
    '@storybook/addon-toolbars',
    '@storybook/addon-design-tokens',
    '@storybook/addon-storysource',
    '@storybook/preset-create-react-app',
    'storybook-addon-designs',
    'storybook-addon-measure',
    'storybook-addon-performance',
    '@storybook/addon-jest',
    '@storybook/testing-react',
  ],
  framework: '@storybook/react-vite',
  core: {
    disableTelemetry: true,
    enableCrashReports: false,
  },
  env: (config) => ({
    ...config,
    STORYBOOK_ENVIRONMENT: process.env.NODE_ENV || 'development',
  }),
  webpackFinal: async (config) => {
    // Custom webpack configuration
    config.resolve.alias = {
      ...config.resolve.alias,
      '@': require('path').resolve(__dirname, '../src'),
    }
    return config
  },
  viteFinal: async (config) => {
    // Custom Vite configuration
    config.optimizeDeps = {
      ...config.optimizeDeps,
      include: ['react', 'react-dom'],
    }
    return config
  },
}

// File: .storybook/preview-head.html - Custom Head HTML

<style>
  /* Global styles for Storybook */
  body {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    margin: 0;
    padding: 0;
  }

  /* Custom CSS variables for theming */
  :root {
    --color-primary: #0070f3;
    --color-secondary: #6c757d;
    --color-success: #28a745;
    --color-danger: #dc3545;
    --color-warning: #ffc107;
    --spacing-xs: 0.25rem;
    --spacing-sm: 0.5rem;
    --spacing-md: 1rem;
    --spacing-lg: 1.5rem;
    --spacing-xl: 2rem;
  }

  /* Loading animation */
  @keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
  }

  .loading {
    animation: spin 1s linear infinite;
  }
</style>

<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

// File: src/components/Dropdown/Dropdown.stories.js - Interactive Components

import React, { useState } from 'react'
import { Dropdown, DropdownItem } from './Dropdown'
import { userEvent, within, waitFor } from '@storybook/testing-library'

export default {
  title: 'Components/Dropdown',
  component: Dropdown,
  parameters: {
    layout: 'centered',
    docs: {
      description: {
        component: 'Interactive dropdown component with keyboard navigation and accessibility features.',
      },
    },
  },
  argTypes: {
    trigger: {
      control: 'text',
      description: 'Trigger button text',
    },
    placement: {
      control: {
        type: 'select',
      },
      options: ['bottom-start', 'bottom-end', 'top-start', 'top-end'],
      description: 'Dropdown placement',
    },
    disabled: {
      control: 'boolean',
      description: 'Disable the dropdown',
    },
  },
}

const Template = (args) => {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <Dropdown
      {...args}
      isOpen={isOpen}
      onToggle={setIsOpen}
    >
      <DropdownItem onClick={() => console.log('Action 1')}>
        Action 1
      </DropdownItem>
      <DropdownItem onClick={() => console.log('Action 2')}>
        Action 2
      </DropdownItem>
      <DropdownItem disabled>
        Disabled Item
      </DropdownItem>
      <DropdownItem danger onClick={() => console.log('Delete')}>
        Delete
      </DropdownItem>
    </Dropdown>
  )
}

export const Default = Template.bind({})
Default.args = {
  trigger: 'Click me',
}

export const Disabled = Template.bind({})
Disabled.args = {
  trigger: 'Disabled Dropdown',
  disabled: true,
}

// Interactive play function for testing
Default.play = async ({ canvasElement }) => {
  const canvas = within(canvasElement)

  // Find and click the trigger
  const trigger = canvas.getByRole('button', { name: /click me/i })
  await userEvent.click(trigger)

  // Wait for dropdown to appear
  await waitFor(() => {
    expect(canvas.getByRole('menu')).toBeInTheDocument()
  })

  // Verify menu items
  expect(canvas.getByRole('menuitem', { name: /action 1/i })).toBeInTheDocument()
  expect(canvas.getByRole('menuitem', { name: /action 2/i })).toBeInTheDocument()
  expect(canvas.getByRole('menuitem', { name: /delete/i })).toBeInTheDocument()
}

// File: src/components/Form/Form.stories.js - Complex Form Stories

import React, { useState } from 'react'
import { Form, FormField, FormSubmit } from './Form'

export default {
  title: 'Components/Form',
  component: Form,
  parameters: {
    docs: {
      description: {
        component: 'Advanced form component with validation, field management, and submission handling.',
      },
    },
  },
}

const FormTemplate = (args) => {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
    remember: false,
  })

  const handleSubmit = (data) => {
    console.log('Form submitted:', data)
    alert('Form submitted successfully!')
  }

  return (
    <Form {...args} onSubmit={handleSubmit} initialData={formData}>
      <FormField
        name="email"
        label="Email Address"
        type="email"
        required
        placeholder="Enter your email"
      />
      <FormField
        name="password"
        label="Password"
        type="password"
        required
        placeholder="Enter your password"
      />
      <FormField
        name="remember"
        type="checkbox"
        label="Remember me"
      />
      <FormSubmit>Sign In</FormSubmit>
    </Form>
  )
}

export const Default = FormTemplate.bind({})
Default.args = {
  title: 'Sign In',
}

export const WithValidation = FormTemplate.bind({})
WithValidation.args = {
  title: 'Sign In',
  validation: {
    email: {
      required: true,
      pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
    },
    password: {
      required: true,
      minLength: 8,
    },
  },
}

export const MultiStep = () => {
  const [currentStep, setCurrentStep] = useState(0)
  const [formData, setFormData] = useState({})

  const steps = [
    {
      title: 'Personal Information',
      fields: [
        { name: 'firstName', label: 'First Name', required: true },
        { name: 'lastName', label: 'Last Name', required: true },
        { name: 'email', label: 'Email', type: 'email', required: true },
      ],
    },
    {
      title: 'Address',
      fields: [
        { name: 'street', label: 'Street Address', required: true },
        { name: 'city', label: 'City', required: true },
        { name: 'zipCode', label: 'ZIP Code', required: true },
      ],
    },
    {
      title: 'Preferences',
      fields: [
        { name: 'newsletter', type: 'checkbox', label: 'Subscribe to newsletter' },
        { name: 'notifications', type: 'checkbox', label: 'Enable notifications' },
      ],
    },
  ]

  const currentStepData = steps[currentStep]

  return (
    <div style={{ maxWidth: '500px', margin: '0 auto' }}>
      <h2>{currentStepData.title}</h2>

      <Form
        onSubmit={(data) => {
          const updatedData = { ...formData, ...data }
          if (currentStep < steps.length - 1) {
            setFormData(updatedData)
            setCurrentStep(currentStep + 1)
          } else {
            console.log('Form completed:', updatedData)
            alert('Form completed successfully!')
          }
        }}
      >
        {currentStepData.fields.map((field) => (
          <FormField
            key={field.name}
            name={field.name}
            label={field.label}
            type={field.type || 'text'}
            required={field.required}
          />
        ))}

        <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '1rem' }}>
          <button
            type="button"
            onClick={() => setCurrentStep(Math.max(0, currentStep - 1))}
            disabled={currentStep === 0}
          >
            Previous
          </button>
          <button type="submit">
            {currentStep === steps.length - 1 ? 'Submit' : 'Next'}
          </button>
        </div>
      </Form>
    </div>
  )
}

// File: .storybook/test-runner.js - Automated Testing

import { test, expect } from '@storybook/test-runner'
import { waitForElementToBeRemoved } from '@testing-library/dom'

test('renders button in default state', async ({ page }) => {
  await page.goto('/iframe.html?id=components-button--default')
  await expect(page.locator('button')).toContainText('Click me')
})

test('button click event works', async ({ page }) => {
  await page.goto('/iframe.html?id=components-button--default&args=onClick:onClick')
  await page.click('button')
  await expect(page.locator('.action-log')).toContainText('clicked')
})

test('form validation works', async ({ page }) => {
  await page.goto('/iframe.html?id=components-form--with-validation')

  // Try to submit empty form
  await page.click('button[type="submit"]')

  // Check for error messages
  await expect(page.locator('text=Email is required')).toBeVisible()
  await expect(page.locator('text=Password is required')).toBeVisible()

  // Fill in form correctly
  await page.fill('input[name="email"]', '[email protected]')
  await page.fill('input[name="password"]', 'password123')

  // Submit should succeed
  await page.click('button[type="submit"]')
  await waitForElementToBeRemoved(() => page.locator('text=Email is required'))
})

// File: src/stories/playground.playground.tsx - Interactive Playground

import React, { useState } from 'react'
import { Playground } from '@storybook/addon-docs'
import { Button, Input, Card } from '../src/components'

<Playground
  kind="Components/Interactive"
  story="playground"
>
  {{
    component: () => {
      const [count, setCount] = useState(0)
      const [text, setText] = useState('')

      return (
        <div style={{ padding: '2rem', maxWidth: '400px' }}>
          <Card title="Interactive Playground">
            <div style={{ marginBottom: '1rem' }}>
              <Input
                placeholder="Type something..."
                value={text}
                onChange={(e) => setText(e.target.value)}
              />
            </div>

            <div style={{ marginBottom: '1rem' }}>
              <p>You typed: {text}</p>
            </div>

            <div style={{ display: 'flex', gap: '0.5rem' }}>
              <Button onClick={() => setCount(count + 1)}>
                Count: {count}
              </Button>
              <Button variant="secondary" onClick={() => setCount(0)}>
                Reset
              </Button>
            </div>
          </Card>
        </div>
      )
    }
  }}
</Playground>

// File: src/hooks/useLocalStorage/useLocalStorage.stories.js - Hook Stories

import React from 'react'
import { useLocalStorage } from './useLocalStorage'
import { Meta, Story, Canvas, ArgsTable } from '@storybook/blocks'

export default {
  title: 'Hooks/useLocalStorage',
  component: useLocalStorage,
  parameters: {
    docs: {
      description: {
        component: 'Custom hook for persisting state in localStorage with SSR support.',
      },
    },
  },
}

export const Default = () => {
  const [value, setValue] = useLocalStorage('my-key', 'default-value')

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
        style={{ padding: '0.5rem', marginRight: '0.5rem' }}
      />
      <button onClick={() => setValue('')}>
        Clear
      </button>
      <p>Current value: {value}</p>
    </div>
  )
}

export const WithObject = () => {
  const [user, setUser] = useLocalStorage('user', {
    name: '',
    email: '',
    preferences: { theme: 'light' },
  })

  return (
    <div>
      <div style={{ marginBottom: '1rem' }}>
        <input
          type="text"
          placeholder="Name"
          value={user.name}
          onChange={(e) => setUser({ ...user, name: e.target.value })}
          style={{ display: 'block', marginBottom: '0.5rem', padding: '0.25rem' }}
        />
        <input
          type="email"
          placeholder="Email"
          value={user.email}
          onChange={(e) => setUser({ ...user, email: e.target.value })}
          style={{ display: 'block', marginBottom: '0.5rem', padding: '0.25rem' }}
        />
        <select
          value={user.preferences.theme}
          onChange={(e) => setUser({
            ...user,
            preferences: { ...user.preferences, theme: e.target.value }
          })}
          style={{ display: 'block', padding: '0.25rem' }}
        >
          <option value="light">Light</option>
          <option value="dark">Dark</option>
        </select>
      </div>

      <pre style={{ background: '#f5f5f5', padding: '1rem', borderRadius: '4px' }}>
        {JSON.stringify(user, null, 2)}
      </pre>
    </div>
  )
}

// File: .storybook/design-tokens.js - Design Token Integration

import { addDecorator } from '@storybook/preview'

// Design tokens for consistent styling
const designTokens = {
  colors: {
    primary: {
      50: '#eff6ff',
      100: '#dbeafe',
      500: '#3b82f6',
      600: '#2563eb',
      700: '#1d4ed8',
    },
    gray: {
      50: '#f9fafb',
      100: '#f3f4f6',
      500: '#6b7280',
      600: '#4b5563',
      700: '#374151',
    },
  },
  spacing: {
    1: '0.25rem',
    2: '0.5rem',
    3: '0.75rem',
    4: '1rem',
    5: '1.25rem',
    6: '1.5rem',
    8: '2rem',
    12: '3rem',
    16: '4rem',
  },
  typography: {
    fontFamily: {
      sans: ['Inter', 'system-ui', 'sans-serif'],
      mono: ['Fira Code', 'Monaco', 'monospace'],
    },
    fontSize: {
      xs: '0.75rem',
      sm: '0.875rem',
      base: '1rem',
      lg: '1.125rem',
      xl: '1.25rem',
    },
  },
}

// Apply design tokens globally
addDecorator((Story, context) => {
  return (
    <div
      style={{
        '--color-primary-50': designTokens.colors.primary[50],
        '--color-primary-500': designTokens.colors.primary[500],
        '--color-primary-600': designTokens.colors.primary[600],
        '--color-gray-100': designTokens.colors.gray[100],
        '--color-gray-600': designTokens.colors.gray[600],
        '--spacing-4': designTokens.spacing[4],
        '--spacing-6': designTokens.spacing[6],
        '--font-sans': designTokens.typography.fontFamily.sans.join(', '),
        '--font-base': designTokens.typography.fontSize.base,
      }}
    >
      <Story {...context} />
    </div>
  )
})

export default designTokens