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.tsx→src/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
};