콘텐츠로 이동

스토리북 치트시트

Storybook - Build UIs in Isolation

스토리북은 UI 컴포넌트와 페이지를 격리된 환경에서 구축하기 위한 프론트엔드 워크샵입니다. 전체 앱을 실행하지 않고도 접근하기 어려운 상태와 엣지 케이스를 개발하고 공유할 수 있도록 도와줍니다. 수천 개의 팀이 UI 개발, 테스트, 문서화를 위해 이를 사용하고 있습니다.

[No text to translate]

목차

The remaining sections (4-20) would need their specific content to be translated. Would you like me to continue with the translations for those sections?```bash

Initialize Storybook in existing project

npx storybook@latest init

Create new project with Storybook

npx create-react-app my-app cd my-app npx storybook@latest init

Start Storybook

npm run storybook


### Manual Installation
```bash
# Install Storybook CLI
npm install -g @storybook/cli

# Initialize in project
sb init

# Or specify framework
sb init --type react
sb init --type vue3
sb init --type angular

Framework-Specific Setup

# React
npx storybook@latest init --type react

# Vue 3
npx storybook@latest init --type vue3

# Angular
npx storybook@latest init --type angular

# Svelte
npx storybook@latest init --type svelte

# Web Components
npx storybook@latest init --type web-components

# HTML
npx storybook@latest init --type html

Project Structure

my-app/
├── .storybook/
│   ├── main.js          # Main configuration
│   └── preview.js       # Global settings
├── src/
│   ├── components/
│   │   ├── Button/
│   │   │   ├── Button.js
│   │   │   ├── Button.css
│   │   │   └── Button.stories.js
│   │   └── Header/
│   │       ├── Header.js
│   │       └── Header.stories.js
│   └── stories/         # Example stories
├── package.json
└── README.md

Getting Started

Basic Story

// src/components/Button/Button.stories.js
import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};

export const Primary = {
  args: {
    primary: true,
    label: 'Button',
  },
};

export const Secondary = {
  args: {
    label: 'Button',
  },
};

export const Large = {
  args: {
    size: 'large',
    label: 'Button',
  },
};

export const Small = {
  args: {
    size: 'small',
    label: 'Button',
  },
};

Component Example

// src/components/Button/Button.js
import React from 'react';
import PropTypes from 'prop-types';
import './button.css';

export const Button = ({ primary = false, backgroundColor = null, size = 'medium', label, ...props }) => {
  const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
  return (
    <button
      type="button"
      className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
      style={{ backgroundColor }}
      {...props}
    >
      {label}
    </button>
  );
};

Button.propTypes = {
  primary: PropTypes.bool,
  backgroundColor: PropTypes.string,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  label: PropTypes.string.isRequired,
  onClick: PropTypes.func,
};

Button.defaultProps = {
  primary: false,
  onClick: undefined,
};

Running Storybook

# Start development server
npm run storybook

# Build static Storybook
npm run build-storybook

# Serve built Storybook
npx http-server storybook-static

# Start with specific port
npm run storybook -- --port 9001

# Start in quiet mode
npm run storybook -- --quiet

Writing Stories

Story Structure

// Component.stories.js
import { Component } from './Component';

// Default export - story metadata
export default {
  title: 'Path/To/Component',
  component: Component,
  parameters: {
    // Story-level parameters
  },
  argTypes: {
    // Control definitions
  },
  args: {
    // Default args for all stories
  },
};

// Named exports - individual stories
export const Default = {};

export const WithProps = {
  args: {
    prop1: 'value1',
    prop2: 'value2',
  },
};

export const CustomStory = {
  render: (args) => <Component {...args} />,
  args: {
    // Story-specific args
  },
};

Story Naming

// Organize stories with hierarchy
export default {
  title: 'Design System/Components/Button',
  // Creates: Design System > Components > Button
};

export default {
  title: 'Pages/Dashboard/UserProfile',
  // Creates: Pages > Dashboard > UserProfile
};

export default {
  title: 'Example/Button',
  // Creates: Example > Button
};

Multiple Components

// stories/Forms.stories.js
import { Input } from '../components/Input';
import { Button } from '../components/Button';
import { Form } from '../components/Form';

export default {
  title: 'Forms/LoginForm',
};

export const LoginForm = () => (
  <Form>
    <Input type="email" placeholder="Email" />
    <Input type="password" placeholder="Password" />
    <Button primary>Login</Button>
  </Form>
);

export const SignupForm = () => (
  <Form>
    <Input placeholder="Full Name" />
    <Input type="email" placeholder="Email" />
    <Input type="password" placeholder="Password" />
    <Input type="password" placeholder="Confirm Password" />
    <Button primary>Sign Up</Button>
  </Form>
);

Story Parameters

export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    // Layout
    layout: 'centered', // 'centered' | 'fullscreen' | 'padded'
    
    // Backgrounds
    backgrounds: {
      default: 'light',
      values: [
        { name: 'light', value: '#ffffff' },
        { name: 'dark', value: '#333333' },
      ],
    },
    
    // Viewport
    viewport: {
      defaultViewport: 'mobile1',
    },
    
    // Documentation
    docs: {
      description: {
        component: 'Button component for user interactions',
      },
    },
  },
};

Story Formats

CSF 3.0 (Current)

// Modern format with object syntax
export default {
  title: 'Example/Button',
  component: Button,
};

export const Primary = {
  args: {
    primary: true,
    label: 'Button',
  },
};

export const Secondary = {
  args: {
    primary: false,
    label: 'Button',
  },
};

CSF 2.0 (Legacy)

// Function-based format
export default {
  title: 'Example/Button',
  component: Button,
};

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

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  primary: false,
  label: 'Button',
};

Custom Render Functions

export const CustomRender = {
  render: (args) => {
    const [count, setCount] = React.useState(0);
    
    return (
      <div>
        <p>Count: {count}</p>
        <Button 
          {...args} 
          onClick={() => setCount(count + 1)}
        />
      </div>
    );
  },
  args: {
    label: 'Increment',
  },
};

Play Functions

import { userEvent, within } from '@storybook/testing-library';

export const InteractiveExample = {
  args: {
    label: 'Click me',
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button');
    
    // Simulate user interaction
    await userEvent.click(button);
    
    // Add assertions if needed
    // expect(button).toHaveClass('clicked');
  },
};

Controls & Actions

ArgTypes Configuration

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    // Text input
    label: {
      control: 'text',
      description: 'Button label text',
    },
    
    // Boolean
    primary: {
      control: 'boolean',
      description: 'Primary button style',
    },
    
    // Select dropdown
    size: {
      control: 'select',
      options: ['small', 'medium', 'large'],
      description: 'Button size',
    },
    
    // Radio buttons
    variant: {
      control: 'radio',
      options: ['primary', 'secondary', 'danger'],
    },
    
    // Color picker
    backgroundColor: {
      control: 'color',
    },
    
    // Number input
    borderRadius: {
      control: { type: 'number', min: 0, max: 50, step: 1 },
    },
    
    // Range slider
    opacity: {
      control: { type: 'range', min: 0, max: 1, step: 0.1 },
    },
    
    // Object editor
    style: {
      control: 'object',
    },
    
    // Array editor
    items: {
      control: 'object',
    },
    
    // Date picker
    createdAt: {
      control: 'date',
    },
    
    // Disable control
    onClick: {
      action: 'clicked',
      control: false,
    },
  },
};

Actions

import { action } from '@storybook/addon-actions';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    onClick: { action: 'clicked' },
    onMouseEnter: { action: 'mouse-enter' },
    onMouseLeave: { action: 'mouse-leave' },
  },
};

// Or in individual stories
export const WithActions = {
  args: {
    label: 'Button',
    onClick: action('button-click'),
    onMouseEnter: action('mouse-enter'),
  },
};

// Multiple actions
export const MultipleActions = {
  args: {
    label: 'Multi Action Button',
    onClick: (...args) => {
      action('primary-click')(...args);
      action('analytics-track')('button-clicked');
    },
  },
};

Advanced Controls

export default {
  title: 'Example/Form',
  component: Form,
  argTypes: {
    // Conditional controls
    showAdvanced: {
      control: 'boolean',
    },
    advancedOptions: {
      control: 'object',
      if: { arg: 'showAdvanced', truthy: true },
    },
    
    // Custom control
    theme: {
      control: {
        type: 'select',
        labels: {
          light: 'Light Theme',
          dark: 'Dark Theme',
          auto: 'Auto (System)',
        },
      },
      options: ['light', 'dark', 'auto'],
    },
    
    // Multi-select
    features: {
      control: 'multi-select',
      options: ['feature1', 'feature2', 'feature3'],
    },
  },
};

애드온

필수 애드온

// .storybook/main.js
module.exports = {
  addons: [
    '@storybook/addon-essentials', // Includes most common addons
    '@storybook/addon-interactions',
    '@storybook/addon-a11y',
    '@storybook/addon-design-tokens',
  ],
};

// Individual addons (if not using essentials)
module.exports = {
  addons: [
    '@storybook/addon-controls',
    '@storybook/addon-actions',
    '@storybook/addon-viewport',
    '@storybook/addon-backgrounds',
    '@storybook/addon-docs',
    '@storybook/addon-toolbars',
  ],
};

접근성 애드온

// .storybook/preview.js
export const parameters = {
  a11y: {
    // Optional selector to inspect
    element: '#storybook-root',
    config: {
      rules: [
        {
          // Disable specific rules
          id: 'color-contrast',
          enabled: false,
        },
      ],
    },
    // Show violations in action panel
    manual: true,
  },
};

// In stories
export const AccessibleButton = {
  args: {
    label: 'Accessible Button',
  },
  parameters: {
    a11y: {
      // Story-specific a11y config
      config: {
        rules: [
          { id: 'button-name', enabled: true },
        ],
      },
    },
  },
};

뷰포트 애드온

// .storybook/preview.js
export const parameters = {
  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',
        },
      },
    },
    defaultViewport: 'mobile',
  },
};

디자인 토큰 애드온

// design-tokens.json
{
  "colors": {
    "primary": "#007bff",
    "secondary": "#6c757d",
    "success": "#28a745",
    "danger": "#dc3545"
  },
  "spacing": {
    "xs": "4px",
    "sm": "8px",
    "md": "16px",
    "lg": "24px",
    "xl": "32px"
  },
  "typography": {
    "fontFamily": {
      "primary": "Inter, sans-serif",
      "mono": "Monaco, monospace"
    },
    "fontSize": {
      "sm": "14px",
      "md": "16px",
      "lg": "18px",
      "xl": "24px"
    }
  }
}

// .storybook/main.js
module.exports = {
  addons: [
    {
      name: '@storybook/addon-design-tokens',
      options: {
        designTokenGlob: '**/design-tokens.json',
      },
    },
  ],
};

커스텀 애드온

// .storybook/addons/theme-switcher/register.js
import { addons, types } from '@storybook/addons';
import { ADDON_ID, TOOL_ID } from './constants';
import { Tool } from './Tool';

addons.register(ADDON_ID, () => {
  addons.add(TOOL_ID, {
    type: types.TOOL,
    title: 'Theme Switcher',
    render: Tool,
  });
});

// .storybook/addons/theme-switcher/Tool.js
import React from 'react';
import { useGlobals } from '@storybook/api';
import { IconButton } from '@storybook/components';

export const Tool = () => {
  const [globals, updateGlobals] = useGlobals();
  const theme = globals.theme || 'light';

  const toggleTheme = () => {
    updateGlobals({
      theme: theme === 'light' ? 'dark' : 'light',
    });
  };

  return (
    <IconButton
      key="theme-switcher"
      title="Toggle theme"
      onClick={toggleTheme}
    >
      {theme === 'light' ? '🌙' : '☀️'}
    </IconButton>
  );
};

구성

기본 구성

// .storybook/main.js
module.exports = {
  // Stories location
  stories: [
    '../src/**/*.stories.@(js|jsx|ts|tsx|mdx)',
    '../stories/**/*.stories.@(js|jsx|ts|tsx|mdx)',
  ],
  
  // Addons
  addons: [
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    '@storybook/addon-a11y',
  ],
  
  // Framework
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
  
  // Features
  features: {
    buildStoriesJson: true,
    storyStoreV7: true,
  },
  
  // TypeScript
  typescript: {
    check: false,
    reactDocgen: 'react-docgen-typescript',
    reactDocgenTypescriptOptions: {
      shouldExtractLiteralValuesFromEnum: true,
      propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
    },
  },
  
  // Webpack customization
  webpackFinal: async (config) => {
    // Add custom webpack config
    config.module.rules.push({
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
    });
    
    return config;
  },
  
  // Vite customization
  viteFinal: async (config) => {
    // Customize Vite config
    config.define = {
      ...config.define,
      global: 'globalThis',
    };
    
    return config;
  },
  
  // Documentation
  docs: {
    autodocs: 'tag',
  },
  
  // Static directories
  staticDirs: ['../public'],
};

미리보기 구성

// .storybook/preview.js
import '../src/index.css'; // Global styles

export const parameters = {
  // Actions
  actions: { argTypesRegex: '^on[A-Z].*' },
  
  // Controls
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
    expanded: true,
    sort: 'requiredFirst',
  },
  
  // Layout
  layout: 'centered',
  
  // Backgrounds
  backgrounds: {
    default: 'light',
    values: [
      { name: 'light', value: '#ffffff' },
      { name: 'dark', value: '#333333' },
      { name: 'gray', value: '#f8f9fa' },
    ],
  },
  
  // Viewport
  viewport: {
    viewports: {
      mobile1: {
        name: 'Small mobile',
        styles: { width: '320px', height: '568px' },
      },
      mobile2: {
        name: 'Large mobile',
        styles: { width: '414px', height: '896px' },
      },
      tablet: {
        name: 'Tablet',
        styles: { width: '768px', height: '1024px' },
      },
    },
  },
  
  // Documentation
  docs: {
    theme: themes.light,
    source: {
      state: 'open',
    },
  },
};

// Global decorators
export const decorators = [
  (Story) => (
    <div style={{ margin: '3em', fontFamily: 'Inter, sans-serif' }}>
      <Story />
    </div>
  ),
];

// Global args
export const args = {
  // Default args for all stories
};

// Global arg types
export const argTypes = {
  // Global arg types
};

환경 변수

// .storybook/main.js
module.exports = {
  env: (config) => ({
    ...config,
    API_URL: 'https://api.example.com',
    FEATURE_FLAG: true,
  }),
  
  webpackFinal: async (config) => {
    config.plugins.push(
      new webpack.DefinePlugin({
        'process.env.STORYBOOK': JSON.stringify(true),
      })
    );
    return config;
  },
};

// In components
const apiUrl = process.env.STORYBOOK 
  ? 'https://api-mock.example.com' 
  : process.env.API_URL;

테마

커스텀 테마

// .storybook/theme.js
import { create } from '@storybook/theming';

export default create({
  base: 'light', // 'light' | 'dark'
  
  // Brand
  brandTitle: 'My Company Storybook',
  brandUrl: 'https://example.com',
  brandImage: 'https://example.com/logo.svg',
  brandTarget: '_self',
  
  // Colors
  colorPrimary: '#FF4785',
  colorSecondary: '#029CFD',
  
  // UI
  appBg: '#F6F9FC',
  appContentBg: '#FFFFFF',
  appBorderColor: '#E6ECF0',
  appBorderRadius: 4,
  
  // Typography
  fontBase: '"Inter", sans-serif',
  fontCode: 'Monaco, monospace',
  
  // Text colors
  textColor: '#2E3438',
  textInverseColor: '#FFFFFF',
  textMutedColor: '#798186',
  
  // Toolbar
  barTextColor: '#798186',
  barSelectedColor: '#029CFD',
  barBg: '#FFFFFF',
  
  // Form
  inputBg: '#FFFFFF',
  inputBorder: '#E6ECF0',
  inputTextColor: '#2E3438',
  inputBorderRadius: 4,
});

// .storybook/manager.js
import { addons } from '@storybook/addons';
import theme from './theme';

addons.setConfig({
  theme,
});

다크 테마

// .storybook/dark-theme.js
import { create } from '@storybook/theming';

export default create({
  base: 'dark',
  
  brandTitle: 'My Company Storybook',
  brandUrl: 'https://example.com',
  brandImage: 'https://example.com/logo-white.svg',
  
  colorPrimary: '#FF4785',
  colorSecondary: '#029CFD',
  
  appBg: '#1A1A1A',
  appContentBg: '#2D2D2D',
  appBorderColor: '#404040',
  
  textColor: '#FFFFFF',
  textInverseColor: '#1A1A1A',
  textMutedColor: '#CCCCCC',
  
  barTextColor: '#CCCCCC',
  barSelectedColor: '#029CFD',
  barBg: '#2D2D2D',
  
  inputBg: '#404040',
  inputBorder: '#666666',
  inputTextColor: '#FFFFFF',
});

테마 전환

// .storybook/preview.js
import { themes } from '@storybook/theming';

export const parameters = {
  docs: {
    theme: themes.light,
  },
  darkMode: {
    // Override the default dark theme
    dark: { ...themes.dark, appBg: 'black' },
    // Override the default light theme
    light: { ...themes.normal, appBg: 'white' },
    // Set the initial theme
    current: 'light',
    // Disable the addon for specific stories
    stylePreview: true,
  },
};

// Global decorator for theme switching
export const decorators = [
  (Story, context) => {
    const theme = context.globals.theme || 'light';
    
    return (
      <div className={`theme-${theme}`}>
        <Story />
      </div>
    );
  },
];

// Global types for theme switcher
export const globalTypes = {
  theme: {
    name: 'Theme',
    description: 'Global theme for components',
    defaultValue: 'light',
    toolbar: {
      icon: 'circlehollow',
      items: [
        { value: 'light', icon: 'sun', title: 'Light' },
        { value: 'dark', icon: 'moon', title: 'Dark' },
      ],
      showName: true,
    },
  },
};

테스팅

시각적 테스팅

// .storybook/test-runner.js
const { getStoryContext } = require('@storybook/test-runner');

module.exports = {
  async postRender(page, context) {
    const storyContext = await getStoryContext(page, context);
    
    // Skip visual tests for specific stories
    if (storyContext.parameters?.skipVisualTest) {
      return;
    }
    
    // Take screenshot
    const image = await page.screenshot();
    expect(image).toMatchImageSnapshot({
      customSnapshotIdentifier: context.id,
    });
  },
};

// In stories
export const VisualTest = {
  args: {
    label: 'Visual Test Button',
  },
  parameters: {
    // Skip this story in visual tests
    skipVisualTest: true,
  },
};

상호작용 테스팅

// Button.stories.js
import { userEvent, within, expect } from '@storybook/test';

export const InteractionTest = {
  args: {
    label: 'Click me',
  },
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);
    
    // Find elements
    const button = canvas.getByRole('button', { name: /click me/i });
    
    // Simulate interactions
    await userEvent.click(button);
    
    // Assertions
    await expect(args.onClick).toHaveBeenCalled();
    
    // Check DOM changes
    await expect(button).toHaveClass('clicked');
    
    // Wait for async operations
    await canvas.findByText('Success!');
  },
};

export const FormInteraction = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Fill form
    const emailInput = canvas.getByLabelText(/email/i);
    const passwordInput = canvas.getByLabelText(/password/i);
    const submitButton = canvas.getByRole('button', { name: /submit/i });
    
    await userEvent.type(emailInput, 'user@example.com');
    await userEvent.type(passwordInput, 'password123');
    await userEvent.click(submitButton);
    
    // Check results
    await expect(canvas.getByText(/success/i)).toBeInTheDocument();
  },
};

스토리를 사용한 단위 테스팅

// Button.test.js
import { render, screen } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import * as stories from './Button.stories';

const { Primary, Secondary, Large } = composeStories(stories);

describe('Button', () => {
  test('renders primary button', () => {
    render(<Primary />);
    expect(screen.getByRole('button')).toHaveClass('storybook-button--primary');
  });

  test('renders secondary button', () => {
    render(<Secondary />);
    expect(screen.getByRole('button')).not.toHaveClass('storybook-button--primary');
  });

  test('renders large button', () => {
    render(<Large />);
    expect(screen.getByRole('button')).toHaveClass('storybook-button--large');
  });
});

접근성 테스팅

// a11y.test.js
import { axe, toHaveNoViolations } from 'jest-axe';
import { render } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import * as stories from './Button.stories';

expect.extend(toHaveNoViolations);

const { Primary, Secondary } = composeStories(stories);

describe('Button Accessibility', () => {
  test('Primary button should not have accessibility violations', async () => {
    const { container } = render(<Primary />);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });

  test('Secondary button should not have accessibility violations', async () => {
    const { container } = render(<Secondary />);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });
});

문서화

자동 문서화

// Component with JSDoc
/**
 * Primary UI component for user interaction
 */
export const Button = ({
  /**
   * Is this the principal call to action on the page?
   */
  primary = false,
  /**
   * What background color to use
   */
  backgroundColor,
  /**
   * How large should the button be?
   */
  size = 'medium',
  /**
   * Button contents
   */
  label,
  /**
   * Optional click handler
   */
  onClick,
  ...props
}) => {
  // Component implementation
};

// Stories with auto-generated docs
export default {
  title: 'Example/Button',
  component: Button,
  tags: ['autodocs'], // Enable auto-documentation
  parameters: {
    docs: {
      description: {
        component: 'Button component for user interactions. Supports multiple sizes and variants.',
      },
    },
  },
};

커스텀 문서화

// Button.stories.js
export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    docs: {
      description: {
        component: `
# Button Component

The Button component is a fundamental UI element used throughout the application.

## Usage

\`\`\`jsx
import { Button } from './Button';

<Button primary onClick={handleClick}>
  Click me
</Button>
\`\`\`

## Design Guidelines

- Use primary buttons for main actions
- Use secondary buttons for supporting actions
- Ensure sufficient color contrast for accessibility
        `,
      },
    },
  },
};

export const Primary = {
  args: {
    primary: true,
    label: 'Button',
  },
  parameters: {
    docs: {
      description: {
        story: 'Primary buttons are used for the main call-to-action on a page.',
      },
    },
  },
};

MDX 문서화

<!-- Button.stories.mdx -->
import { Meta, Story, Canvas, ArgsTable, Description } from '@storybook/addon-docs';
import { Button } from './Button';

<Meta title="Example/Button" component={Button} />

# Button

<Description of={Button} />

## Examples

### Primary Button

<Canvas>
  <Story name="Primary" args={{ primary: true, label: 'Button' }}>
    {(args) => <Button {...args} />}
  </Story>
</Canvas>

### Secondary Button

<Canvas>
  <Story name="Secondary" args={{ label: 'Button' }}>
    {(args) => <Button {...args} />}
  </Story>
</Canvas>

## Props

<ArgsTable of={Button} />

## Usage Guidelines

- Use primary buttons sparingly, typically one per page
- Secondary buttons can be used multiple times
- Always provide meaningful labels
- Consider accessibility when choosing colors

## Code Example

```jsx
import { Button } from './Button';

function MyComponent() {
  return (
    <div>
      <Button primary onClick={() => alert('기본 버튼 클릭!')}>
        기본 작업
      </Button>
      <Button onClick={() => alert('보조 버튼 클릭!')}>
        보조 작업
      </Button>
    </div>
  );
}

### 문서화 페이지

Would you like me to fill in the empty sections or provide more details about the translation?```javascript
// .storybook/main.js
module.exports = {
  stories: [
    '../src/**/*.stories.@(js|jsx|ts|tsx)',
    '../docs/**/*.stories.mdx', // Documentation stories
  ],
};

// docs/Introduction.stories.mdx
import { Meta } from '@storybook/addon-docs';

<Meta title="Introduction" />

# Design System

Welcome to our design system documentation.

## Getting Started

This Storybook contains all the components, patterns, and guidelines for our design system.

### Installation

```bash
npm install @company/design-system

Usage

import { Button, Input, Card } from '@company/design-system';

Principles

  1. Consistency - Maintain visual and functional consistency
  2. Accessibility - Ensure all components are accessible
  3. Performance - Optimize for speed and efficiency
  4. Flexibility - Support customization and theming
```bash
# Build Storybook for deployment
npm run build-storybook

# Output directory
ls storybook-static/

# Serve locally
npx http-server storybook-static
```### 정적 빌드
```bash
# netlify.toml
[build]
  command = "npm run build-storybook"
  publish = "storybook-static"

[build.environment]
  NODE_VERSION = "16"

# Deploy
npm install -g netlify-cli
netlify deploy --prod --dir=storybook-static
```### Netlify 배포
```json
// vercel.json
{
  "buildCommand": "npm run build-storybook",
  "outputDirectory": "storybook-static",
  "framework": null
}
```### Vercel 배포
```yaml
# .github/workflows/storybook.yml
name: Build and Deploy Storybook

on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout
      uses: actions/checkout@v3
      
    - name: Setup Node
      uses: actions/setup-node@v3
      with:
        node-version: '16'
        cache: 'npm'
        
    - name: Install dependencies
      run: npm ci
      
    - name: Build Storybook
      run: npm run build-storybook
      
    - name: Deploy to GitHub Pages
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./storybook-static
```### GitHub Pages
```dockerfile
# Dockerfile
FROM node:16-alpine as builder

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build-storybook

FROM nginx:alpine
COPY --from=builder /app/storybook-static /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```### Docker 배포
```bash
# Build and deploy to S3
npm run build-storybook
aws s3 sync storybook-static/ s3://my-storybook-bucket --delete
aws cloudfront create-invalidation --distribution-id ABCD1234 --paths "/*"
```### AWS S3 + CloudFront
```javascript
// .storybook/main.js
const path = require('path');

module.exports = {
  webpackFinal: async (config) => {
    // Add alias
    config.resolve.alias = {
      ...config.resolve.alias,
      '@': path.resolve(__dirname, '../src'),
      '@components': path.resolve(__dirname, '../src/components'),
    };
    
    // Add custom loader
    config.module.rules.push({
      test: /\.scss$/,
      use: [
        'style-loader',
        'css-loader',
        {
          loader: 'sass-loader',
          options: {
            additionalData: `@import "@/styles/variables.scss";`,
          },
        },
      ],
    });
    
    // Add plugin
    config.plugins.push(
      new webpack.DefinePlugin({
        __VERSION__: JSON.stringify(process.env.npm_package_version),
      })
    );
    
    return config;
  },
};
```## 고급 기능
```javascript
// .storybook/main.js
module.exports = {
  babel: async (options) => ({
    ...options,
    plugins: [
      ...options.plugins,
      ['babel-plugin-styled-components', { displayName: true }],
    ],
  }),
};
```### 사용자 정의 Webpack 구성
```javascript
// .storybook/main.js
const isDevelopment = process.env.NODE_ENV === 'development';

module.exports = {
  addons: [
    '@storybook/addon-essentials',
    ...(isDevelopment ? ['@storybook/addon-a11y'] : []),
  ],
  
  features: {
    storyStoreV7: !isDevelopment, // Disable in development for faster builds
  },
  
  webpackFinal: async (config) => {
    if (isDevelopment) {
      // Development-specific webpack config
      config.optimization.minimize = false;
    }
    
    return config;
  },
};
```### 사용자 정의 Babel 구성
```javascript
// .storybook/manager.js
import { addons } from '@storybook/addons';

addons.setConfig({
  // Show/hide panels
  showPanel: true,
  panelPosition: 'bottom', // 'bottom' | 'right'
  
  // Sidebar
  showNav: true,
  showToolbar: true,
  
  // Initial active panel
  selectedPanel: 'storybook/controls/panel',
  
  // Sidebar tree expansion
  initialActive: 'sidebar',
  sidebar: {
    showRoots: false,
    collapsedRoots: ['other'],
  },
  
  // Toolbar
  toolbar: {
    title: { hidden: false },
    zoom: { hidden: false },
    eject: { hidden: false },
    copy: { hidden: false },
    fullscreen: { hidden: false },
  },
});
```### 환경별 구성
```javascript
// .storybook/main.js
module.exports = {
  features: {
    storyStoreV7: true, // Enable lazy loading
  },
  
  stories: [
    {
      directory: '../src/components',
      files: '**/*.stories.*',
      titlePrefix: 'Components',
    },
    {
      directory: '../src/pages',
      files: '**/*.stories.*',
      titlePrefix: 'Pages',
    },
  ],
};
```### 사용자 정의 관리자 UI
```javascript
// .storybook/main.js
module.exports = {
  webpackFinal: async (config) => {
    // Code splitting
    config.optimization.splitChunks = {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    };
    
    // Tree shaking
    config.optimization.usedExports = true;
    config.optimization.sideEffects = false;
    
    return config;
  },
};
```## 성능
```javascript
// .storybook/preview.js
export const parameters = {
  // Reduce memory usage
  options: {
    storySort: {
      method: 'alphabetical',
      order: ['Introduction', 'Components', 'Pages'],
      locales: 'en-US',
    },
  },
  
  // Disable source code addon for better performance
  docs: {
    source: {
      state: 'closed',
    },
  },
};

// Cleanup decorators
export const decorators = [
  (Story, context) => {
    // Cleanup on story change
    React.useEffect(() => {
      return () => {
        // Cleanup logic
      };
    }, [context.id]);
    
    return <Story />;
  },
];
```### 지연 로딩
```javascript
// Figma integration
export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    design: {
      type: 'figma',
      url: 'https://www.figma.com/file/ABC123/Design-System?node-id=123%3A456',
    },
  },
};

// Sketch integration
export const SketchExample = {
  parameters: {
    design: {
      type: 'sketch',
      url: 'https://sketch.cloud/s/abc123',
    },
  },
};

// Adobe XD integration
export const XDExample = {
  parameters: {
    design: {
      type: 'adobe-xd',
      url: 'https://xd.adobe.com/view/abc123',
    },
  },
};
```### 번들 최적화
```javascript
// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
  testPathIgnorePatterns: ['/node_modules/', '/storybook-static/'],
  
  // Test stories
  testMatch: [
    '<rootDir>/src/**/__tests__/**/*.(js|jsx|ts|tsx)',
    '<rootDir>/src/**/?(*.)(spec|test).(js|jsx|ts|tsx)',
    '<rootDir>/src/**/*.stories.test.(js|jsx|ts|tsx)',
  ],
};

// Chromatic integration
// .github/workflows/chromatic.yml
name: 'Chromatic'
on: push

jobs:
  chromatic-deployment:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - run: yarn
      - uses: chromaui/action@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
```### 메모리 최적화
```yaml
# .github/workflows/storybook-tests.yml
name: Storybook Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '16'
          cache: 'npm'
      
      - run: npm ci
      - run: npm run build-storybook --quiet
      - run: npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
          "npx http-server storybook-static --port 6006 --silent" \
          "npx wait-on http://127.0.0.1:6006 && npm run test-storybook"
```## 통합
```bash
# Port already in use
npm run storybook -- --port 9001

# Clear cache
rm -rf node_modules/.cache/storybook

# Webpack issues
npm run storybook -- --no-manager-cache

# TypeScript issues
npm run storybook -- --no-dll

# Memory issues
NODE_OPTIONS="--max-old-space-size=4096" npm run storybook
```### 디자인 도구 통합
```bash
# Enable debug logging
DEBUG=storybook:* npm run storybook

# Webpack debug
npm run storybook -- --debug-webpack

# Verbose output
npm run storybook -- --verbose

성능 문제

// .storybook/main.js
module.exports = {
  // Disable source maps in development
  webpackFinal: async (config) => {
    if (process.env.NODE_ENV === 'development') {
      config.devtool = false;
    }
    return config;
  },
  
  // Reduce bundle size
  features: {
    storyStoreV7: true,
    buildStoriesJson: true,
  },
};

빌드 문제

# Clear all caches
rm -rf node_modules/.cache
rm -rf storybook-static
npm run build-storybook

# Check for conflicting dependencies
npm ls

# Update Storybook
npx storybook@latest upgrade

모범 사례

스토리 구성

  • 계층적 구조: 일관된 이름 지정 패턴 사용
  • 논리적 그룹화: 관련 컴포넌트를 함께 그룹화
  • 명확한 이름 지정: 설명적인 스토리 이름 사용
  • 문서화: 포괄적인 문서 포함

컴포넌트 개발

  • 격리: 컴포넌트를 격리하여 개발
  • Props 인터페이스: 명확하고 일관된 prop 인터페이스 설계
  • 기본값: 합리적인 기본값 제공
  • 오류 처리: 엣지 케이스를 우아하게 처리

테스트 전략

  • 시각적 테스트: 시각적 회귀 테스트 사용
  • 상호작용 테스트: 사용자 상호작용 테스트
  • 접근성 테스트: 컴포넌트의 접근성 보장
  • 단위 테스트: 컴포넌트 로직 테스트

성능 최적화

  • 지연 로딩: 스토리 스토어 v7을 사용한 지연 로딩
  • 번들 분할: webpack 구성 최적화
  • 메모리 관리: 리소스 적절히 정리
  • 빌드 최적화: 빌드 시간 최소화

팀 협업

  • 디자인 시스템: 일관된 디자인 시스템 유지
  • 문서화: 문서를 최신 상태로 유지
  • 코드 리뷰: 스토리와 컴포넌트 검토
  • 자동화: 테스트 및 배포 자동화

요약

Storybook은 다음을 가능하게 하는 현대 프론트엔드 개발의 필수 도구입니다:

  • 컴포넌트 격리: UI 컴포넌트를 독립적으로 개발 및 테스트
  • 시각적 문서화: 디자인 시스템을 위한 라이브 문서 생성
  • 대화형 개발: 실시간 피드백으로 컴포넌트 빌드
  • 테스트 통합: 시각적, 상호작용, 접근성 테스트
  • 팀 협업: 팀 간 컴포넌트 공유
  • 디자인 시스템 관리: 일관된 UI 패턴 유지

주요 이점으로는 더 빠른 개발 주기, 더 나은 컴포넌트 품질, 향상된 팀 협업, 포괄적인 문서화가 있습니다. Storybook은 모든 주요 프론트엔드 프레임워크를 지원하고 애드온 및 구성을 통해 광범위한 맞춤 설정 옵션을 제공합니다.