StoryBook๐Ÿ“– ์ดˆ๊ธฐ ์„ธํŒ…๊ณผ ์Šคํ† ๋ฆฌ ์ž‘์„ฑ, ํฌ๋กœ๋งˆํ‹ฑ ๋ฐฐํฌ ์ž๋™ํ™”๊นŒ์ง€..

ใ…Žใ…‡ยท2025๋…„ 1์›” 7์ผ
2

ํ”„๋กœ์ ํŠธ

๋ชฉ๋ก ๋ณด๊ธฐ
2/2

Confeti ์„œ๋น„์Šค์˜ ๋””์ž์ธ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๋Š”๋ฐ ์Šคํ† ๋ฆฌ๋ถ์„ ์‚ฌ์šฉํ•  ์˜ˆ์ •์ด๋ผ, ์Šคํ† ๋ฆฌ๋ถ์ด ๋ฌด์—‡์ธ์ง€์™€ ๋„์ž… ๊ณผ์ •์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ StoryBook์ด๋ž€?

์Šคํ† ๋ฆฌ๋ถ์€ UI ์š”์†Œ์™€ ํŽ˜์ด์ง€๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋ก ํŠธ์—”๋“œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค. UI ๊ฐœ๋ฐœ, ํ…Œ์ŠคํŠธ, ๋ฌธ์„œํ™”์— ์‚ฌ์šฉํ•œ๋‹ค.

  • UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋…๋ฆฝ์ ์ธ ํ™˜๊ฒฝ์—์„œ ๊ทธ๋ ค๋ณผ ์ˆ˜ ์žˆ๋Š” ํˆด, ์ฆ‰ ํ•œ๋งˆ๋””๋กœ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์ •๋ฆฌ๋˜์–ด ์žˆ๋Š” ๋ฌธ์„œ

Microsoft Fluent UI React Storybook ์˜ˆ์‹œ

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๋‹ค๋ฅธ ๋ณ„๋„์˜ ๊ฐœ๋ฐœ ์„œ๋ฒ„๋กœ ์‹คํ–‰๋œ๋‹ค.(๋…๋ฆฝ์ ์ธ ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์คŒ)
  • ์Šคํ† ๋ฆฌ๋ถ ํ™˜๊ฒฝ์—์„œ ์ปดํฌ๋„ŒํŠธ์˜ ๋‹ค์–‘ํ•œ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค. (์‹œ๊ฐ์ ์œผ๋กœ ๋ณด๋ฉด์„œ ๊ฐœ๋ฐœ ๊ฐ€๋Šฅ)

๋“ค์–ด๊ฐ€๊ธฐ ์•ž์„œ..

๐Ÿ’ก Story๋ž€?
์Šคํ† ๋ฆฌ๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ๋‹ค์–‘ํ•œ ์ƒํƒœ์™€ ๋ณ€ํ˜•์„ ์‹œ๊ฐ์ ์œผ๋กœ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ์ž‘์„ฑ๋œ ์˜ˆ์ œ๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ฐ ์ปดํฌ๋„ŒํŠธ๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ ์Šคํ† ๋ฆฌ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ฐ ์Šคํ† ๋ฆฌ๋Š” ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์˜ ํŠน์ • ์ƒํƒœ์˜ ์™ธ๊ด€๊ณผ ๋™์ž‘์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.

๐Ÿ’ก Addon์ด๋ž€?
Storybook์˜ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•ด์ฃผ๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋งํ•œ๋‹ค. ์Šคํ† ๋ฆฌ๋ถ์—์„œ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•ด์ฃผ๋Š” addon์ด ์žˆ์ง€๋งŒ, ํ•„์š”์‹œ ์ถ”๊ฐ€๋กœ ์„ค์น˜ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.
๋ฌธ์„œํ™”, ์ ‘๊ทผ์„ฑ ํ…Œ์ŠคํŠธ, ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์ปจํŠธ๋กค ๋“ฑ ๋Œ€๋ถ€๋ถ„์˜ Storybook ๊ธฐ๋Šฅ์€ ์• ๋“œ์˜จ์œผ๋กœ ๊ตฌํ˜„๋œ๋‹ค๊ณ  ํ•œ๋‹ค.
์„ค์น˜ ์‹œ ์ž๋™์œผ๋กœ ์„ค์น˜๋˜๋Š” ํ•„์ˆ˜ ์• ๋“œ์˜จ


๐ŸŽธ Button ์ปดํฌ๋„ŒํŠธ๋กœ ์•Œ์•„๋ณด๋Š” ์Šคํ† ๋ฆฌ๋ถ ์ž‘์„ฑ ๋ฐฉ๋ฒ•

1๏ธโƒฃ ๋จผ์ € ์Šคํ† ๋ฆฌ๋ถ ์„ค์น˜

pnpm dlx storybook@next init

pnpm run storybook
  • ์‹คํ–‰ ํ™”๋ฉด
  • ๋‹ค์Œ๊ณผ ๊ฐ™์ด .storybook ํด๋”์™€ ํŒŒ์ผ๋“ค์ด ๊ธฐ๋ณธ ์ƒ์„ฑ -> storybook์˜ ์„ค์ • ํŒŒ์ผ๋“ค์„ ๋ชจ์•„๋‘๋Š” ํด๋”

main.tsx

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

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  addons: [
    '@storybook/addon-onboarding',
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@chromatic-com/storybook',
    '@storybook/addon-interactions',
  ],
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
};
export default config;
  • stories๋ฅผ ์œ„ํ•œ config ์„ค์ •, ๊ธฐ๋ณธ์œผ๋กœ ์„ค์ •๋˜๋Š” stories์™€ addons์˜ ์„ธํŒ…์ด ํฌํ•จ๋œ ํŒŒ์ผ
  • ์„ค์น˜ํ•˜๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ํ•„์ˆ˜ addon์ด ์„ค์น˜๋˜์–ด์žˆ๋Š”๋ฐ, ํ•„์š”ํ•œ addon์„ ์ถ”๊ฐ€๋กœ ์„ค์น˜ํ•ด์ค€๋‹ค.
    ๋‚˜๋Š” ์ ‘๊ทผ์„ฑ addon (@storybook/addon-a11y)์„ ์ถ”๊ฐ€ํ•ด์คฌ๋‹ค.

preview.tsx

import type { Preview } from '@storybook/react';

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
};

export default preview;
  • ์Šคํ† ๋ฆฌ ๋ Œ๋”๋ง์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ๊ธ€๋กœ๋ฒŒ ์„ค์ •๊ณผ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ •์˜ํ•˜๋Š” ํŒŒ์ผ

์ด์™ธ์—๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—ฌ๋Ÿฌ ์„ค์ • ํŒŒ์ผ๋“ค์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • manager.ts: Storybook์˜ UI(์Šคํ† ๋ฆฌ ๋ชฉ๋ก, ์• ๋“œ์˜จ ํŒจ๋„ ๋“ฑ)๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ๋Š” ์„ค์ • ํŒŒ์ผ
  • preview-head.html: ์™ธ๋ถ€ ํฐํŠธ๋‚˜ ์Šคํƒ€์ผ์‹œํŠธ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋„๋ก, <head> ํƒœ๊ทธ์— ์ถ”๊ฐ€ํ•  HTML์„ ์ •์˜ํ•˜๋Š” ํŒŒ์ผ

2๏ธโƒฃ vanila-extract ๊ด€๋ จ ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์น˜

vanila-extract๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๊ด€๋ จ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์„ค์น˜ํ•ด์ค˜์„œ vite๊ฐ€ vanila-extract๋ฅผ ํ•ด์„ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

โ—๏ธ๋ชจ๋…ธ๋ ˆํฌ์—์„œ ์ข…์†์„ฑ์„ ์„ค์น˜ํ•  ๋•Œ, ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ์„ค์น˜ํ•˜์ง€ ์•Š๊ณ  ๊ฐ ํŒจํ‚ค์ง€ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ๊ฐœ๋ณ„์ ์œผ๋กœ ์„ค์น˜ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์  ์œ ์˜!

pnpm add -D @vanilla-extract/vite-plugin

ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์น˜ ํ›„ ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ main.ts์˜ config ๊ฐ์ฒด ์•ˆ์— ์ถ”๊ฐ€ํ•ด์คฌ๋‹ค.


import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';

async viteFinal(config) {
    config.plugins = config.plugins || [];
    config.plugins.push(
      vanillaExtractPlugin({
        identifiers: ({ hash }) => `_${hash}`,
      }),
    );
    return config;
  },

3๏ธโƒฃ global style, theme ์ถ”๊ฐ€

decorator๋ฅผ ํ™œ์šฉํ•˜์—ฌ global style์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
์Šคํ† ๋ฆฌ๋ถ์—์„œ๋Š” ํ”„๋ฆฌ๋ทฐ ์„ค์ • ํŒŒ์ผ.storybook/preview.js์„ ์‚ฌ์šฉํ•ด์„œ ๋ชจ๋“  ์Šคํ† ๋ฆฌ๋ฅผ ๊ทธ ์ปดํฌ๋„ŒํŠธ ์•ˆ์— ๋„ฃ๊ณ  ๊ฐ์‹ธ์ฃผ๋ฉด๋œ๋‹ค.

const preview: Preview = {
    decorators: [
      (Story) => (
        <div className={themeClass}>
          <Story />
        </div>
      ),
    ],
  },
};

๋‹ค์Œ๊ณผ ๊ฐ™์ด preview ๊ฐ์ฒด ๋‚ด์—์„œ story๋ฅผ decorator๋กœ ๊ฐ์‹ธ์ฃผ๋ฉด, ๋ชจ๋“  story์— ๊ธ€๋กœ๋ฒŒ ์Šคํƒ€์ผ์ด ์ ์šฉ๋œ๋‹ค.
์ด๋•Œ jsx ๊ตฌ๋ฌธ ๋ถ€๋ถ„์ด ๋ฆฐํŠธ์—๋Ÿฌ๊ฐ€ ์ซ™ ๋‚ฌ๋Š”๋ฐ, preview.tsx๋กœ ํ™•์žฅ์ž ๋ณ€๊ฒฝ ํ›„ import React from 'react'; ํ•ด์ฃผ๋ฉด ํ•ด๊ฒฐ๋œ๋‹ค.


4๏ธโƒฃ ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„ํ•˜๊ธฐ

// Button.tsx
import { ButtonHTMLAttributes } from 'react';
import { buttonStyle } from './styles.css';

export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'tertiary' | 'outline';
  size?: 'xLarge' | 'large' | 'medium';
  isDisabled?: boolean;
}

const Button = ({
  variant = 'primary',
  children,
  size = 'medium',
  isDisabled = false,
  ...props
}: ButtonProps) => {
  return (
    <button
      type="button"
      className={buttonStyle({ color: variant, size: size })}
      disabled={isDisabled}
      {...props}
    >
      {children}
    </button>
  );
};

export default Button;

์œ„์˜ Button ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” variant, size, isDiabled ์˜ 3๊ฐ€์ง€ prop๊ณผ children ์š”์†Œ์— ๋”ฐ๋ผ์„œ ์™ธํ˜•์ด ๋ฐ”๋€๋‹ค.
โžก๏ธ ๋”ฐ๋ผ์„œ ์Šคํ† ๋ฆฌ๋ถ์—์„œ๋Š” ์ด 4๊ฐœ์˜ ์š”์†Œ๊ฐ€ ๋ฐ”๋€Œ์—ˆ์„ ๋•Œ ๊ฐ๊ฐ€ ์–ด๋–ค ๋ชจ์Šต์ธ์ง€ ํ™•์ธํ•˜๋Š” ์Šคํ† ๋ฆฌ๋ฅผ ๋งŒ๋“ค๋ฉด ๋œ๋‹ค!


5๏ธโƒฃ story ํŒŒ์ผ ์ถ”๊ฐ€

๋””์ž์ธ ์‹œ์Šคํ…œ ๋‚ด์˜ ํด๋” ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

|-- src
	|-- components
    	|-- Button
		  |-- Button.tsx
          |-- Button.stories.tsx
		  |-- Button.css.ts
		
  • storyํ™•์žฅ์ž๋ฅผ ์ปดํฌ๋„ŒํŠธ๋ช….stories.ts ๋กœ ํ•ด์ฃผ์–ด์•ผ ์Šคํ† ๋ฆฌ๋กœ ์ธ์‹ (๊ตณ์ด ์Šคํ† ๋ฆฌ ํŒŒ์ผ์ด stories ํด๋” ์•ˆ์— ์œ„์น˜ํ•˜์ง€ ์•Š์•„๋„ ๋จ)

meta

์Šคํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑ์„ ๋„์™€์ฃผ๋Š” ์ปดํฌ๋„ŒํŠธ. ์Šคํ† ๋ฆฌ๋ฅผ ๊ทธ๋ฆฌ๋Š” ์—ญํ• ์„ ํ•˜์ง€๋Š” ์•Š์•„์„œ meta๋กœ ์˜ต์…˜๋“ค์„ ์„ค์ •ํ•˜๊ณ  story ์ƒ์„ฑ์„ ๋”ฐ๋กœ ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

  • title : storybook์˜ ํด๋” ๊ฒฝ๋กœ ๋ฐ ํŒŒ์ผ ์ด๋ฆ„ ์ง€์ •
  • component : ํ•ด๋‹น ์Šคํ† ๋ฆฌ์— ์—ฐ๊ฒฐ๋  component ์ง€์ •
  • tags : autodocs๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ์— ์ž‘์„ฑํ•œ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑ
  • parameters : ์Šคํ† ๋ฆฌ ์ปดํฌ๋„ŒํŠธ์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ์„ค์ •
    • layout : ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์—์„œ ๋ณด์—ฌ์งˆ ์œ„์น˜ ์„ค์ •
      • centered : ํ™”๋ฉด์˜ ์ค‘์•™์— ์ •๋ ฌ
      • fullscreen : ์ „์ฒดํ™”๋ฉด์œผ๋กœ ๋ Œ๋”๋ง
      • padded : ์ปดํฌ๋„ŒํŠธ ์ฃผ์œ„์— ํŒจ๋”ฉ ์ถ”๊ฐ€
  • args : ํ•ด๋‹น ํŒŒ์ผ์—์„œ ์ƒ์„ฑ๋˜๋Š” story์— ๊ณตํ†ต์œผ๋กœ ์ ์šฉํ•  props || ๊ธฐ๋ณธ ๊ฐ’ ๋ช…์‹œ
  • argTypes : ์ปดํฌ๋„ŒํŠธ props์˜ type์„ ๋ช…์‹œ
    • options : ํ•ด๋‹น props์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’๋“ค ์ง€์ •
    • control : ์‚ฌ์šฉ์ž๊ฐ€ storybook์—์„œ props๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” UI ์ปจํŠธ๋กค ์ œ๊ณต
      • type ๋ชฉ๋ก
        • text : ํ…์ŠคํŠธ ์ž…๋ ฅ ๊ฐ€๋Šฅ
        • number : ์ˆซ์ž ์ž…๋ ฅ ๊ฐ€๋Šฅ
        • color : ์ƒ‰์ƒ ์„ ํƒ ๊ฐ€๋Šฅ
        • radio : options ๊ฐ’๋“ค ์„ ํƒ ๊ฐ€๋Šฅํ•œ ๋ผ๋””์˜ค ๋ฒ„ํŠผ
        • select : options ๊ฐ’๋“ค ์„ ํƒ ๊ฐ€๋Šฅํ•œ ๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด
        • multi-select : ์—ฌ๋Ÿฌ ์˜ต์…˜ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์…€๋ ‰ํŠธ ๋ฐ•์Šค
        • boolean : true / false ์„ ํƒ ๊ฐ€๋Šฅ
        • date : ๋‚ ์งœ ์„ ํƒ๊ธฐ
        • range : ์Šฌ๋ผ์ด๋”๋ฅผ ์‚ฌ์šฉํ•œ ์ˆซ์ž ์ž…๋ ฅ
        • object : ๊ฐ์ฒด ๋˜๋Š” JSON ์ž…๋ ฅ
        • file : ํŒŒ์ผ ์—…๋กœ๋“œ

story

  • args : ๊ฐ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•  props๊ฐ’ ๋ช…์‹œ
  • render : ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง / react hook์„ ์ด์šฉํ•˜์—ฌ rendering ํ•  ๋•Œ ์‚ฌ์šฉ

storymeta ๋ฐ์ดํ„ฐ ์„ค์ • ์˜ˆ์‹œ

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

import { ButtonHTMLAttributes } from 'react';
import Button from '../components/Button/Button';

const meta = {
	// ์Šคํ† ๋ฆฌ๋ถ์— Common/Button ํด๋” ์ƒ์„ฑ
  title: 'Common/Button',
   
  // Button ์ปดํฌ๋„ŒํŠธ๋กœ ์Šคํ† ๋ฆฌ ์ƒ์„ฑ
  component: Button, 
  parameters: {
    layout: 'centered',
  },
  
  // ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑ
  tags: ['autodocs'], 
  
  // Button ์ปดํฌ๋„ŒํŠธ์—์„œ ์“ฐ์ด๋Š” props ๊ฐ’๋“ค์˜ ํƒ€์ž… ๋ช…์‹œ
  argTypes: { 
	  // radio ๋ฒ„ํŠผ 
    variant: {
      control: { type: 'radio' },
      options: ['primary', 'secondary', 'tertiary', 'outline'],
    },
    size: {
      control: { type: 'radio' },
      options: ['xLarge', 'large', 'medium'],
    },
    // text ์ž…๋ ฅ
    children: {
      control: { type: 'text' },
    },
    // true / false ์„ ํƒ
    isDisabled: {
      control: { type: 'boolean' },
    },
  },
  
  // ๊ณตํ†ต์œผ๋กœ ์“ฐ์ด๋Š” props๊ฐ’ ์ง€์ • || ๊ธฐ๋ณธ๊ฐ’ ์ง€์ •
  args: {
    variant: 'primary',
    size: 'medium',
    children: 'Button',
    isDisabled: false,
  },
} satisfies Meta<typeof Button>;

// ์ด meta ๋ฐ์ดํ„ฐ๋กœ ์Šคํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ• ๊ฑฐ๋‹ค ์„ ์–ธ!
export default meta;

control์˜ type๊ณผ props ๋ณ€๊ฒฝ ์˜ˆ์‹œ

์ด๋ ‡๊ฒŒ variant๋“ค์„ ์‹œ๊ฐ์ ์œผ๋กœ ๋ฐ”๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ!!


๊ธฐ๋ณธ story ์ƒ์„ฑ

type Story = StoryObj<typeof meta>;

// meta์—์„œ ์„ค์ •ํ•œ ๊ฐ’๋“ค๋กœ Default ์Šคํ† ๋ฆฌ ์ƒ์„ฑ
export const Default: Story = {};

createButtonStory ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํƒ€์ž…๋ณ„ story ์ƒ์„ฑ

// variant ๊ฐ’์— ๋”ฐ๋ฅธ story ์ƒ์„ฑ ํ•จ์ˆ˜
const createButtonStory = (variant: ButtonProps['variant']) => ({
  args: {
    variant,
  },
  argsType: {
    variant: {
      control: false,
    },
  },
});

// Primary ํƒ€์ž… ๋ฒ„ํŠผ ์Šคํ† ๋ฆฌ ์ƒ์„ฑ
export const Primary: Story = createButtonStory('primary');

// Secondary ํƒ€์ž… ๋ฒ„ํŠผ ์Šคํ† ๋ฆฌ ์ƒ์„ฑ
export const Secondary: Story = createButtonStory('secondary');

// Tertiary ํƒ€์ž… ๋ฒ„ํŠผ ์Šคํ† ๋ฆฌ ์ƒ์„ฑ
export const Tertiary: Story = createButtonStory('tertiary');

// Outline ํƒ€์ž… ๋ฒ„ํŠผ ์Šคํ† ๋ฆฌ ์ƒ์„ฑ
export const Outline: Story = createButtonStory('outline');

์ด๋ ‡๊ฒŒ ์ง€์ •ํ•ด์ค€ ํƒ€์ž…๋“ค๋งˆ๋‹ค story๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.


๊ทธ๋Ÿผ ์ด์ œ ํฌ๋กœ๋งˆํ‹ฑ์œผ๋กœ storybook์„ ๋ฐฐํฌํ•ด๋ณด๊ฒ ๋‹ค.
๋ฐฐํฌํ•˜๋Š” ์ด์œ ๋Š” ui๋ฅผ ๋‹ค๋ฅธ ํŒ€์›๋“ค๊ณผ ๊ณต์œ ํ•˜์—ฌ ์ฆ‰๊ฐ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.


๐ŸŽธ ํฌ๋กœ๋งˆํ‹ฑ์œผ๋กœ storybook ๋ฐฐํฌ

๋จผ์ € ํฌ๋กœ๋งˆํ‹ฑ ํ™ˆํŽ˜์ด์ง€์— ๊ฐ€์„œ github ๊ณ„์ •์„ ์—ฐ๋™ํ•˜๊ณ , ๋ฐฐํฌํ•  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ ˆํฌ๋ฅผ ์„ ํƒํ•˜์—ฌ ํ”„๋กœ์ ํŠธ์˜ ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›๋Š”๋‹ค!

๋‹ค์Œ์œผ๋กœ๋Š” ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

	pnpm add --save-dev chromatic
	npx chromatic --project-token=<your-project-token>

project-token์— ํ”„๋กœ์ ํŠธ์˜ ํ† ํฐ์„ ์ž…๋ ฅ

๋ฐฐํฌ๋œ ๋งํฌ๊ฐ€ cli์— ๋œธ, ๋ฐฐํฌ๋Š” ์•„์ฃผ ๊ฐ„๋‹จํ•˜๋‹ค.
๊ทธ๋Ÿผ ์ด์ œ ์ด ๋ฐฐํฌ๋ฅผ ์ž๋™ํ™”ํ•˜์—ฌ pr์„ ์˜ฌ๋ฆด๋•Œ๋งˆ๋‹ค ์ž‘์„ฑํ•œ ์Šคํ† ๋ฆฌ๊ฐ€ ๋ฐฐํฌ๋œ ํฌ๋กœ๋งˆํ‹ฑ์— ๋ฐ”๋กœ ๋ฐ˜์˜๋˜์–ด ์ฆ‰๊ฐ์ ์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ๋” ํ•ด๋ณด๊ฒ ๋‹ค.


๐ŸŽธ github action ci/cd ๋“ฑ๋ก

  • ์•„๊นŒ ๋ฐœ๊ธ‰๋ฐ›์€ ํ”„๋กœ์ ํŠธ ํ† ํฐ์„ secrets์— ๋“ฑ๋ก
  • ๋‹ค์Œ๊ณผ ๊ฐ™์ด .yml ์ƒ์„ฑ
name: 'Chromatic Publish'

on:
  pull_request:
    branches:
      - develop

permissions: write-all

jobs:
  storybook:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install PNPM
        run: npm i -g pnpm

      - name: Cache node modules
        id: cache-node
        uses: actions/cache@v3
        with:
          path: |
            **/node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install Dependencies
        if: steps.cache-node.outputs.cache-hit != 'true'
        run: pnpm install --frozen-lockfile

      - name: Build Storybook for Design System
        run: pnpm --filter @confeti/design-system run build-storybook

      - name: Publish to Chromatic
        id: chromatic
        uses: chromaui/action@latest
        with:
          projectToken: ${{ secrets.AUTO_SYNC_CHROMATIC }}
          token: ${{ secrets.SECRET_KEY }}
          onlyChanged: true
          autoAcceptChanges: true

      - name: Comment PR
        if: github.event_name == 'pull_request'
        uses: thollander/actions-comment-pull-request@v2
        env:
          GITHUB_TOKEN: ${{ secrets.SECRET_KEY}}
        with:
          comment_tag: ${{github.event.number}}-storybook
          message: '๐Ÿดโ€โ˜ ๏ธ Storybook ํ™•์ธ: ๐Ÿ”— ${{ steps.chromatic.outputs.storybookUrl }}'
          edit_mode: update


๐Ÿš€ ๊ด€๋ จ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…...

github์—์„œ ์ž๋™์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๋Š” GITHUB_TOKEN์€ ๋”ฐ๋กœ ๋“ฑ๋ก ์•ˆํ•ด์ค˜๋„ ๋œ๋‹ค๊ณ  ์•Œ๊ณ  ์žˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ๋‹ค์Œ๊ณผ ๊ฐ™์ด workflow ๊ถŒํ•œ๋ฌธ์ œ๊ฐ€ ๊ณ„์† ๋ฐœ์ƒํ•˜์—ฌ, ๋‚ด ๊ฐœ์ธ access token์„ ๋ฐœ๊ธ‰ํ•˜์—ฌ workflow ๊ถŒํ•œ์„ ์ถ”๊ฐ€ํ•œ ๋‹ค์Œ์— secret์— ๋“ฑ๋ก ํ›„ ๋Œ€์‹  ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๊ฒฐํ•˜์˜€๋‹ค..

Confeti์—์„œ๋Š” ๋””์ž์ธ ์‹œ์Šคํ…œ์„ ๋ชจ๋…ธ๋ ˆํฌ ๊ตฌ์กฐ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์–ด, ๋””์ž์ธ ์‹œ์Šคํ…œ๋งŒ ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•˜๋Š” ๋ฐ ์–ด๋ ค์›€์ด ์žˆ์—ˆ๋‹ค. ์›Œํฌํ”Œ๋กœ์šฐ์— ๋”ฐ๋ผ ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ storybook์„ ๋นŒ๋“œํ•˜์ง€๋งŒ, package.json์—์„œ ๋ช…๋ น์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์•„์„œ ๋นŒ๋“œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

//๋ฃจํŠธ package.json์— ๋‹ค์Œ scripts ์ถ”๊ฐ€
"scripts": {
    "build-storybook": "pnpm --filter @confeti/design-system run build-storybook"
  },

๋นŒ๋“œ๋ฅผ ์œ„ํ•ด @confeti/design-system์ด ํฌํ•จ๋œ package.json์—์„œ name ํ•„๋“œ๊ฐ€ ์ œ๋Œ€๋กœ ์„ค์ •๋˜์–ด ์žˆ์–ด์•ผ๋งŒ ํ•ด๋‹น ํŒจํ‚ค์ง€๊ฐ€ ํ•„ํ„ฐ๋ง๋˜๋Š”๋ฐ name์„ ํ™•์ธ ์•ˆํ•˜๊ณ  ๊ทธ๋ƒฅ ๊ฒฝ๋กœ ๋Œ€๋กœ packages/design-system์œผ๋กœ ์ž…๋ ฅํ•˜์—ฌ ๊ณ„์† ์˜ค๋ฅ˜๋‚ฌ์—ˆ๋‹ค.

-> name ํ™•์ธ ํ›„ @confeti/design-system์œผ๋กœ ๋ณ€๊ฒฝ

- name: Build Storybook for Design System
        run: pnpm --filter @confeti/design-system run build-storybook

๋ชจ๋…ธ๋ ˆํฌ์˜ ํด๋”๊ตฌ์กฐ ์ดํ•ด๊ฐ€ ์กฐ๊ธˆ ๋ถ€์กฑํ•ด์„œ ํ—ค๋งธ์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.
๋ชจ๋…ธ๋ ˆํฌ์—์„œ๋Š” ๊ฐ ํŒจํ‚ค์ง€๊ฐ€ ๋…๋ฆฝ์ ์ธ package.json์„ ๊ฐ–๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ์˜ package.json๊ณผ ๊ฐ ํŒจํ‚ค์ง€ ๋””๋ ‰ํ† ๋ฆฌ์˜ package.json์„ ๊ตฌ๋ถ„ํ•˜์—ฌ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ด์•ผ ํ•œ๋‹ค !!

profile
์•ˆ๋…•ํ•˜์„ธ์š”

1๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
1์ผ ์ „

๋•๋ถ„์— ์Šคํ† ๋ฆฌ๋ถ์—์„œ ๋ฐ”๋‹๋ผ์ต์ŠคํŠธ๋ž™ํŠธ ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์น˜ ์ž˜ ํ–‡์Šต๋‹ˆ๋‹น ใ…Ž ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ

๊ด€๋ จ ์ฑ„์šฉ ์ •๋ณด