Skip to content

Conversation

@u-zzn
Copy link
Collaborator

@u-zzn u-zzn commented Jan 4, 2026

✏️ Summary

  • close [Init] Storybook 초기 세팅 #11

  • Comfit 서비스에 Storybook 초기 세팅을 진행했습니다.

  • shared 레이어 공용 UI부터 문서화/검증할 수 있도록 FSD 구조 기반으로 Story 작성 규칙을 정리했습니다.

  • 첫 예시 컴포넌트로 Button을 등록하여 Docs(Props 문서화), A11y(접근성 검사) 를 바로 확인할 수 있도록 테스트해봤습니다.

  • 기본 템플릿(온보딩/예제 스토리/테스트 통합 등) 중 아직 클라 팀 내부에서 합의되지 않거나 프로젝트에 적용이 이르다고 판단되는 항목은 제거하였습니다.

📑 Tasks

✔️ Comfit이 Storybook을 도입한 이유

Comfit 서비스는 공용 UI(Shared) 컴포넌트가 여러 페이지/기능에서 재사용되며, 작은 UI 변경이 전체 화면에 영향을 줄 수 있다고 판단했습니다. 따라서 Storybook을 도입하면 다음을 해결할 수 있습니다.

  • 컴포넌트를 페이지와 분리해 독립적으로 개발하고 테스트할 수 있습니다.
  • variant, size, disabled 같은 주요 상태를 Story로 관리해 UI 변경에 따른 사이드 이펙트를 빠르게 확인할 수 있습니다.
  • 컴포넌트의 사용 방식과 Props를 한 곳에서 공유해 협업 비용을 줄일 수 있습니다.
  • 공용 UI 변경 시 페이지 QA 전에 UI 레벨에서 먼저 검증할 수 있습니다.

✔️ 실행방법

로컬 실행

pnpm storybook

기본 포트

빌드

pnpm build-storybook

✔️ 폴더 구조 및 문서화 범위 (FSD 기준)

  • Comfit은 Feature-Sliced Design(FSD) 구조를 기준으로 컴포넌트를 관리합니다.
src/
├── app/
├── pages/
├── widgets/
├── features/
└── shared/

지금까지 합의된 Comfit의 Storybook 문서화 대상 우선순위

  • shared/: 최우선 문서화 대상 (Button, Input, Text 등 공용 UI)
  • features / widgets: 재사용/복잡도가 높을 때만 선택적으로 문서화
  • 특정 페이지 전용 UI는 Storybook 대상에서 제외 (문서화 비용 대비 효용이 낮다고 판단함)

✔️ 이번 PR에서 추가/변경된 내용

Storybook config 추가

  • .storybook/main.ts
  • .storybook/preview.ts
    Storybook에서 경로 aliasDocs/A11y 동작을 위한 최소 설정을 구성했습니다.

Button 컴포넌트 + 스토리 추가 (첫 문서화 대상)

  • src/shared/ui/button/Button.tsx
  • src/shared/ui/button/Button.stories.tsx

왜 Button을 첫 대상으로 선택했나요?

  • UI 프로젝트에서 가장 먼저 합의가 필요한 요소(variant/size/disabled)가 명확하고,
  • 이후 Input/Modal 등 다른 공용 컴포넌트로 확장할 때 기준점이 된다고 판단했기 때문입니다.

✔️ Story 작성 규칙

Story 위치

Story 파일은 컴포넌트와 동일한 디렉토리에 작성합니다.

src/shared/ui/button/
├── Button.tsx
└── Button.stories.tsx
  • 컴포넌트와 Story를 한 위치에서 관리하여 구현 코드와 사용 예시를 함께 파악할 수 있습니다.
  • Storybook 전용 디렉토리는 사용하지 않습니다. (Comfit의 FSD 구조에 맞게 초기 Storybook 전용 디렉토리 설치된 후 바로 삭제 진행하였습니다.)

Story 네이밍 컨벤션

title: 'Shared/Button'
  • Shared / Feature / Widget 기준으로 카테고리를 구분합니다.
  • Storybook 사이드바 구조가 프로젝트의 FSD 구조를 자연스럽게 반영하도록 구성합니다.
  • 이를 통해 컴포넌트의 역할과 사용 범위를 한눈에 파악할 수 있도록 합니다.

✔️ Button.tsx는 왜 이렇게 구성했을까요?

HTML button 기본 속성 확장

type Props = ButtonHTMLAttributes<HTMLButtonElement> & {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
};
  • React 기본 button 속성(onClick, disabled 등)을 그대로 사용 가능합니다.
  • 디자인 시스템에서 필요한 props(variant/size)만 추가하는 형태입니다.

스타일은 “상태 차이가 명확하게 보이도록” 구성

  • Storybook은 단순 미리보기뿐 아니라 “상태 차이”를 빠르게 확인하기 위한 도구이기 때문에, primary/secondary, sm/md/lg, disabled가 시각적으로 잘 구분되도록 구성했습니다.
  • (추후 vanilla-extract 도입 시 동일한 구조로 토큰 기반 스타일로 교체하면 좋을 것 같은데 클라 팀끼리 얘기 나눠봐요!)

✔️ Button.stories.tsx는 왜 이렇게 구성했을까요?

argTypes로 Controls 제공

argTypes: {
  variant: { control: 'radio', options: ['primary', 'secondary'] },
  size: { control: 'radio', options: ['sm', 'md', 'lg'] },
  disabled: { control: 'boolean' },
  onClick: { action: 'clicked' },
}
  • Storybook의 Controls 패널에서 variant/size/disabled를 즉시 바꿔보며 UI를 확인할 수 있습니다.
  • onClick: { action: 'clicked' }를 명시해서 Actions 패널에서 클릭 이벤트가 로깅되는 것을 한눈에 확인할 수 있습니다. (preview의 regex로 자동 수집도 가능하지만, 컴포넌트 사용자가 “클릭 가능한 컴포넌트”임을 더 명확히 인지할 수 있도록 명시했습니다.)

✔️ addons 구성 (왜 docs/a11y만 포함했을까요?)

이번 PR은 “초기 세팅”이 목적이라 판단하여 실제로 바로 필요한 addons만 선택해서 진행했습니다.

포함한 addon

@storybook/addon-docs

  • Docs 탭에서 Props 자동 문서화 및 스토리 기반 문서 페이지를 제공합니다.
  • 공용 UI 컴포넌트의 사용 방법과 의도를 코드 기준으로 정리할 수 있어 팀 내 UI 컨벤션 합의에 직접적인 도움이 됩니다.
  • Storybook을 “문서”로 활용하기 위한 필수 addon이라고 판단했습니다.

@storybook/addon-a11y

  • 컴포넌트 단위에서 접근성 이슈를 조기에 확인할 수 있습니다.
  • Button, Input, Modal 등 공용 UI에서 특히 중요도가 높아 초기부터 포함했습니다.
  • 페이지 단위 QA 이전에 UI 레벨에서 기본적인 접근성 검증이 가능합니다.

제외한 addon (삭제 / 비활성화 이유)

@chromatic-com/storybook

  • Chromatic은 외부 서비스 연동이 필요하며 계정, 토큰, CI 설정, 비용 및 운영 정책까지 함께 고려해야 합니다.
  • Storybook 사용 방식이 아직 정착되지 않은 초기 단계에서 도입할 경우 툴 관리 부담이 커질 수 있다고 판단했습니다.
  • 팀 내 Storybook 활용 컨벤션이 안정된 이후, 필요 시 visual regression 용도로 도입하는 것이 더 적절하다고 판단했습니다.

@storybook/addon-vitest (및 Vitest 관련 설정)

  • 현재 프로젝트에서는 Vitest 기반 테스트를 사용하지 않고 있습니다.
  • Storybook 도입 초기부터 테스트 통합까지 포함하면 핵심 목적이 흐려질 수 있다고 판단해서 우선은 팀 내에서 합의가 된 Storybook의 본래 목적(문서화 / 상태 검증)을 정착시키고, 이후 필요할 경우 Interaction Test나 Visual Test를 단계적으로 추가하는 흐름이 더 안전하다고 판단했습니다.

✔️ 추후 수정 계획

이번 PR은 Storybook 초기 도입에 집중하고 있어서, 아래 항목들은 추후 단계적으로 확장해보면 좋을 것 같습니다!

vanilla-extract 도입 이후

  • theme token 및 globalStyle 세팅하도록 하겠습니다.

문서화 범위 / 규칙 정리

  • 어떤 컴포넌트를 Storybook에 포함할지, 어떤 수준까지 문서화할지에 대한 기준을 팀 컨벤션으로 정리하면 좋을 것 같습니다.

Chromatic 등 도입

  • 팀 컨벤션과 CI 환경이 안정된 이후 필요 시 Chromatic 또는 테스트 도구 도입을 검토해보면 좋을 것 같습니다.

👀 To Reviewer

  • 제가 Storybook을 처음 사용해보고, 처음 세팅해봐서 😭 제대로 한 건지 사실 잘 모르겠어요 .. 많은 리뷰와 코멘트 환영입니다 ㅠㅠ ..
  • 그리고 addons 설정을 일단은 필요한 것만 최소화하려고 하다보니 혹시 필수적인 것도 제가 삭제시켜버린게 아닌가 ㅠㅠ 걱정이 되어서 한 번 씩 확인해주시면 감사하겠습니다.. 🖤
  • Stories에서 Primary / Secondary / Small / Large / Disabled / LongLabel 등은 제가 임의로 작성한 예시인데 클라 팀끼리 컨벤션(variant 종류, 기본 크기, 라벨 정책 등)을 정해보면 좋을 것 같습니다! 컨벤션 정해지면 그에 맞게 추후에 다시 수정하도록 할게요!
  • shared 레이어 우선 문서화 기준과 title: 'Shared/... 카테고리 구분이 적절한지 의견 부탁드립니다!
  • 아직 작업되지 않은 다른 초기 세팅들이 되어 있어야 할 수 있는 부분들은 후속 PR로 작업해보도록 하겠습니다!

📸 Screenshot

image image image

🔔 ETC

  • CI 환경에서는 frozen-lockfile 옵션이 적용되어 있어, package.json이 변경되었는데 pnpm-lock.yaml이 함께 업데이트되지 않으면 빌드가 실패할 수 있습니다.
  • 따라서 의존성을 추가·제거·변경하는 PR에서는 반드시 pnpm-lock.yaml을 함께 커밋해야 합니다.

@github-actions
Copy link

github-actions bot commented Jan 4, 2026

🚀 빌드 결과

린트 검사 완료
빌드 성공

로그 확인하기
Actions 탭에서 자세히 보기

@u-zzn u-zzn changed the title [Init] Storybook 초기세팅 [Init] Storybook 초기 세팅 Jan 4, 2026
Copy link
Collaborator

@odukong odukong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

세팅하면서 처음이라 생각할 게 많으셨을 것 같은데 너무너무 수고하셨습니다 💞

특히 Introduction.mdx를 통해 스토리북 도입 배경과 함께 추가적으로 스토리북 파일 컨벤션 문서화해주신 부분이 너무 좋습니다(^///^) 제안해주신 대로 variant 등의 상세 컨벤션은 개발하며 맞춰가면 충분할 것 같아요!

추가적으로 preview.ts파일에 Vanilla Extract 테마 클래스(themeVars)적용이나 Query Provider설정을 위해 decorators를 활용해 스토리를 감싸주는 작업이 필요할 것 같아요. 현재는 디자인 시스템 구축과 tanstack-query설정이 아직 진행되지 않기도 해서 다음 작업 시 이 부분도 고려해 주시면 좋을 것 같아요! [참조링크1] [참조링크2]

만약 공통 컴포넌트만 스토리북으로 작업하게 된다면 queryProvider로 감싸는게 의미가 있을까..? 생각이 들기도 하지만,..? 추후 스토리북 컴포넌트에서 API 호출 작업을 진행할 때 필요할 수도 있으니 참고해주세요!!

Comment on lines 19 to 40
viteFinal: async (storybookConfig) =>
mergeConfig(storybookConfig, {
resolve: {
alias: {
'@': resolve(__dirname, '../src'),
'@app': resolve(__dirname, '../src/app'),
'@pages': resolve(__dirname, '../src/pages'),
'@widgets': resolve(__dirname, '../src/widgets'),
'@features': resolve(__dirname, '../src/features'),
'@shared': resolve(__dirname, '../src/shared'),
'@images': resolve(__dirname, '../src/shared/assets/images'),
'@icons': resolve(__dirname, '../src/shared/assets/icons'),
'@api': resolve(__dirname, '../src/shared/api'),
'@config': resolve(__dirname, '../src/shared/config'),
'@model': resolve(__dirname, '../src/shared/model'),
'@styles': resolve(__dirname, '../src/shared/styles'),
'@types': resolve(__dirname, '../src/shared/types'),
'@ui': resolve(__dirname, '../src/shared/ui'),
'@lib': resolve(__dirname, '../src/shared/lib'),
},
},
}),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스토리북 파일 내부에서도 path alias형태로 가져올 수 있게 정의해준 점 너무 좋은 것 같아요!
vite.config.ts, tsconfig.app.json에서 새로운 path가 추가될 때마다 스토리북 내부에서도 이를 같이 동기화시킬 필요가 있을 것 같아서 vite-tsconfig-paths 플러그인으로 싱크 맞춰주는 방법도 한 번 제안드려봐요 🎵

Suggested change
viteFinal: async (storybookConfig) =>
mergeConfig(storybookConfig, {
resolve: {
alias: {
'@': resolve(__dirname, '../src'),
'@app': resolve(__dirname, '../src/app'),
'@pages': resolve(__dirname, '../src/pages'),
'@widgets': resolve(__dirname, '../src/widgets'),
'@features': resolve(__dirname, '../src/features'),
'@shared': resolve(__dirname, '../src/shared'),
'@images': resolve(__dirname, '../src/shared/assets/images'),
'@icons': resolve(__dirname, '../src/shared/assets/icons'),
'@api': resolve(__dirname, '../src/shared/api'),
'@config': resolve(__dirname, '../src/shared/config'),
'@model': resolve(__dirname, '../src/shared/model'),
'@styles': resolve(__dirname, '../src/shared/styles'),
'@types': resolve(__dirname, '../src/shared/types'),
'@ui': resolve(__dirname, '../src/shared/ui'),
'@lib': resolve(__dirname, '../src/shared/lib'),
},
},
}),
import tsconfigPaths from 'vite-tsconfig-paths'; // 별도 설치
export default {
viteFinal: async (config) => {
/* 생략 */
return mergeConfig(config, {
plugins: [tsconfigPaths()],
});
},
};

Comment on lines 44 to 47
reactDocgenTypescriptOptions: {
shouldExtractLiteralValuesFromEnum: true,
propFilter: (prop) => !/node_modules/.test(prop.parent?.fileName || ''),
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

옵셔널 타입의 경우(ex. disabled?: boolean), storybook상에 생성될 문서에 boolean(true, false) 뿐만 아니라 undefined까지 함께 보여진다고 해요.
현재 Button예제는 react-docgen-typescript이 기본적으로 undefined를 제거하는 방향으로 동작하고 있지만, 더 복잡한 타입의 경우에는 undefined가 그대로 노출되는 경우도 있다고 해서, undefined는 제거하여 docs를 만들어주는 옵션인 shouldRemoveUndefinedFromOptional : true도 함께 정의해주면 좀 더 보기 좋은 storybook docs가 만들어질 것 같아요🥰💞

Comment on lines +13 to +18
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스토리북의 대부분 컴포넌트에서 docs 파일이 생성될 거라면, preview.ts파일에 tags: ["autodocs"] 옵션을 미리 추가해주는 것도 좋을 것 같아요🙌🏻

Suggested change
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
tags: ["autodocs"],

Comment on lines +104 to +112
### 📁 Story 위치

Story 파일은 **컴포넌트와 동일한 디렉토리**에 작성합니다.

```txt
src/shared/ui/button/
├── Button.tsx
└── Button.stories.tsx
```
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

물론 스토리북 파일의 위치에 대한 내용은 추후에 논의를 통해 변경될 수 있는 사항이겠지만,
컴포넌트 파일과 관련 스타일 파일을 최대한 가까운 위치에 두는 것이 응집도를 높이는데 좋은 방식이라는 의견이 많기 때문에 현재 작성해주신 컨벤션대로 스토리북 파일을 위치시키면 좋을 것 같아요💟

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동의합니다~

Copy link
Contributor

@hummingbbird hummingbbird left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storybook 세팅 고생많았습니다! storybook을 많이 사용해본 팀원이 없어서 부담이 컸을텐데 너무 잘해주셨어요 최고다 유진이 .. 👊 storybook 도입 목적, 도입 분기점, 작성 규칙 등을 근거있게 PR에 잘 정리해주셔서 빠르게 이해하고 코리 남길 수 있었던 거 같아요 👍

유진님 말씀대로 바익 초기세팅하면서 토큰 기반 스타일 구조로 작성하는 틀을 함께 잡아보면 좋을 거 같아요! (1주차에서 제가 바익 세팅해보면서 작성해놓은 아티클이 있는데 거기에 관련 코드 붙여두겠습니다😀)

shared 레이어 우선 문서화 기준 적합하다고 생각하고, 카테고리 구분 역시 적절하다고 생각합니다~ 빠르게 스타일 초기화 세팅 후에 그거에 맞게 좀 더 보완하면 좋을 거 같아요~

코드 상 문제는 없는 거 같아 어푸하겠습니다 수빈이 리뷰만 한 번 확인해주세요!

Comment on lines 1 to 2
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Button } from '@shared/ui/button/Button';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import type을 가장 하단에 작성하기로 한 컨벤션 지켜서 수정해주세요! (후다닥 노션에 추가해두겠습니다 💨)

Comment on lines +104 to +112
### 📁 Story 위치

Story 파일은 **컴포넌트와 동일한 디렉토리**에 작성합니다.

```txt
src/shared/ui/button/
├── Button.tsx
└── Button.stories.tsx
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동의합니다~

Comment on lines 8 to 11
parameters: {
layout: 'padded',

actions: { argTypesRegex: '^on[A-Z].*' },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추가적으로 합세 스토리북 세팅할 때 발견했던 부분인데,
{ argTypesRegex: '^on[A-Z].*' }옵션은 on으로 시작하고 그 다음에는 대문자로 시작하는 이벤트 핸들러 props(ex. onClick, onChange)에 대해 자동으로 actions으로 연결해주는 방식이에요.

하지만 v8버전 이상부터는 { argTypesRegex: '^on[A-Z].*' } 옵션을 권장하고 있지 않다고 합니다! (실제로 합세 때 해당 옵션을 사용했을 때 경고 콘솔이 뜨더라구요)
https://storybook.js.org/docs/essentials/actions#automatically-matching-args
대신 @storybook/test에서 제공하는 fn()을 사용하여 스토리북 단에서 명시적으로 이벤트 핸들러를 정의하는 방식을 권장하고 있다고 해요!

args: {
  onClick: fn(), 
}

현재는 별도 경고는 안 떠서 괜찮아 보이긴 합니다!

다만 공식 문서에서 권장하는 fn()은 주로 인터랙션 테스트(@storybook/test) 를 위한 기능이라, 저희가 당장 테스트 자동화를 도입할 게 아니라면 굳이 라이브러리를 추가로 설치해서 무겁게 갈 필요는 없을 것 같습니다.
작성해주신 Button 스토리처럼 argTypes에 action: 'clicked'를 명시하면 로그 확인이 충분히 가능하니, 우선은 추가 설치 없이 preview.tsargTypesRegex 설정만 제거하는 방향으로 가볍게 유지해도 좋을 것 같아요!👍🏻

// 나중에 입력값 등을 상세히 보고 싶을 땐 action 함수를 사용하는 방식도 있다고 하네요!
import { action } from '@storybook/addon-actions';

const meta = {
  // ...
  args: {
    onClick: action('clicked'),
    onChange: (e) => action('onChange')(e.target.value),
  },
};

저도 스토리북을 제대로 활용해본 경험은 아직 없어서😭 같이 배워가면서 한 번 열심히 스토리북 채워봅시다!!

@u-zzn
Copy link
Collaborator Author

u-zzn commented Jan 5, 2026

리뷰에서 남겨주신 의견들 모두 확인하고 반영했습니다 ☺️🖤
한 번씩 확인해주시면 감사하고, 저도 Storybook 세팅 및 사용이 처음이라 많이 어려웠는데 꼼꼼한 리뷰 덕분에 많이 도움이 되었네요 :)

수빈님 말씀처럼 추후 작업 시에 preview.ts파일에 Vanilla Extract 테마 클래스(themeVars)적용이나 Query Provider설정을 위해 decorators를 활용해 스토리를 감싸주는거 염두해두고 작업 진행하도록 하겠습니다!! 💪

앞으로 차근차근 컨벤션 확정해가며 작업해보도록 해요!! 다들 감사합니다 😽

Copy link
Collaborator

@qowjdals23 qowjdals23 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storybook 제대로 써본 적이 거의 없어서 잘 몰랐는데, 자세한 PR과 꼼꼼한 리뷰 보면서 많이 배워 갑니다 😍
Button 예시로 variant/size/disabled 상태를 Story로 확인 가능하게 해둔 것도 , Docs/A11y 신경 쓴 부분도 너무 좋네요 !!

리뷰 반영도 깔끔하게 잘하신 것 같고, 유진님 덕분에 이후 공용 컴포넌트들 Storybook으로 차근차근 쌓아가기 쉬울 것 같아요 !!! 수고 많으셨습니다!! ❤️‍🔥

u-zzn and others added 2 commits January 6, 2026 08:11
- pnpm-lock.yaml 중복키 제거를 위한 삭제, 재설치
@u-zzn u-zzn merged commit e2fc2f6 into dev Jan 6, 2026
2 checks passed
@u-zzn u-zzn deleted the init/#11/storybook-init branch January 6, 2026 05:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Init] Storybook 초기 세팅

5 participants