Files
gh-shaunrfox-okshaun-claude…/skills/panda-create-stories.md
2025-11-30 08:56:16 +08:00

33 KiB

name, description
name description
panda-create-stories Create Storybook stories that document and demonstrate Panda CSS components with variants, accessibility testing, and interactive controls

Panda CSS Component Stories

When to Use This Skill

Use this skill when:

  • Creating Storybook documentation for Panda CSS components
  • Demonstrating component variants and props
  • Building a component library with visual documentation
  • Testing component accessibility interactively
  • Showcasing responsive behavior and theme switching
  • Creating a design system playground
  • Onboarding developers to component APIs

This skill assumes you have:

  • An existing component (use panda-component-impl skill to create one)
  • Storybook installed and configured in your project
  • Basic understanding of component props and variants

Storybook Setup Prerequisites

Quick Storybook Check

Before creating stories, verify Storybook is installed:

# Check for Storybook dependencies
ls -la .storybook/

# Look for Storybook scripts in package.json
cat package.json | grep storybook

If Storybook Not Installed

Install Storybook with modern defaults:

npx storybook@latest init

Recommended addons for Panda CSS projects:

npm install --save-dev @storybook/addon-a11y @storybook/addon-themes @storybook/test

Storybook Configuration for Panda CSS

Ensure .storybook/main.ts includes your Panda CSS output:

import type { StorybookConfig } from '@storybook/react-vite'

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(ts|tsx)'],
  addons: [
    '@storybook/addon-essentials',
    '@storybook/addon-a11y',         // Accessibility testing
    '@storybook/addon-themes',       // Theme switching
    '@storybook/addon-interactions', // Interactive testing
  ],
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
}

export default config

Configure theme switching in .storybook/preview.tsx:

import type { Preview } from '@storybook/react'
import { withThemeByClassName } from '@storybook/addon-themes'

// Import Panda CSS output
import '../styled-system/styles.css'

const preview: Preview = {
  decorators: [
    withThemeByClassName({
      themes: {
        light: '',          // No class for light (default)
        dark: 'dark',       // Add 'dark' class for dark mode
      },
      defaultTheme: 'light',
    }),
  ],
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  },
}

export default preview

Story File Structure

File Naming Convention

Stories are colocated with components:

src/
  components/
    Button/
      Button.tsx          # Component implementation
      Button.stories.tsx  # Stories file
      index.tsx           # Public exports
    CheckBox/
      CheckBox.tsx
      CheckBox.stories.tsx
      index.tsx

Pattern: ComponentName.stories.tsx in same directory as component.

Basic Story Structure (CSF3 Format)

CSF3 (Component Story Format 3) is the modern, concise format.

Create: src/components/Button/Button.stories.tsx

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

// Meta defines default component configuration
const meta = {
  title: 'Components/Button',
  component: Button,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'outline', 'ghost'],
      description: 'Visual style variant',
    },
    size: {
      control: 'radio',
      options: ['small', 'medium', 'large'],
      description: 'Size variant',
    },
    disabled: {
      control: 'boolean',
      description: 'Disable the button',
    },
    loading: {
      control: 'boolean',
      description: 'Show loading state',
    },
  },
} satisfies Meta<typeof Button>

export default meta
type Story = StoryObj<typeof meta>

// Default story - most common usage
export const Primary: Story = {
  args: {
    children: 'Button',
    variant: 'primary',
    size: 'medium',
  },
}

// Additional variant stories
export const Secondary: Story = {
  args: {
    children: 'Button',
    variant: 'secondary',
    size: 'medium',
  },
}

export const Outline: Story = {
  args: {
    children: 'Button',
    variant: 'outline',
    size: 'medium',
  },
}

export const Ghost: Story = {
  args: {
    children: 'Button',
    variant: 'ghost',
    size: 'medium',
  },
}

// Size variants
export const Small: Story = {
  args: {
    children: 'Button',
    variant: 'primary',
    size: 'small',
  },
}

export const Large: Story = {
  args: {
    children: 'Button',
    variant: 'primary',
    size: 'large',
  },
}

// State stories
export const Disabled: Story = {
  args: {
    children: 'Button',
    variant: 'primary',
    disabled: true,
  },
}

export const Loading: Story = {
  args: {
    children: 'Button',
    variant: 'primary',
    loading: true,
  },
}

// With icons (if component supports)
export const WithLeftIcon: Story = {
  args: {
    children: 'Button',
    variant: 'primary',
    leftIcon: '←',
  },
}

Story Creation Process with TodoWrite

When creating stories for a component, use TodoWrite to track systematic coverage:

Create Story Creation Checklist

TodoWrite({
  todos: [
    { content: "Read component file and inspect actual props", status: "pending", activeForm: "Reading component file and inspecting actual props" },
    { content: "Create story file with meta configuration", status: "pending", activeForm: "Creating story file with meta configuration" },
    { content: "Document props with argTypes (only actual component props)", status: "pending", activeForm: "Documenting props with argTypes" },
    { content: "Add Default story showing primary usage", status: "pending", activeForm: "Adding Default story showing primary usage" },
    { content: "Add All States story showing all variants/states together", status: "pending", activeForm: "Adding All States story showing all variants/states together" },
    { content: "Add example stories with 'Ex:' prefix (interactive, patterns)", status: "pending", activeForm: "Adding example stories with 'Ex:' prefix" },
    { content: "Add accessibility stories with 'A11y:' prefix (keyboard, focus)", status: "pending", activeForm: "Adding accessibility stories with 'A11y:' prefix" },
    { content: "Test stories in Storybook UI", status: "pending", activeForm: "Testing stories in Storybook UI" },
  ]
})

CRITICAL: Inspect Component Props First

Before creating argTypes, read the component file to understand its actual props:

// Read the component file
const componentCode = await Read({ file_path: 'src/components/Button/Button.tsx' })

// Identify the prop type definition
// Example: export type ButtonProps = BoxProps & ButtonVariantProps & { ... }

// Only include props that are:
// 1. Explicitly defined in the component's prop type
// 2. Relevant for user control (not internal implementation details)
// 3. Actually used by the component

// DO NOT include props from base components (like 'as' from Box) unless the component explicitly exposes them

Meta Configuration Patterns

Basic Meta

const meta = {
  title: 'Components/Button',           // Sidebar organization
  component: Button,                    // Component reference
  tags: ['autodocs'],                   // Generate docs automatically
} satisfies Meta<typeof Button>

Meta with Parameters

const meta = {
  title: 'Components/Button',
  component: Button,
  parameters: {
    layout: 'centered',                 // Center in canvas
    docs: {
      description: {
        component: 'A versatile button component with multiple variants and states.',
      },
    },
  },
  tags: ['autodocs'],
} satisfies Meta<typeof Button>

Meta with ArgTypes (Control Panel)

const meta = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'outline', 'ghost'],
      description: 'Visual style variant',
      table: {
        type: { summary: 'string' },
        defaultValue: { summary: 'primary' },
      },
    },
    size: {
      control: 'radio',
      options: ['small', 'medium', 'large'],
      description: 'Size variant',
    },
    onClick: {
      action: 'clicked',                // Log to Actions panel
    },
    disabled: {
      control: 'boolean',
    },
    // Hide props that shouldn't be controlled
    className: {
      table: { disable: true },
    },
  },
} satisfies Meta<typeof Button>

Story Patterns for Panda CSS Components

Recommended Story Structure

Prefer this structure for better Storybook organization:

  1. Default - Most common usage (1 story)
  2. All States - All variants/states shown together (1 story)
  3. Ex: [Pattern Name] - Example patterns and use cases (multiple stories)
  4. A11y: [Test Name] - Accessibility testing stories (multiple stories)

Why this approach?

  • Reduces clutter: One "All States" story instead of 6+ individual state stories
  • Better scanning: Prefixes group related stories together in sidebar
  • Easier comparison: See all states side-by-side
  • Clearer purpose: "Ex:" for examples, "A11y:" for accessibility tests

Pattern 1: All States Story (Preferred)

Show all variant combinations and states in one comprehensive view:

export const AllVariants: Story = {
  render: () => (
    <Box display="flex" flexDirection="column" gap="20">
      {/* Primary variants */}
      <Box display="flex" gap="12">
        <Button variant="primary" size="small">Small</Button>
        <Button variant="primary" size="medium">Medium</Button>
        <Button variant="primary" size="large">Large</Button>
      </Box>

      {/* Secondary variants */}
      <Box display="flex" gap="12">
        <Button variant="secondary" size="small">Small</Button>
        <Button variant="secondary" size="medium">Medium</Button>
        <Button variant="secondary" size="large">Large</Button>
      </Box>

      {/* Outline variants */}
      <Box display="flex" gap="12">
        <Button variant="outline" size="small">Small</Button>
        <Button variant="outline" size="medium">Medium</Button>
        <Button variant="outline" size="large">Large</Button>
      </Box>

      {/* Ghost variants */}
      <Box display="flex" gap="12">
        <Button variant="ghost" size="small">Small</Button>
        <Button variant="ghost" size="medium">Medium</Button>
        <Button variant="ghost" size="large">Large</Button>
      </Box>
    </Box>
  ),
}

Pattern 2: Example Stories (Use "Ex:" Prefix)

Show interactive patterns and real-world use cases. Use "Ex:" prefix in the story name for better organization:

// Note: Story names in sidebar will show as "Ex: Interactive Toggle"
export const ExInteractiveToggle: Story = {
  name: 'Ex: Interactive Toggle',
  render: () => {
    const [isOn, setIsOn] = useState(false)
    return (
      <Button
        variant="primary"
        onClick={() => setIsOn(!isOn)}
        aria-pressed={isOn}
      >
        {isOn ? 'On' : 'Off'}
      </Button>
    )
  },
}

export const ExFormIntegration: Story = {
  name: 'Ex: Form Integration',
  render: () => (
    <form onSubmit={(e) => { e.preventDefault(); alert('Submitted!') }}>
      <Button type="submit" variant="primary">Submit Form</Button>
    </form>
  ),
}

export const ExLoadingSimulation: Story = {
  name: 'Ex: Loading Simulation',
  render: () => {
    const [loading, setLoading] = useState(false)
    const handleClick = () => {
      setLoading(true)
      setTimeout(() => setLoading(false), 2000)
    }
    return (
      <Button variant="primary" loading={loading} onClick={handleClick}>
        {loading ? 'Loading...' : 'Click to Load'}
      </Button>
    )
  },
}

Why "Ex:" prefix?

  • Groups all example stories together in Storybook sidebar
  • Clearly indicates these are usage examples, not baseline states
  • Easier to scan and find relevant patterns

Pattern 3: Theme Comparison

Show light and dark themes side by side:

export const ThemeComparison: Story = {
  render: () => (
    <Box display="flex" gap="40">
      {/* Light theme */}
      <Box p="20" bg="white">
        <Box mb="8" fontSize="sm" fontWeight="semibold">Light Theme</Box>
        <Box display="flex" flexDirection="column" gap="12">
          <Button variant="primary">Primary</Button>
          <Button variant="secondary">Secondary</Button>
          <Button variant="outline">Outline</Button>
        </Box>
      </Box>

      {/* Dark theme */}
      <Box className="dark" p="20" bg="slate.90">
        <Box mb="8" fontSize="sm" fontWeight="semibold" color="white">Dark Theme</Box>
        <Box display="flex" flexDirection="column" gap="12">
          <Button variant="primary">Primary</Button>
          <Button variant="secondary">Secondary</Button>
          <Button variant="outline">Outline</Button>
        </Box>
      </Box>
    </Box>
  ),
}

Pattern 4: Responsive Behavior

Show responsive props:

export const Responsive: Story = {
  render: () => (
    <Box
      display={{ base: 'block', md: 'flex' }}
      gap={{ md: '12' }}
    >
      <Button
        size={{ base: 'small', md: 'medium', lg: 'large' }}
        variant="primary"
      >
        Responsive Size
      </Button>
      <Button
        width={{ base: 'full', md: 'auto' }}
        variant="secondary"
      >
        Responsive Width
      </Button>
    </Box>
  ),
  parameters: {
    viewport: {
      defaultViewport: 'mobile1',
    },
  },
}

Accessibility Stories with Play Functions (Use "A11y:" Prefix)

Play functions enable automated accessibility testing. Use "A11y:" prefix for these stories to group them together in the sidebar.

Basic Click Interaction

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

export const A11yClickInteraction: Story = {
  name: 'A11y: Click Interaction',
  args: {
    children: 'Click Me',
    variant: 'primary',
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement)

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

    // Click it
    await userEvent.click(button)

    // Verify it's clickable (no errors thrown)
  },
}

Keyboard Navigation Test

export const A11yKeyboardNavigation: Story = {
  name: 'A11y: Keyboard Navigation',
  render: () => (
    <Box display="flex" gap="12">
      <Button variant="primary">First</Button>
      <Button variant="primary">Second</Button>
      <Button variant="primary">Third</Button>
    </Box>
  ),
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement)

    const firstButton = canvas.getByRole('button', { name: /first/i })

    // Focus first button
    firstButton.focus()

    // Tab to next button
    await userEvent.tab()

    // Verify second button is focused
    const secondButton = canvas.getByRole('button', { name: /second/i })
    expect(secondButton).toHaveFocus()
  },
}

Form Interaction Test

export const A11yFormSubmission: Story = {
  name: 'A11y: Form Submission',
  render: () => {
    const handleSubmit = (e: React.FormEvent) => {
      e.preventDefault()
      console.log('Form submitted')
    }

    return (
      <form onSubmit={handleSubmit}>
        <Box display="flex" flexDirection="column" gap="12">
          <input type="text" placeholder="Name" />
          <Button type="submit" variant="primary">Submit</Button>
        </Box>
      </form>
    )
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement)

    // Type in input
    const input = canvas.getByPlaceholderText('Name')
    await userEvent.type(input, 'John Doe')

    // Click submit
    const submitButton = canvas.getByRole('button', { name: /submit/i })
    await userEvent.click(submitButton)
  },
}

Accessibility Validation

import { expect } from '@storybook/test'

export const A11yAccessibilityCheck: Story = {
  name: 'A11y: Accessibility Check',
  args: {
    children: 'Accessible Button',
    variant: 'primary',
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement)

    const button = canvas.getByRole('button')

    // Verify button has accessible name
    expect(button).toHaveAccessibleName()

    // Verify focus visible on keyboard interaction
    button.focus()
    expect(button).toHaveFocus()

    // Verify disabled buttons are not clickable
  },
  parameters: {
    a11y: {
      config: {
        rules: [
          {
            id: 'color-contrast',
            enabled: true,
          },
        ],
      },
    },
  },
}

Slot Recipe Component Stories

Multi-part components require special story patterns.

CheckBox Stories Example

Create: src/components/CheckBox/CheckBox.stories.tsx

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

const meta = {
  title: 'Components/CheckBox',
  component: CheckBox,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
  argTypes: {
    size: {
      control: 'radio',
      options: ['small', 'medium', 'large'],
    },
    checked: {
      control: 'boolean',
    },
    disabled: {
      control: 'boolean',
    },
    indeterminate: {
      control: 'boolean',
    },
    error: {
      control: 'boolean',
    },
  },
} satisfies Meta<typeof CheckBox>

export default meta
type Story = StoryObj<typeof meta>

export const Default: Story = {
  args: {
    label: 'Accept terms',
  },
}

export const Checked: Story = {
  args: {
    label: 'Checked',
    checked: true,
  },
}

export const Indeterminate: Story = {
  args: {
    label: 'Indeterminate',
    indeterminate: true,
  },
}

export const Disabled: Story = {
  args: {
    label: 'Disabled',
    disabled: true,
  },
}

export const Error: Story = {
  args: {
    label: 'Has error',
    error: true,
  },
}

// Show all states together
export const AllStates: Story = {
  render: () => (
    <Box display="flex" flexDirection="column" gap="12">
      <CheckBox label="Unchecked" />
      <CheckBox label="Checked" checked />
      <CheckBox label="Indeterminate" indeterminate />
      <CheckBox label="Disabled" disabled />
      <CheckBox label="Checked Disabled" checked disabled />
      <CheckBox label="Error" error />
    </Box>
  ),
}

// Interactive checkbox group
export const CheckboxGroup: Story = {
  render: () => {
    const [checked, setChecked] = React.useState({
      option1: false,
      option2: true,
      option3: false,
    })

    return (
      <Box display="flex" flexDirection="column" gap="12">
        <CheckBox
          label="Option 1"
          checked={checked.option1}
          onChange={(e) => setChecked({ ...checked, option1: e.target.checked })}
        />
        <CheckBox
          label="Option 2"
          checked={checked.option2}
          onChange={(e) => setChecked({ ...checked, option2: e.target.checked })}
        />
        <CheckBox
          label="Option 3"
          checked={checked.option3}
          onChange={(e) => setChecked({ ...checked, option3: e.target.checked })}
        />
      </Box>
    )
  },
}

ArgTypes Configuration Reference

Control Types

argTypes: {
  // Select dropdown
  variant: {
    control: 'select',
    options: ['primary', 'secondary', 'outline'],
  },

  // Radio buttons (for fewer options)
  size: {
    control: 'radio',
    options: ['small', 'medium', 'large'],
  },

  // Text input
  label: {
    control: 'text',
  },

  // Number input with range
  opacity: {
    control: { type: 'range', min: 0, max: 1, step: 0.1 },
  },

  // Boolean checkbox
  disabled: {
    control: 'boolean',
  },

  // Color picker
  backgroundColor: {
    control: 'color',
  },

  // Date picker
  publishDate: {
    control: 'date',
  },

  // Object editor
  config: {
    control: 'object',
  },

  // Action logger (for event handlers)
  onClick: {
    action: 'clicked',
  },

  // Hide from controls
  className: {
    table: { disable: true },
  },
}

ArgType Documentation

argTypes: {
  variant: {
    control: 'select',
    options: ['primary', 'secondary', 'outline', 'ghost'],
    description: 'The visual style variant of the button',
    table: {
      type: { summary: 'string' },
      defaultValue: { summary: 'primary' },
      category: 'Appearance',
    },
  },
  size: {
    control: 'radio',
    options: ['small', 'medium', 'large'],
    description: 'Controls the size and padding of the button',
    table: {
      type: { summary: 'string' },
      defaultValue: { summary: 'medium' },
      category: 'Appearance',
    },
  },
  disabled: {
    control: 'boolean',
    description: 'Disables the button and prevents interaction',
    table: {
      type: { summary: 'boolean' },
      defaultValue: { summary: 'false' },
      category: 'State',
    },
  },
}

Best Practices Checklist

Create TodoWrite items when creating stories:

  • Read component file and inspect actual props before creating argTypes
  • Story file is colocated with component (same directory)
  • Using CSF3 format (modern, concise)
  • Default story shows most common usage
  • All States story shows all variants/states together (preferred over individual state stories)
  • ArgTypes document only actual component props (not inherited props like 'as' from Box)
  • Example stories use "Ex:" prefix for better sidebar organization
  • Accessibility stories use "A11y:" prefix for better sidebar organization
  • Interactive examples use play functions
  • Accessibility checked with @storybook/addon-a11y
  • Theme switching works (light/dark) if component is theme-aware
  • Responsive behavior demonstrated if applicable
  • Stories follow consistent naming convention
  • Stories don't include implementation details
  • Meta title follows hierarchy (Components/Category/Name)

Common Story Patterns

Composition Story

Show how component composes with others:

export const ComposedWithIcon: Story = {
  render: () => (
    <Button variant="primary">
      <Icon name="check" size="16" />
      <span>With Icon</span>
    </Button>
  ),
}

Custom Styling Override

Show how to override with Panda CSS props:

export const CustomStyling: Story = {
  render: () => (
    <Button
      variant="primary"
      bg="purple.50"        // Override background
      px="40"               // Override padding
      borderRadius="full"   // Override border radius
    >
      Custom Styled
    </Button>
  ),
}

Loading State Simulation

Demonstrate async behavior:

export const LoadingSimulation: Story = {
  render: () => {
    const [loading, setLoading] = React.useState(false)

    const handleClick = () => {
      setLoading(true)
      setTimeout(() => setLoading(false), 2000)
    }

    return (
      <Button
        variant="primary"
        loading={loading}
        onClick={handleClick}
      >
        {loading ? 'Loading...' : 'Click to Load'}
      </Button>
    )
  },
}

Story Organization

Title Hierarchy

Organize stories in logical groups:

// Button variations
title: 'Components/Button'           // Basic components
title: 'Components/Inputs/TextInput' // Nested categories
title: 'Patterns/Forms/LoginForm'    // Pattern examples
title: 'Layouts/Grid'                // Layout components

Parameters for Layout

parameters: {
  layout: 'centered',     // Center in canvas (default for small components)
  layout: 'fullscreen',   // Full viewport (for pages/layouts)
  layout: 'padded',       // Add padding around component
}

Tags

tags: [
  'autodocs',           // Generate documentation automatically
  'dev',                // Only show in dev environment
  'test',               // Mark as test story
]

Accessing Storybook Documentation

For advanced patterns and latest best practices:

// Resolve Storybook library ID
mcp__MCP_DOCKER__resolve-library-id({ libraryName: "storybook" })

// Fetch relevant documentation
mcp__MCP_DOCKER__get-library-docs({
  context7CompatibleLibraryID: "/storybookjs/storybook",
  topic: "writing-stories" // or "args", "play-function", "theming"
})

Topics to reference:

  • writing-stories - CSF3 format and story patterns
  • args - ArgTypes and controls
  • play-function - Interactive testing
  • theming - Theme switching and decoration

Common Pitfalls

Avoid: Complex Logic in Stories

// BAD: Business logic in story
export const BadStory: Story = {
  render: () => {
    const data = fetchDataFromAPI() // Don't fetch real data
    const processed = complexProcessing(data) // Don't do heavy computation
    return <Button>{processed}</Button>
  },
}

// GOOD: Simple, declarative stories
export const GoodStory: Story = {
  args: {
    children: 'Button Text',
    variant: 'primary',
  },
}

Avoid: Testing Implementation Details

// BAD: Testing internal state
export const BadTest: Story = {
  play: async ({ canvasElement }) => {
    const button = canvasElement.querySelector('.button-internal-class')
    expect(button.state.isActive).toBe(true) // Don't access internal state
  },
}

// GOOD: Testing user-visible behavior
export const GoodTest: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement)
    const button = canvas.getByRole('button')
    await userEvent.click(button)
    expect(button).toHaveAttribute('aria-pressed', 'true')
  },
}

Avoid: Not Using ArgTypes

// BAD: No controls
const meta = {
  title: 'Components/Button',
  component: Button,
} satisfies Meta<typeof Button>

// GOOD: Document with argTypes
const meta = {
  title: 'Components/Button',
  component: Button,
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary'],
      description: 'Visual style variant',
    },
  },
} satisfies Meta<typeof Button>

Integration with Panda CSS Workflows

This skill works with:

  • panda-component-impl: Reference component patterns for story examples
  • panda-recipe-patterns: Use recipe variants in story demonstrations
  • panda-review-component: Stories help validate component accessibility

Complete Example: Button Stories

This example demonstrates all the recommended patterns:

import type { Meta, StoryObj } from '@storybook/react'
import { useState } from 'react'
import { userEvent, within, expect } from '@storybook/test'
import { Button } from './Button'
import { Box } from '../Box/Box'

const meta = {
  title: 'Components/Button',
  component: Button,
  parameters: {
    layout: 'centered',
    docs: {
      description: {
        component: 'A versatile button component with multiple variants, sizes, and states. Built with Panda CSS recipes.',
      },
    },
  },
  tags: ['autodocs'],
  argTypes: {
    // IMPORTANT: Only include props that are explicitly defined in ButtonProps
    // Do NOT include inherited props like 'as' from BoxProps unless Button explicitly exposes them
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'outline', 'ghost'],
      description: 'Visual style variant',
      table: {
        defaultValue: { summary: 'primary' },
      },
    },
    size: {
      control: 'radio',
      options: ['small', 'medium', 'large'],
      description: 'Size variant',
      table: {
        defaultValue: { summary: 'medium' },
      },
    },
    disabled: {
      control: 'boolean',
      description: 'Disable the button',
    },
    loading: {
      control: 'boolean',
      description: 'Show loading state',
    },
    onClick: {
      action: 'clicked',
    },
  },
} satisfies Meta<typeof Button>

export default meta
type Story = StoryObj<typeof meta>

// 1. Default - Most common usage
export const Default: Story = {
  args: {
    children: 'Button',
    variant: 'primary',
  },
}

// 2. All States - Show everything together (PREFERRED over individual state stories)
export const AllStates: Story = {
  name: 'All States',
  render: () => (
    <Box display="flex" flexDirection="column" gap="20">
      <Box display="flex" gap="12">
        <Button variant="primary" size="small">Small</Button>
        <Button variant="primary" size="medium">Medium</Button>
        <Button variant="primary" size="large">Large</Button>
      </Box>
      <Box display="flex" gap="12">
        <Button variant="secondary" size="small">Small</Button>
        <Button variant="secondary" size="medium">Medium</Button>
        <Button variant="secondary" size="large">Large</Button>
      </Box>
      <Box display="flex" gap="12">
        <Button variant="outline" size="small">Small</Button>
        <Button variant="outline" size="medium">Medium</Button>
        <Button variant="outline" size="large">Large</Button>
      </Box>
      <Box display="flex" gap="12">
        <Button variant="ghost" size="small">Small</Button>
        <Button variant="ghost" size="medium">Medium</Button>
        <Button variant="ghost" size="large">Large</Button>
      </Box>
    </Box>
  ),
}

// 3. Example Stories - Use "Ex:" prefix for patterns and use cases
export const ExInteractiveToggle: Story = {
  name: 'Ex: Interactive Toggle',
  render: () => {
    const [isOn, setIsOn] = useState(false)
    return (
      <Button
        variant="primary"
        onClick={() => setIsOn(!isOn)}
        aria-pressed={isOn}
      >
        {isOn ? 'On' : 'Off'}
      </Button>
    )
  },
}

export const ExLoadingSimulation: Story = {
  name: 'Ex: Loading Simulation',
  render: () => {
    const [loading, setLoading] = useState(false)
    const handleClick = () => {
      setLoading(true)
      setTimeout(() => setLoading(false), 2000)
    }
    return (
      <Button variant="primary" loading={loading} onClick={handleClick}>
        {loading ? 'Loading...' : 'Click to Load'}
      </Button>
    )
  },
}

export const ExFormIntegration: Story = {
  name: 'Ex: Form Integration',
  render: () => (
    <form onSubmit={(e) => { e.preventDefault(); alert('Submitted!') }}>
      <Box display="flex" flexDirection="column" gap="12">
        <input type="text" placeholder="Name" />
        <Button type="submit" variant="primary">Submit</Button>
      </Box>
    </form>
  ),
}

// 4. Accessibility Stories - Use "A11y:" prefix for accessibility tests
export const A11yAccessibilityCheck: Story = {
  name: 'A11y: Accessibility Check',
  args: {
    children: 'Accessible Button',
    variant: 'primary',
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement)
    const button = canvas.getByRole('button')

    // Verify accessible name
    expect(button).toHaveAccessibleName('Accessible Button')

    // Test keyboard focus
    button.focus()
    expect(button).toHaveFocus()

    // Test click
    await userEvent.click(button)
  },
  parameters: {
    a11y: {
      config: {
        rules: [
          { id: 'color-contrast', enabled: true },
          { id: 'button-name', enabled: true },
        ],
      },
    },
  },
}

export const A11yKeyboardNavigation: Story = {
  name: 'A11y: Keyboard Navigation',
  render: () => (
    <Box display="flex" gap="12">
      <Button variant="primary">First</Button>
      <Button variant="primary">Second</Button>
      <Button variant="primary">Third</Button>
    </Box>
  ),
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement)

    const firstButton = canvas.getByRole('button', { name: /first/i })
    firstButton.focus()

    await userEvent.tab()

    const secondButton = canvas.getByRole('button', { name: /second/i })
    expect(secondButton).toHaveFocus()
  },
}

Story Organization in Storybook Sidebar:

  • Default
  • All States
  • Ex: Form Integration
  • Ex: Interactive Toggle
  • Ex: Loading Simulation
  • A11y: Accessibility Check
  • A11y: Keyboard Navigation

Skill Usage Summary

When creating stories for a Panda CSS component:

  1. Read component file - Inspect actual props and types before creating stories
  2. Verify Storybook setup - Check installation and configuration
  3. Create story file - Colocate with component: ComponentName.stories.tsx
  4. Define meta - Configure title, component, argTypes (only actual component props)
  5. Create Default story - Show most common usage
  6. Create All States story - Show all variants/states together (preferred over individual stories)
  7. Add example stories - Use "Ex:" prefix for patterns and use cases
  8. Add accessibility stories - Use "A11y:" prefix for accessibility tests with play functions
  9. Test in Storybook - Run npm run storybook and verify all stories
  10. Use TodoWrite - Track systematic coverage throughout

Key Improvements from User Feedback:

  • Only use appropriate props (inspect component first)
  • Prefer "All States" over individual state stories
  • Use "Ex:" prefix for example/pattern stories
  • Use "A11y:" prefix for accessibility testing stories
  • Better sidebar organization and scanning

Stories provide living documentation and enable visual regression testing. They're essential for component libraries and design systems built with Panda CSS.