Claude Code Plugins

Community-maintained marketplace

Feedback

Storybook stories, CSF3 format, autodocs. Use for component documentation.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name storybook
description Storybook stories, CSF3 format, autodocs. Use for component documentation.

Storybook Skill

Atomic Rule

Компонент в shared/ → story обов'язково в тому ж коміті.

F1 creates component → F1 creates story → Same commit

Before Creating UI

  1. Перевір Storybook на існуючі компоненти
  2. Використовуй існуючі замість створення нових
  3. Дотримуйся patterns з Design System
just storybook  # http://localhost:6006

Story Requirements

CSF3 Format

import type { Meta, StoryObj } from '@storybook/react-vite';
import { ComponentName } from './ComponentName';

const meta: Meta<typeof ComponentName> = {
  title: 'Category/ComponentName',  // See naming below
  component: ComponentName,
  tags: ['autodocs'],               // REQUIRED!
  parameters: {
    docs: {
      description: {
        component: 'Brief description of the component purpose.',
      },
    },
  },
};

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

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

Naming Convention

Location Title Prefix Example
shared/ui/ UI/ UI/Button, UI/Card
shared/patterns/ Design System/Patterns/ Design System/Patterns/CardWithStatus
shared/components/ Components/ Components/DataTable
features/*/components/ Features/{Domain}/ Features/Analysis/RunCard

Minimum Stories by Tier

Tier Location Minimum Stories
1 shared/ui/ 4-6 (Default, Variants, States, Sizes)
2 shared/patterns/ 5-8 (All statuses, compositions, use cases)
3 features/ 2-4 (Default, Empty, Loading, Error)

Required Story Types

For Shared UI (Tier 1)

// REQUIRED
export const Default: Story = { args: { ... } };

// If component has variants
export const Primary: Story = { args: { variant: 'primary' } };
export const Secondary: Story = { args: { variant: 'secondary' } };
export const Destructive: Story = { args: { variant: 'destructive' } };

// If component has sizes
export const Small: Story = { args: { size: 'sm' } };
export const Large: Story = { args: { size: 'lg' } };

// If component has states
export const Disabled: Story = { args: { disabled: true } };
export const Loading: Story = { args: { loading: true } };

For Patterns (Tier 2)

// REQUIRED - All status states
export const Connected: Story = { args: { status: 'connected' } };
export const Validating: Story = { args: { status: 'validating' } };
export const Pending: Story = { args: { status: 'pending' } };
export const Error: Story = { args: { status: 'error' } };

// REQUIRED - Compositions
export const WithFooter: Story = { args: { footer: <Button>Action</Button> } };
export const WithContent: Story = { args: { children: <div>...</div> } };

// REQUIRED - Layout examples
export const CardGrid: Story = {
  render: () => (
    <div className="grid grid-cols-3 gap-4">
      <Component ... />
      <Component ... />
      <Component ... />
    </div>
  ),
};

For Feature Components (Tier 3)

// REQUIRED
export const Default: Story = { args: { ... } };
export const Empty: Story = { args: { items: [] } };
export const Loading: Story = { args: { isLoading: true } };
export const Error: Story = { args: { error: new Error('Failed to load') } };

Providers for Stories

Hook Used Required Provider
useTheme ThemeProvider
useLocation, Link MemoryRouter
useQuery QueryClientProvider
useSidebar SidebarProvider

Decorator Template

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MemoryRouter } from 'react-router-dom';
import { ThemeProvider } from '@/shared/components/ThemeProvider';

const queryClient = new QueryClient();

const meta: Meta<typeof Component> = {
  component: Component,
  decorators: [
    (Story) => (
      <QueryClientProvider client={queryClient}>
        <ThemeProvider>
          <MemoryRouter>
            <Story />
          </MemoryRouter>
        </ThemeProvider>
      </QueryClientProvider>
    ),
  ],
};

When Story Is Mandatory

Location Mandatory? Notes
shared/ui/ ✅ Yes All UI primitives
shared/patterns/ ✅ Yes All patterns
shared/components/ ✅ Yes If exported publicly
features/*/components/ ⚠️ Conditional If >50 LOC or reused
pages/ ❌ No Use E2E tests

Output Format

When creating component + story:

✅ Component implemented

Component: [Name]
Files:
- [component path]
- [story path]
Story: [Tier] / [# of stories]
Verify: npm run build && npx tsc --noEmit

Interaction Testing

Use play functions to test user interactions directly in stories.

Basic Interaction Test

import { expect, within, userEvent } from '@storybook/test';

export const ClickTest: Story = {
  args: {
    onClick: fn(),  // Mock function from @storybook/test
  },
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);

    // Find and click button
    const button = canvas.getByRole('button');
    await userEvent.click(button);

    // Assert callback was called
    await expect(args.onClick).toHaveBeenCalled();
  },
};

Form Interaction Test

export const FormSubmit: Story = {
  args: {
    onSubmit: fn(),
  },
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);

    // Fill form
    await userEvent.type(canvas.getByLabelText('Email'), 'test@example.com');
    await userEvent.type(canvas.getByLabelText('Password'), 'password123');

    // Submit
    await userEvent.click(canvas.getByRole('button', { name: /submit/i }));

    // Assert
    await expect(args.onSubmit).toHaveBeenCalledWith({
      email: 'test@example.com',
      password: 'password123',
    });
  },
};

Dialog/Modal Test

export const DialogOpen: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Open dialog
    await userEvent.click(canvas.getByRole('button', { name: /open/i }));

    // Wait for dialog
    await expect(canvas.findByRole('dialog')).resolves.toBeInTheDocument();

    // Close with ESC
    await userEvent.keyboard('{Escape}');
    await expect(canvas.queryByRole('dialog')).not.toBeInTheDocument();
  },
};

Keyboard Navigation Test

export const KeyboardNav: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Tab through elements
    await userEvent.tab();
    await expect(canvas.getByRole('button', { name: /first/i })).toHaveFocus();

    await userEvent.tab();
    await expect(canvas.getByRole('button', { name: /second/i })).toHaveFocus();

    // Activate with Enter
    await userEvent.keyboard('{Enter}');
  },
};

When to Add Interaction Tests

Component Type Interaction Test Required?
Buttons with onClick ✅ Yes
Forms ✅ Yes
Dialogs/Modals ✅ Yes
Dropdowns ✅ Yes
Static display ❌ No
Layout components ❌ No

Running Interaction Tests

# In Storybook UI - click "Interactions" tab
just storybook

# CLI (requires Storybook running)
just storybook-test

# CI mode
just storybook-test-ci

Templates

Use templates from @templates/ directory:

  • shared-ui.template.tsx — for shared/ui/ components
  • pattern.template.tsx — for shared/patterns/ components
  • feature.template.tsx — for features/*/ components

References