공통 컴포넌트인 input 컴포넌트의 스토리 코드를 보면서 기본 사용 방법을 정리해보려고 한다.

스토리 파일 경로
src/app/stories/design-system/components/input/text/input.stories.tsx
(next.js app router 기준)
스토리 파일은 src/app/stories 폴더 밑에 들어가게된다.
파일명은 000.stories.tsx
폴더 경로는 스토리북에서 보이는 경로와는 무관하며
스토리북에 생성되는 폴더 경로는 meta 객체 안의 title 에 보이는
Design System/Components/TextInput/TextInput
이 경로로 생성 된다.
우리 프로젝트의 스토리북 폴더 구조는 대략 이렇다.

meta 객체
"문서화는 중요하지만 힘든 작업이다. 그러나 대부분의 문서는 생성되는 즉시 구식이 되어버린다."
"그렇기에 다른 개발자에게 더 많은 문맥(왜, 언제, 어떻게) 를 제공할 필요가 있다." 고 한다.
너무 공감하는 바이다. ..
이러한 문서의 정보(메타 데이터) 를 제공하는 역할로서 meta 객체가 사용된다.
const meta = {
title: "Design System/Components/TextInput/TextInput",
component: BaseInput,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
variant: { ... },
type: { ... },
_storySize: { ... },
disabled: { ... },
},
} satisfies Meta<StoryProps>;
title: Storybook의 UI에서 스토리가 표시되는 경로를 설정 => 자동으로 폴더가 생성된다.
component: 스토리에서 다루는 실제 컴포넌트를 지정 => 해당 컴포넌트를 import 해야한다.
parameters: Storybook에서 스토리를 렌더링할 때의 전역 설정
- layout: "centered"는 스토리의 컴포넌트를 화면 가운데 정렬합니다.
tags: 추가 정보를 제공하기 위한 태그
- "autodocs"는 자동 문서화를 활성화하는 태그입니다.
argTypes: 컴포넌트의 Props(스토리에서 사용할 속성)를 정의하고, 이를 Storybook에서 제어할 수 있도록 설정
argTypes 에 포함되는 control 방법과 선택 가능한 여러 옵션을 추가할 수 있다
argTypes: {
variant: {
control: "radio",
options: ["white", "transparent"],
},
_storySize: {
control: "radio",
options: ["mobile", "desktop"],
description: "입력창 크기 설정",
},
disabled: {
control: "boolean",
},
}
스토리북 하단에서 controls 패널을 확인할 수 있고,
여기서 argsTypes 에 각 prop을 어떻게 제어할 것인지 설정해준 대로 제어할 수 있다.

여러가지 속성과 옵션으로 prop을 컨트롤러에서 제어할 수 있다
| controls 객체의 key 속성 | value | 예시 |
| type | text, number, radio, inline-radio, boolean, select, multi-select , color, range, date 등 |
control: { type: "color" } |
| options | 사용가능한 값 | control: { type: "multi-select" }, options: ["apple", "banana", "cherry"] |
| min, max, step, | 숫자 값의 범위와 증감 단위 | control: { type: "range", min: 0, max: 100, step: 10 } |
| description | Props에 대한 설명 | |
| defaultValue | 컨트롤러의 초기 값 | |
| table | 표로 나타냄 | table: { disable: true, // 표에서 숨김 category: "Advanced Options", type: { summary: "string" } } |
https://storybook.js.org/docs/6/essentials/controls
Storybook: Frontend workshop for UI development
Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It's open source and free.
storybook.js.org
나도 다음에 더 다양하게 적용해봐야겠다
StoryObj 로 스토리 정의하기
StoryObj 는 Storybook에서 스토리를 정의할 때 사용하는 타입이다.
Storybook 7.0 이후로 스토리를 정의하는 방법이라고 한다.
간략하게는 아래와 같이 사용할 수 있다.
Error, Feedback이라는 두 개의 스토리가 생성되고 args 객체에서 설정된 값이 컴포넌트에 prop으로 전달된다.
이때 컴포넌트가 필수로 받아야 하는 prop을 빼먹으면 에러가 난다.
따라서 컴포넌트의 prop을 수정했을때 스토리도 같이 수정이 되어야한다.
export const Error: Story = {
args: {
type: "text",
variant: "white",
placeholder: "텍스트 입력",
errormessage: "에러 메시지",
},
};
export const Feedback: Story = {
args: {
type: "text",
variant: "white",
placeholder: "텍스트 입력",
feedbackMessage: "피드백 메시지",
},
};
StoryObj 의 주요 속성으로는
args 외에도 parameters, play, decorators, argTypes, render 가 있다.
- args: "이 값으로 시작하세요"
- argTypes: "이렇게 제어하고 문서화하세요"
나는 default 상태와 hover, focus 상태의 컴포넌트를 동시에 보여주고 싶어서 render를 사용했다.
export const Default_Hover_Focus: Story = {
args: {
//...
},
render: (args) => {
const { _storySize, ...rest } = args as StoryProps;
const sizeClass = _storySize ? storySizeMap[_storySize] : "";
const StoryComponent = () => <BaseInput {...rest} size={sizeClass} />;
return (
<div className="space-y-4">
<div>
<p className="mb-2 text-sm text-grayscale-500">기본 상태:</p>
<StoryComponent />
</div>
//...
<div>
<p className="mb-2 text-sm text-grayscale-500">Focus 상태:</p>
<div className="[&>div>div]:border-primary-orange-300">
<StoryComponent />
</div>
</div>
</div>
);
},
};
decorators 와 render 속성의 주요 차이점은 아래와 같다
| 특징 | render | decorators |
| 목적 | 스토리의 렌더링 방식을 직접 정의 | 스토리를 꾸미거나 외부 환경을 추가 |
| 적용 대상 | 특정 스토리에서만 사용 | 특정 스토리 또는 전역적으로 적용 가능 |
| 구현 방식 | 스토리를 렌더링하는 자체 로직 제공 | 스토리를 래핑(Wrapping)하는 컴포넌트 제공 |
| 전역 적용 | 불가능 | 가능 (글로벌 데코레이터 설정 가능) |
| 사용 사례 | 렌더링을 완전히 커스터마이징해야 하는 경우 | 공통적인 스타일, 레이아웃, 컨텍스트, 테마 등을 적용해야 하는 경우 |
두 가지를 함께 사용하여 decorators를 통해 스토리의 외부 환경을 설정하고, render를 사용해 내부 로직을 커스터마이징할 수 있다고 한다 ~
코드 전문은 아래와 같다.
// TextInput.stories.tsx
import { Meta, StoryObj } from "@storybook/react";
import { BaseInputProps } from "@/types/textInput";
import BaseInput from "@/app/components/input/text/BaseInput";
type StoryProps = BaseInputProps & {
_storySize?: "mobile" | "desktop";
};
const meta = {
title: "Design System/Components/TextInput/TextInput",
component: BaseInput,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
variant: {
control: "radio",
options: ["white", "transparent"],
},
type: {
control: "radio",
options: ["text", "password"],
},
_storySize: {
control: "radio",
options: ["mobile", "desktop"],
description: "입력창 크기 설정",
},
disabled: {
control: "boolean",
},
},
} satisfies Meta<StoryProps>;
export default meta;
const storySizeMap = {
mobile: "w-[327px] h-[54px]",
desktop: "lg:w-[640px] lg:h-[64px]",
};
type Story = StoryObj<typeof BaseInput>;
export const Default_Hover_Focus: Story = {
args: {
type: "text",
variant: "white",
placeholder: "텍스트 입력",
},
render: (args) => {
const { _storySize, ...rest } = args as StoryProps;
const sizeClass = _storySize ? storySizeMap[_storySize] : "";
const StoryComponent = () => <BaseInput {...rest} size={sizeClass} />;
return (
<div className="space-y-4">
<div>
<p className="mb-2 text-sm text-grayscale-500">기본 상태:</p>
<StoryComponent />
</div>
<div>
<p className="mb-2 text-sm text-grayscale-500">Hover 상태:</p>
<div className="[&>div>div]:border-grayscale-200 [&>div>div]:bg-background-300">
<StoryComponent />
</div>
</div>
<div>
<p className="mb-2 text-sm text-grayscale-500">Focus 상태:</p>
<div className="[&>div>div]:border-primary-orange-300">
<StoryComponent />
</div>
</div>
</div>
);
},
};
export const Error: Story = {
args: {
type: "text",
variant: "white",
placeholder: "텍스트 입력",
errormessage: "에러 메시지",
},
};
export const Feedback: Story = {
args: {
type: "text",
variant: "white",
placeholder: "텍스트 입력",
feedbackMessage: "피드백 메시지",
},
};
아마 비효율적이고 엉성한 코드일지도 모르겠지만, 우선은 이렇게 작성했습니다 헤헷,,
컴포넌트 별로 스토리도 제각기이지만 전체적인 큰 틀은 비슷할 것이기에 요것만 대표로 포스팅 해보았다.
나의 첫번째 스토리...*
사실 프로젝트 도중에는 개념을 하나하나 알아가며 쓰기 보다는, ai 선생님께 무분별한 도움을 받아서 작성한 부분이 많다.
그래서 뒤늦게나마 이렇게 정리를 해보는것이 큰 도움이 되는 것 같다.

'개발개발 > WorkRoot_워크루트' 카테고리의 다른 글
| 리액트 훅 폼 (React Hook Form) 트러블 슈팅 1 - 여러 탭에 걸친 폼 데이터 제출하기(watch, setValue 사용하기, getValues) (0) | 2025.01.03 |
|---|---|
| 데이터 피커 (react-datepicker) 달력 UI 커스텀하기 (1) | 2025.01.03 |
| 스토리북 (storybook) 트러블 슈팅 - 2 (React-hook-form) (1) | 2025.01.03 |
| 스토리북 (storybook) 트러블 슈팅 - 1 (Tailwind, addon-styling, 컴포넌트 고민 ) (3) | 2025.01.03 |
| UI 컴포넌트 개발을 위한 스토리북(Storybook) 장단점 (1) | 2025.01.03 |