ClaBi Design System

소비 앱 가이드

← 소비 앱 가이드

Developer Guide for ClaBi Storybook

이 레포에 컴포넌트·카탈로그를 추가·수정하는 개발자용입니다. 소비 앱에서 UI만 가져다 쓰는 분은 홈(Welcome)을 보세요.

개요

이 레포는 소비용 컴포넌트(src/components/*)와 카탈로그 문서(src/catalog/*)를 분리합니다. 새 컴포넌트·문서 추가 시 아래 단위·shared UI·스타일 우선순위를 따르면 됩니다.

1. 폴더 단위
src/
  components/<name>/     ← 소비 앱이 복사하는 단위
    Component.tsx        # 구현 (forwardRef · native 속성 전달)
    types.ts             # props / variant / size 타입
    styles.ts            # className · *Variants() · import { clsx } from "clsx"
    index.ts             # public export

  catalog/<name>/        ← 이 사이트 Overview · Docs · Stories만
    docs.ts              # *_CATALOG_PAGES · *_DOC_SECTIONS · *_API
    *StateView.tsx       # Overview / Docs 화면
    *.stories.tsx        # Storybook CSF
    index.ts             # StateView · docs export

  shared/
    ui/                  # 카탈로그 문서 전용 UI (소비 앱 비필수)
    config/navigator.ts  # 사이드바 등록
    config/component-previews.tsx  # 라우트 → StateView 연결
  • components는 가능하면 다른 컴포넌트에 의존하지 않습니다. 조합이 필요하면 Overview에서 권장 패턴으로 보여 줍니다 (예: Toggle 상단은 Label).
  • 사이드바에 노출하려면 navigator.ts component-previews.tsx에 등록합니다.
2. Overview / Docs에서 shared UI를 쓰는 법

실제로 쓰는 shared 모듈은 다음과 같습니다.

  • SectionIndex — Overview 우측 앵커 목차. OVERVIEW_INDEX의 id는 *_DOC_SECTIONS의 id와 1:1.
  • DocSectionView — 섹션 제목·summary·options 리스트 + 프리뷰 영역. contentVariant="framed"(기본, 카드 박스) / "plain"(States 매트릭스 등 호출측 레이아웃).
  • LabeledPreview — DocSectionView에서 export. 프리뷰 한 칸의 라벨 + 슬롯.
  • ApiGuide — Docs 페이지. *_API(import 예시 · props · events · notes)를 표로 렌더.
  • docs.ts 타입 —DocSection · DocApi.
// catalog/<name>/*StateView.tsx 골격
"use client";
import ApiGuide from "@/shared/ui/ApiGuide";
import DocSectionView, { LabeledPreview } from "@/shared/ui/DocSectionView";
import SectionIndex from "@/shared/ui/SectionIndex";
import { X_API, X_CATALOG_PAGES, X_DOC_SECTIONS, type XCatalogPage } from "./docs";

const OVERVIEW_INDEX = [
  { id: "variants", label: "Variants" }, // DOC_SECTIONS id와 동일
  { id: "sizes", label: "Sizes" },
] as const;

function OverviewView() {
  const variants = X_DOC_SECTIONS.find((s) => s.id === "variants")!;
  return (
    <div className="relative space-y-10 md:space-y-20">
      <SectionIndex items={OVERVIEW_INDEX} />
      <DocSectionView section={variants} contentClassName="grid …">
        <LabeledPreview label="Primary">
          <Component />
        </LabeledPreview>
      </DocSectionView>
      {/* States 매트릭스 등은 contentVariant="plain" */}
    </div>
  );
}

function DocsView() {
  return <ApiGuide api={X_API} id="api" />;
}

export default function XStateView({ state }: { state: XCatalogPage }) {
  return state === "docs" ? <DocsView /> : <OverviewView />;
}

Overview= 눈으로 고르는 축(Variants · Sizes · Colors · States · Props 등). Docs= 복사해 쓸 API. overview description / section summary는 docs.ts에 두고 StateView는 렌더만 담당합니다.

3. 스타일 우선순위
  1. 디자인 토큰 (1순위) var(--surface-primary)· text-text-default 등 ClaBi 시맨틱. hex를 컴포넌트 기본값에 하드코딩하지 않습니다.
  2. styles.ts + *Variants() — variant / size / state 기본 클래스. JSX에 긴 Tailwind 문자열을 직접 쌓지 않습니다.
  3. 소비 앱 className / selectedClassName — 덮어쓰기용. clsx만 쓰므로(twMerge 없음) 유틸이 겹치면 기본 클래스를 생략하는 패턴을 따릅니다 (Button size·색, Tabs hover, Modal 크기 등).
  4. 인라인 style — 동적 색(Badge soft 커스텀 등)처럼 클래스만으로 어려운 경우만.
// styles.ts 권장 패턴
import { clsx } from "clsx";

export function componentVariants({ variant, size, className } = {}) {
  return clsx(
    baseClassName,
    !hasSizeOverrideClass(className) && sizeClassName[size],
    !hasColorOverrideClass(className) && variantClassName[variant],
    className, // 소비 앱 마지막
  );
}
4. 새 컴포넌트 체크리스트
  • components/<name> 구현 · types · styles · index
  • catalog/<name> docs · StateView · stories · index
  • Overview 섹션 id = kebab-case, SectionIndex와 동기화
  • Docs에 import 예시 · props · notes(소비 앱 필수 스택 포함)
  • navigator + component-previews 등록 (라벨 정렬은 localeCompare)
  • 레퍼런스: Visual → Button, Behavior → Input, Composition → Field