veza/apps/web/docs/STORYBOOK_CONTRACT.md

3.9 KiB

Storybook Contract — Veza Design System

Purpose

This document defines the mandatory structure and conventions for all Storybook stories in the Veza frontend. It is referenced by .cursorrules and must be followed by all contributors and AI assistants.

Story Requirements

1. Feature Components

Every Feature component (components in src/features/) must have the following story variants:

Story Name Description Required
Default Component with typical data Yes
Loading Component in loading state (using Skeleton components) Yes
Error Component displaying an error fallback Yes
Empty Component with no data (empty list/state) Yes, if applicable

2. UI Components

Every UI component (components in src/components/ui/) must have:

Story Name Description Required
Default Default variant/state Yes
Variants All visual variants (size, color, etc.) If applicable
Disabled Disabled state If applicable
WithIcon With icon prop If applicable

3. Layout Components

Layout components (src/components/layout/) should demonstrate:

  • Responsive behavior (mobile, tablet, desktop)
  • Light and dark modes

Decorators

Global Decorator

All stories automatically receive the global StorybookDecorator configured in .storybook/preview.tsx. This provides:

  • Theme provider (dark/light mode)
  • i18n provider
  • Router context (MemoryRouter)
  • Toast provider
  • MSW request interception

Do NOT Import Application Providers

Never import application-level providers directly in stories:

// BAD — do not do this
import { AuthProvider } from '@/providers/AuthProvider';
export const MyStory = () => (
  <AuthProvider>
    <MyComponent />
  </AuthProvider>
);

// GOOD — the global decorator handles providers
export const MyStory = () => <MyComponent />;

Data Mocking

Use MSW Handlers

All API data should be mocked via MSW handlers in src/mocks/handlers.ts:

// In the story file
import type { Meta, StoryObj } from '@storybook/react';
import { http, HttpResponse } from 'msw';

const meta: Meta<typeof MyComponent> = {
  component: MyComponent,
  parameters: {
    msw: {
      handlers: [
        http.get('*/api/v1/my-endpoint', () => {
          return HttpResponse.json({
            success: true,
            data: { items: [] },
          });
        }),
      ],
    },
  },
};

Loading States

Use delay: 'infinite' in MSW handlers to simulate loading:

export const Loading: StoryObj<typeof MyComponent> = {
  parameters: {
    msw: {
      handlers: [
        http.get('*/api/v1/my-endpoint', async () => {
          await new Promise(() => {}); // Never resolves
        }),
      ],
    },
  },
};

Error States

Return error responses from MSW handlers:

export const Error: StoryObj<typeof MyComponent> = {
  parameters: {
    msw: {
      handlers: [
        http.get('*/api/v1/my-endpoint', () => {
          return HttpResponse.json(
            { success: false, error: { message: 'Server error' } },
            { status: 500 },
          );
        }),
      ],
    },
  },
};

File Naming Convention

  • Story files: ComponentName.stories.tsx
  • Co-located with their component file
  • Example: src/components/ui/button.tsxsrc/components/ui/button.stories.tsx

Story File Template

import type { Meta, StoryObj } from '@storybook/react';
import { MyComponent } from './MyComponent';

const meta: Meta<typeof MyComponent> = {
  title: 'Features/MyFeature/MyComponent',
  component: MyComponent,
  tags: ['autodocs'],
};

export default meta;
type Story = StoryObj<typeof MyComponent>;

export const Default: Story = {
  args: {
    // Default props
  },
};

export const Loading: Story = {
  // Loading state configuration
};

export const Error: Story = {
  // Error state configuration
};

export const Empty: Story = {
  // Empty state configuration
};