Skip to content

Feat(design-system): 뱃지 컴포넌트 #30

Merged
jllee000 merged 14 commits intodevelopfrom
feat/#18/badge-component
Aug 25, 2025
Merged

Feat(design-system): 뱃지 컴포넌트 #30
jllee000 merged 14 commits intodevelopfrom
feat/#18/badge-component

Conversation

@jllee000
Copy link
Collaborator

@jllee000 jllee000 commented Aug 25, 2025

📌 Related Issues

관련된 Issue를 태그해주세요. (e.g. - close #25)

✅ 체크 리스트

  • PR 제목의 형식을 잘 작성했나요? e.g. Feat(client): PR 템플릿 작성
  • 빌드가 성공했나요? (pnpm build)

📄 Tasks

  • 뱃지 비활성, 활성 분기 작업

⭐ PR Point (To Reviewer)

  • cva 활용해서, 활성 상태에 따른 스타일링 분기했어요
  • 토글 형식이 아니라, 딱 한번 클릭 시에 활성되는 구조라 (default: false -> 클릭시 true로 변경 끝)

📷 Screenshot

image

Summary by CodeRabbit

  • 신기능
    • 배지(Badge) 컴포넌트를 추가해 텍스트와 선택적 카운트를 표시하고 클릭 시 상태 변화를 제공합니다.
  • 문서
    • 배지 컴포넌트의 스토리와 자동 문서화, 상호작용 데모(클릭 상태 미리보기) 예제를 추가했습니다.
  • 작업(Chores)
    • 스토리북과 주요 애드온을 통합하고 실행/빌드 스크립트를 추가했습니다.
    • 스토리북 기반의 Vitest/Playwright 브라우저 테스트 환경을 설정했습니다.
    • 워크스페이스 의존성 정리 및 내부 모듈 별칭을 정돈했습니다.
  • 스타일
    • 사소한 코드 포매팅을 정리했습니다.

@jllee000 jllee000 self-assigned this Aug 25, 2025
@jllee000 jllee000 added the feat 기능 개발하라 개발 달려라 달려 label Aug 25, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 25, 2025

Warning

Rate limit exceeded

@jllee000 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 22 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between fe509d6 and a0b21d8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • apps/client/src/App.tsx (0 hunks)
  • packages/design-system/src/components/index.ts (1 hunks)

Walkthrough

디자인 시스템에 Badge 컴포넌트를 추가하고, Storybook/Vitest 브라우저 테스트 환경을 프로젝트 루트에 도입했다. 디자인 시스템의 경로 별칭을 @lib로 정비하며 Switch 컴포넌트의 import를 갱신했다. 루트/앱 패키지 설정을 정리하고, 스토리 및 테스트 설정과 스크립트를 추가했다.

Changes

Cohort / File(s) Summary
Design-system: 신규 Badge 컴포넌트 및 공개 API 추가
packages/design-system/src/components/badge/Badge.tsx, packages/design-system/src/components/badge/Badge.stories.tsx, packages/design-system/src/components/index.ts
Badge 컴포넌트 추가(상태 포함), 스토리북 스토리 4종 및 인터랙션(play) 추가, 컴포넌트 배럴에 Badge 공개 export 추가.
Design-system: 경로 별칭 정비
packages/design-system/tsconfig.json, packages/design-system/vite.config.ts, packages/design-system/src/components/switch/Switch.tsx
@lib 별칭 신설(+tsconfig paths), Vite alias에서 @utils 제거 및 @lib 추가, Switch 컴포넌트의 경로 import를 @lib로 변경.
Storybook 설정 도입
.storybook/main.ts, .storybook/preview.ts, .storybook/vitest.setup.ts
React-Vite용 Storybook 설정, Preview 파라미터/애드온 설정, Vitest용 프로젝트 어노테이션 연결.
Vitest 브라우저 테스트 설정
vitest.config.ts, vitest.shims.d.ts
Storybook Vitest 플러그인 기반 브라우저(Playwright/Chromium) 테스트 프로젝트 구성 및 타입 셰임 추가.
루트 패키지 설정(툴링 확장)
package.json
Storybook 스크립트 추가 및 관련 devDependencies(스토리북/테스트 툴링) 추가.
앱/디자인 시스템 패키지 정리
apps/client/package.json, packages/design-system/package.json
devDependencies에서 @types/react, @types/react-dom 정리(루트로 이동), 워크스페이스 의존성/플러그인 항목 정렬.
클라이언트 소소한 정리
apps/client/src/App.tsx
불필요 공백 제거(포매팅).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as User
  participant Badge as Badge Component
  participant React as React State

  User->>Badge: Click
  Note over Badge: onClick 핸들러
  Badge->>React: setState(isClick = true)
  React-->>Badge: Re-render with state
  Badge->>User: 텍스트/배경 스타일 변형 적용
  Note over Badge: cva variant로 스타일 분기
Loading
sequenceDiagram
  autonumber
  participant Dev as Dev
  participant SB as Storybook (Vite)
  participant VT as Vitest
  participant PW as Playwright (Chromium)
  participant DS as Design System

  Dev->>SB: 스토리 실행 (storybook dev)
  Dev->>VT: 테스트 실행 (vitest - project "storybook")
  VT->>SB: storybookTest 플러그인 초기화
  VT->>PW: 브라우저 세션 시작 (headless)
  PW->>DS: 스토리 렌더(컴포넌트)
  Note over PW,DS: .storybook/vitest.setup.ts 어노테이션 적용
  VT-->>Dev: 테스트 결과 보고
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
디자인 시스템에 Progress Bar 컴포넌트 추가 및 스타일 분기 처리 (#25, #24) Progress 관련 파일/코드가 없음.
Progress 컴포넌트 접근성 속성 및 값 제한(0–100) 구현 (#25) 접근성/값 제한 로직 확인 불가.
Progress 스토리북 스토리, 인터랙션 테스트 추가 (#25) Progress 스토리/테스트 추가 내역 없음.
@radix-ui/react-progress 의존성 추가 (#25) 해당 의존성 추가 내역 없음.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Badge 컴포넌트 추가 (packages/design-system/src/components/badge/Badge.tsx; new file) 링크된 이슈는 Progress Bar 구현 관련이며 Badge는 범위 밖임.
Badge 스토리 추가 (packages/design-system/src/components/badge/Badge.stories.tsx; new file) Progress 관련 스토리가 아닌 Badge 스토리로, 이슈 범위를 벗어남.
Storybook/Vitest 설정 및 브라우저 테스트 구성 도입 (.storybook/*, vitest.config.ts, vitest.shims.d.ts; new/updated files) 이슈에 툴링 도입 요구사항이 명시되지 않음.
경로 별칭 변경(@lib) 및 Switch import 경로 수정 (packages/design-system/tsconfig.json, packages/design-system/vite.config.ts, packages/design-system/src/components/switch/Switch.tsx) Progress 컴포넌트와 직접적으로 관련된 변경이 아님.

Possibly related PRs

Suggested labels

🛠️ Feature, frontend

Suggested reviewers

  • jjangminii
  • constantly-dev
  • karnelll

Poem

퐁당! 뱃지가 반짝, 클릭에 귀가 쫑긋 🐰
스토리북 문 열고 테스트는 살금살금—슝!
@lib 길 따라 컴포넌트들 줄 맞춰 서고,
바람처럼 도는 크로뮴의 밤.
오늘도 토끼는 말해요: “빌드, 통과!” ✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#18/badge-component

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@jllee000 jllee000 changed the title [Feature] 뱃지 컴포넌트 feat: 뱃지 컴포넌트 Aug 25, 2025
@jllee000 jllee000 changed the title feat: 뱃지 컴포넌트 Feat(design-system): 뱃지 컴포넌트 Aug 25, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (7)
packages/design-system/tsconfig.json (1)

1-21: 전역 타입 사용 전환에 맞춰 typeRoots 제한 해제 권장

현재 파일 상단의 "typeRoots": ["node_modules/@types"] 설정은 타입 검색 경로를 로컬 패키지로만 제한합니다. 루트로 타입을 승격시켰으므로 기본 TS 해석(상위 디렉터리 node_modules/@types까지 탐색)을 활용하도록 typeRoots 제거를 제안합니다.

아래는 참고용 JSON 스니펫입니다(변경은 선택):

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@lib/*": ["./src/lib/*"]
    },
    "jsx": "react-jsx"
  }
}
apps/client/package.json (1)

18-22: @pinback/design-system: 런타임 의존성 이동 및 peerDependencies 정의 필요

앱에서 디자인시스템 컴포넌트를 직접 import하고 있으므로, apps/client/package.jsondevDependencies에서 dependencies로 반드시 옮겨주세요.
또한, packages/design-system/package.json에는 현재 peerDependencies가 정의되어 있지 않으므로, React 및 React-DOM 19 계열을 peerDependencies에 추가해 주시기 바랍니다.

· 앱 클라이언트 의존성 이동 (apps/client/package.json)

--- a/apps/client/package.json
+++ b/apps/client/package.json
@@ -14,13 +14,14 @@
   "react": "^19.1.1",
   "react-dom": "^19.1.1"
 },
 "dependencies": {
+  "@pinback/design-system": "workspace:*",
   "react": "^19.1.1",
   "react-dom": "^19.1.1"
 },
 "devDependencies": {
-  "@pinback/design-system": "workspace:*",
   "@eslint/js": "^9.33.0",
   "@pinback/eslint-config": "workspace:*",
   "@pinback/tailwind-config": "workspace:*",
   "@pinback/typescript-config": "workspace:*",

· 디자인시스템 패키지 peerDependencies 추가 (packages/design-system/package.json)

--- a/packages/design-system/package.json
+++ b/packages/design-system/package.json
@@ -X,Y +X,Y @@
   "name": "@pinback/design-system",
   "version": "…",
+  "peerDependencies": {
+    "react": "^19.1.1",
+    "react-dom": "^19.1.1"
+  },
   // 기존 필드들…
packages/design-system/src/components/switch/switch.tsx (1)

10-23: Tailwind 클래스 중복(두 개의 높이) — 마지막 선언만 적용됨

SwitchPrimitive.RootclassNameh-[1.15rem] h-[2rem]가 동시에 존재합니다. Tailwind 특성상 뒤쪽 h-[2rem]만 적용되어 혼동을 줄 수 있습니다. 의도된 크기만 남기거나, 크기 변형이 목적이면 variant/size prop으로 분기하는 것을 권장합니다.

가능한 수정 예:

- '... inline-flex h-[1.15rem] h-[2rem] w-[4rem] shrink-0 ...'
+ '... inline-flex h-[2rem] w-[4rem] shrink-0 ...'
packages/design-system/src/components/badge/Badge.tsx (3)

3-6: Badge API 확장 제안: 제어/비제어 선택 상태, 스타일 확장성

DS 컴포넌트 특성상 외부 제어가 필요할 가능성이 높습니다. 다음 속성 추가를 고려해 주세요: selected?, defaultSelected?, onSelectedChange?, className?, countClassName?, disabled?. 이러면 내부 상태 강제/영구 선택 문제도 해소됩니다.

예시 타입 확장:

 export interface BadgeProps {
   text: string;
   countNum?: number;
+  selected?: boolean;              // 제어 모드
+  defaultSelected?: boolean;       // 비제어 초기값
+  onSelectedChange?: (v: boolean) => void;
+  className?: string;
+  countClassName?: string;
+  disabled?: boolean;
 }

추가로 내부 useStateselected/defaultSelected와 동기화하도록 리팩터 필요합니다. 원하시면 패치 제안 드리겠습니다.


39-41: 빈 카운트 뱃지 렌더 방지 및 표기 규칙(예: 99+) 적용

countNum가 없을 때 빈 뱃지 캡슐이 그려집니다. 정의된 값일 때만 렌더하고 큰 수는 축약 표기를 권장합니다.

다음 패치를 제안합니다:

-      <span className={BadgeStyleVariants({ click: isClick })}>{countNum}</span>
+      {countNum != null && (
+        <span className={BadgeStyleVariants({ click: isClick })}>
+          {countNum > 99 ? '99+' : countNum}
+        </span>
+      )}

7-17: 네이밍 정교화: variant 키 clickselected

상태 의미상 click보다는 selected가 자연스럽습니다. 스타일 호출부도 동일하게 갱신해 주세요.

제안 패치:

-const BadgeTxtStyleVariants = cva('sub3-b', {
+const BadgeTxtStyleVariants = cva('sub3-b', {
   variants: {
-    click: {
+    selected: {
       true: 'text-font-black-1',
       false: 'text-font-ltgray-4',
     } as const,
   },
   defaultVariants: {
-    click: false,
+    selected: false,
   },
 });
@@
-    variants: {
-      click: {
+    variants: {
+      selected: {
         true: 'bg-main500',
         false: 'bg-gray300',
       } as const,
     },
     defaultVariants: {
-      click: false,
+      selected: false,
     },
   }
 );
@@
-      <span className={BadgeTxtStyleVariants({ click: isClick })}>{text}</span>
-      <span className={BadgeStyleVariants({ click: isClick })}>{countNum}</span>
+      <span className={BadgeTxtStyleVariants({ selected: isClick })}>{text}</span>
+      <span className={BadgeStyleVariants({ selected: isClick })}>{countNum}</span>

Also applies to: 21-29, 39-41

packages/design-system/src/components/index.ts (1)

3-3: 배럴에 타입도 함께 노출 제안

소비자가 타입 재사용하기 편하도록 BadgeProps도 export 해 주세요.

제안 패치:

 export { default as Badge } from './badge/Badge';
+export type { BadgeProps } from './badge/Badge';
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3cbf52e and 19307b2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • apps/client/package.json (1 hunks)
  • apps/client/src/App.tsx (2 hunks)
  • package.json (1 hunks)
  • packages/design-system/package.json (0 hunks)
  • packages/design-system/src/components/badge/Badge.tsx (1 hunks)
  • packages/design-system/src/components/index.ts (1 hunks)
  • packages/design-system/src/components/switch/switch.tsx (1 hunks)
  • packages/design-system/tsconfig.json (1 hunks)
  • packages/design-system/vite.config.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/design-system/package.json
🔇 Additional comments (8)
package.json (1)

15-16: 디자인 시스템의 tsconfig.json typeRoots 설정 검토 및 조정 필요

packages/design-system/tsconfig.json에서

"compilerOptions": {
  
  "typeRoots": ["node_modules/@types"],
  
}

로 명시되어 있어, 패키지 루트(packages/design-system/node_modules/@types)에서만 타입 선언을 검색하게 됩니다. 모노레포 루트(node_modules/@types/@types/react)에 설치된 전역 React 타입을 참조하지 못해, 하위 패키지에서 React 관련 타입을 인식하지 못할 수 있습니다.

따라서 아래 중 하나로 조정할 것을 권장합니다.

  • typeRoots 옵션 제거 (기본값 사용 → 상위/루트 node_modules/@types까지 모두 참조)
  • 또는 모노레포 루트까지 포함하도록 경로 확장
     "compilerOptions": {
    -  "typeRoots": ["node_modules/@types"],
    +  "typeRoots": [
    +    "node_modules/@types",
    +    "../../node_modules/@types"
    +  ],
       …
     }

실제 전역 타입 해석이 제대로 동작하는지, 아래 커맨드로 로컬 환경(의존성 설치 완료 상태)에서 확인해 주세요:

# 디자인 시스템 패키지 루트로 이동 후
pnpm install          # 또는 npm/yarn install
pnpm tsc --noEmit     # 빌드 오류 확인

# 또는
rg -n --regexp '\bReact\.' packages/design-system
packages/design-system/tsconfig.json (1)

9-11: 경로 별칭(@lib) 추가는 적절 — Vite 별칭과 정확히 일치하도록 동기화 필요

"@lib/*": ["./src/lib/*"] 추가는 합리적입니다. 다만 Vite 설정에서 @lib가 절대경로(/src/lib)로 되어 있어 현재 TS 설정과 불일치합니다. Vite 쪽을 "src/lib"로 맞추지 않으면 런타임/번들 시 경로 해석이 깨질 가능성이 큽니다(아래 Vite 코멘트 참조).

packages/design-system/src/components/switch/switch.tsx (1)

3-3: 별칭 전환 자체는 적절 — Vite 별칭 수정 선행 필요

@/lib/utils@lib/utils 전환은 tsconfig 경로와 일치합니다. 다만 현재 Vite 별칭이 절대경로로 잘못 설정되어 있어(위 코멘트 참고) 빌드 실패 가능성이 큽니다. Vite 수정 후 이 변경은 정상 동작할 것입니다.

packages/design-system/src/components/badge/Badge.tsx (1)

1-2: 토큰/유틸 클래스 검증 권고

sub3-b, sub5-sb, text-font-*, bg-main500 등 커스텀 유틸이 Tailwind 설정(또는 토큰 레이어)에 존재하는지 확인 부탁드립니다. 런타임에 클래스를 문자열로 조합하므로 미정의 토큰은 빌드 타임엔 탐지되지 않습니다.

원하시면 저장소에서 해당 토큰 사용/정의를 스캔하는 스크립트를 제공해 드립니다.

apps/client/src/App.tsx (2)

20-20: 샘플 사용 👍

Badge 기본 사용 예로 충분합니다. 이후 제어형 API가 도입되면 selected/onSelectedChange 예제도 추가하면 좋겠습니다.


5-5: 확인 완료: @pinback/design-system/ui 경로가 exports에 정의되어 있습니다

design-system 패키지의 package.json exports"./ui": "./src/components/index.ts" 엔트리가 명시되어 있어
import { Badge } from '@pinback/design-system/ui';는 배포 시에도 정상 작동합니다.
추가 조치 불필요합니다.

packages/design-system/src/components/index.ts (2)

3-3: PR 목적과 변경 내용 불일치 확인 필요

연결된 이슈(#25)는 Progress 컴포넌트 구현인데, 본 PR은 Badge 추가입니다. 이슈 링크/PR 제목/설명 정합성을 맞춰 주세요.

원하시면 체크리스트/설명 템플릿을 자동 보정하는 가이드를 드리겠습니다.


3-3: 배럴 업데이트 자체는 적절합니다

UI 퍼블릭 표면에 Badge를 추가한 점 문제 없습니다.

Comment on lines +18 to +20
const BadgeStyleVariants = cva(
'text-white-bg sub5-sb rounded-[0.4rem] px-[0.8rem] py-[0.4rem]',
{
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

오타 추정: text-white-bgtext-white

Tailwind/DS 네이밍 관례상 text-white-bg는 비정상입니다. 베이스에 텍스트 색만 두고 배경은 variant에서 제어 중이므로 text-white가 맞습니다.

다음 패치를 제안합니다:

-const BadgeStyleVariants = cva(
-  'text-white-bg sub5-sb rounded-[0.4rem] px-[0.8rem] py-[0.4rem]',
+const BadgeStyleVariants = cva(
+  'text-white sub5-sb rounded-[0.4rem] px-[0.8rem] py-[0.4rem]',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const BadgeStyleVariants = cva(
'text-white-bg sub5-sb rounded-[0.4rem] px-[0.8rem] py-[0.4rem]',
{
// packages/design-system/src/components/badge/Badge.tsx
const BadgeStyleVariants = cva(
'text-white sub5-sb rounded-[0.4rem] px-[0.8rem] py-[0.4rem]',
{
/* variants… */
}
);
🤖 Prompt for AI Agents
In packages/design-system/src/components/badge/Badge.tsx around lines 18 to 20,
the base cva class string contains a typo "text-white-bg" which should be
"text-white"; update the class list to replace "text-white-bg" with "text-white"
so text color is applied in the base and background variants remain controlled
by the variant definitions.

Comment on lines +34 to +41
return (
<div
className="flex cursor-pointer items-center justify-center gap-[0.8rem]"
onClick={() => setIsClick(true)}
>
<span className={BadgeTxtStyleVariants({ click: isClick })}>{text}</span>
<span className={BadgeStyleVariants({ click: isClick })}>{countNum}</span>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

클릭 가능한 요소의 접근성/키보드 조작 개선 (divbutton)

현재 div에 onClick만 있어 스크린리더/키보드 접근성이 떨어집니다. 토글 동작도 없고 한 번 true가 되면 해제 불가합니다. 버튼 요소와 aria-pressed를 사용하고 토글 처리로 개선하는 것이 좋습니다.

다음 패치를 제안합니다:

-  return (
-    <div
-      className="flex cursor-pointer items-center justify-center gap-[0.8rem]"
-      onClick={() => setIsClick(true)}
-    >
+  return (
+    <button
+      type="button"
+      className="inline-flex items-center gap-[0.8rem] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-main500"
+      aria-pressed={isClick}
+      onClick={() => setIsClick((prev) => !prev)}
+    >
@@
-    </div>
+    </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return (
<div
className="flex cursor-pointer items-center justify-center gap-[0.8rem]"
onClick={() => setIsClick(true)}
>
<span className={BadgeTxtStyleVariants({ click: isClick })}>{text}</span>
<span className={BadgeStyleVariants({ click: isClick })}>{countNum}</span>
</div>
return (
- <div
- className="flex cursor-pointer items-center justify-center gap-[0.8rem]"
- onClick={() => setIsClick(true)}
<button
type="button"
className="inline-flex items-center gap-[0.8rem] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-main500"
aria-pressed={isClick}
onClick={() => setIsClick((prev) => !prev)}
>
<span className={BadgeTxtStyleVariants({ click: isClick })}>{text}</span>
<span className={BadgeStyleVariants({ click: isClick })}>{countNum}</span>
</button>
);

Comment on lines 27 to 29
'@': path.resolve(__dirname, 'src'),
'@utils': path.resolve(__dirname, './src/utils'),
'@lib': path.resolve(__dirname, '/src/lib'),
},
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

중대: @lib 별칭이 절대경로(/src/lib)로 설정됨 — 빌드/스토리북 경로 해석 오류 유발

path.resolve(__dirname, '/src/lib')는 루트 절대경로로 해석됩니다. 로컬 소스에 맞춰 상대 경로 'src/lib'를 사용해야 TS paths와 일관성이 맞고, 번들/테스트에서도 정상 동작합니다.

아래 패치 적용을 권장합니다.

 resolve: {
   alias: {
     '@': path.resolve(__dirname, 'src'),
-    '@lib': path.resolve(__dirname, '/src/lib'),
+    '@lib': path.resolve(__dirname, 'src/lib'),
   },
 },

적용 후 @lib/utils import가 정상적으로 해석되는지 스토리북/빌드를 통해 확인해 주세요.

다음 스크립트로 최소한의 정적 확인을 할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# @lib 사용처 나열 및 실존 파일 확인
rg -nP --type=ts --type=tsx "\@lib/" packages/design-system | sed -n '1,100p'
test -d packages/design-system/src/lib && echo "src/lib 디렉터리 존재" || echo "src/lib 없음"

Length of output: 187


중대한 경로 해석 오류: /src/libsrc/lib로 수정 필요

  • 파일: packages/design-system/vite.config.ts
    대상 라인: 27–29
  • 현상:
    • path.resolve(__dirname, '/src/lib') 호출 시 최상위 루트(/src/lib)로 해석되어 실제 코드 위치(packages/design-system/src/lib)를 가리키지 않습니다.
    • 스크립트 실행 결과 packages/design-system/src/lib 디렉터리가 존재함이 확인되었습니다.
  • 영향:
    • @lib/... import가 빌드 및 스토리북에서 올바르게 해석되지 않아 모듈을 찾지 못하는 오류가 발생합니다.

아래와 같이 패치 적용을 권장합니다:

 resolve: {
   alias: {
     '@': path.resolve(__dirname, 'src'),
-    '@lib': path.resolve(__dirname, '/src/lib'),
+    '@lib': path.resolve(__dirname, 'src/lib'),
   },
 },

적용 후 @lib/utils 등 모든 @lib/ import가 스토리북 및 번들 빌드에서 정상 해석되는지 반드시 확인해 주세요.

🤖 Prompt for AI Agents
In packages/design-system/vite.config.ts around lines 27–29, the alias for
'@lib' uses path.resolve(__dirname, '/src/lib') which resolves to the filesystem
root (/src/lib) instead of the package's src; change it to
path.resolve(__dirname, 'src/lib') (remove the leading slash) so it points to
packages/design-system/src/lib, then rebuild and verify all @lib/... imports
(e.g., @lib/utils) resolve correctly in Storybook and bundle builds.

@github-actions
Copy link

github-actions bot commented Aug 25, 2025

✅ Storybook chromatic 배포 확인:
🐿️ storybook

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (40)
packages/design-system/src/components/badge/Badge.stories.tsx (8)

3-3: 미사용 타입 import 제거로 린트 경고 해소

BadgeProps가 사용되지 않아 lint 경고가 발생합니다. 불필요한 import는 제거해주세요.

-import Badge, { type BadgeProps } from './Badge';
+import Badge from './Badge';

13-19: Controls UX 미세 개선: step 추가 및 테이블 요약 명시

Number 컨트롤에 step: 1을 추가해 조작성을 높이고, Docs 테이블에 타입 요약을 제공하는 것을 제안합니다.

     countNum: {
-      control: { type: 'number', min: 0 },
+      control: { type: 'number', min: 0, step: 1 },
       description: '카운트 숫자(옵션)',
+      table: { type: { summary: 'number | undefined' } },
     },

20-27: 문구 다듬기: 내부 구현 누설 최소화 및 문장부호 정리

Docs에 내부 상태명(isClick) 언급은 유지보수 시 문서/코드 불일치 리스크가 있습니다. 의미는 유지하면서 구현 세부는 숨기는 편이 좋습니다. 불필요한 .!도 정리합니다.

-          '클릭 시 내부 state(isClick)가 true로 바뀌며 스타일이 활성화됩니다. 토글 방식은 아닙니다.!',
+          '클릭 시 활성 상태로 바뀌며 스타일이 적용됩니다. 토글 방식은 아닙니다.',

35-37: undefined 명시 대신 속성 생략 권장

Storybook args에선 선택적 프로퍼티는 생략하는 편이 일반적입니다. 의미 동일하며 UI도 더 깔끔합니다.

 export const NoCount: Story = {
-  args: { text: '카운트 없음', countNum: undefined },
+  args: { text: '카운트 없음' },
 };

39-41: 대용량 카운트 시나리오에 대한 Docs 보강

Overflow 표기 정책(예: 999+)을 문서화하면 디자인 의도가 명확해집니다.

 export const LargeCount: Story = {
   args: { text: '메시지', countNum: 12000 },
+  parameters: {
+    docs: {
+      description: { story: '매우 큰 카운트 값을 전달했을 때의 표시 방식(예: 999+ 등)을 확인합니다.' },
+    },
+  },
 };

43-48: play 함수: 접근성 쿼리와 상호작용 검증 고려

현재 텍스트 매칭 클릭은 동작은 하지만, 인터랙티브 역할(버튼 등)을 갖는다면 role 기반 쿼리가 더 견고합니다. 또한 클릭 후 상태 변화(예: aria-pressed, data-state 등)를 가볍게 검증하면 회귀 방지에 도움됩니다. 컴포넌트 역할/속성 노출 여부에 따라 적용을 검토해주세요.

예시(컴포넌트가 버튼 역할을 노출한다면):

-  play: async ({ canvasElement, args }) => {
-    const canvas = within(canvasElement);
-    await userEvent.click(await canvas.findByText(String(args.text)));
-  },
+  play: async ({ canvasElement, args }) => {
+    const canvas = within(canvasElement);
+    const btn = await canvas.findByRole('button', { name: String(args.text) });
+    await userEvent.click(btn);
+    // TODO: aria-pressed 또는 data-state 등이 있다면 아래와 같이 검증 추가
+    // await expect(btn).toHaveAttribute('aria-pressed', 'true');
+  },

확인 요청:

  • Badge가 키보드로도 활성화 가능한지(Tab/Enter/Space) 확인 바랍니다. 접근성 요구가 있다면 play에 키보드 상호작용도 추가하는 것을 권장합니다.

1-55: PR 메타와 변경 내용 불일치 가능성: 이슈 연결 상태 점검

현재 PR은 Badge 컴포넌트(스토리 포함) 추가인데, 연결된 이슈(#25)는 Progress 컴포넌트 구현 목적입니다. 의도된 링크인지 확인 부탁드립니다. 잘못 연결된 경우 PR 설명/이슈 링크 정정 및 스크린샷/체크리스트 업데이트를 권장합니다.

원하시면 PR 템플릿 보강 초안을 드리겠습니다(사용 예, 접근성 요건, 디자인 토큰 참조, 스크린샷 섹션 등).


5-28: 타입 추론 강화: satisfies 패턴 적용 검증 완료

프로젝트에서 사용 중인 TypeScript 버전(5.9.2)이 4.9 이상을 충족하므로, Storybook Meta 객체에 satisfies 키워드를 안전하게 도입할 수 있습니다. 이를 통해 제네릭 선언(Meta<typeof Badge>)을 제거해도 속성 추론을 유지하면서 타입 안전성을 확보하고, StoryObj<typeof meta>를 사용해 컴포넌트 교체 시 제네릭 변경을 줄일 수 있습니다.

적용 예시 (packages/design-system/src/components/badge/Badge.stories.tsx, 5–28, 31 라인):

-const meta: Meta<typeof Badge> = {
+const meta = {
   title: 'UI/Badge',
   component: Badge,
   tags: ['autodocs'],
   args: {
     text: '알림',
     countNum: 3,
   },
   argTypes: {
     text: { control: 'text', description: '뱃지 라벨 텍스트' },
     countNum: {
       control: { type: 'number', min: 0 },
       description: '카운트 숫자(옵션)',
     },
   },
   parameters: {
     docs: {
       description: {
         component:
           '클릭 시 내부 state(isClick)가 true로 바뀌며 스타일이 활성화됩니다. 토글 방식은 아닙니다.!',
       },
     },
   },
-};
+} satisfies Meta<typeof Badge>;

-type Story = StoryObj<typeof Badge>;
+type Story = StoryObj<typeof meta>;

이 변경은 선택적 리팩터로, 필요에 따라 적용 가능합니다.

vitest.shims.d.ts (1)

1-1: 전역 타입 주입 범위 제한 권장

현재 워크스페이스 루트와 packages/design-system 패키지에 위치한 두 개의 vitest.shims.d.ts 파일이

/// <reference types="@vitest/browser/providers/playwright" />

를 통해 Playwright/Vitest 타입을 전역으로 주입하고 있습니다. 이로 인해 앱 내부 타입 체킹 단계에 테스트 전용 타입이 누수될 수 있으므로, 테스트 전용으로 범위를 한정할 것을 권장합니다.

수정 대상 파일

  • vitest.shims.d.ts (루트)
  • packages/design-system/vitest.shims.d.ts
  • (참고) packages/design-system/vite.config.ts 2행: /// <reference types="vitest/config" />

권장 옵션

  • 옵션 A: 테스트 전용 tsconfig (vitest.tsconfig.json) 추가 후, Vitest 실행 시 --config vitest.tsconfig.json 으로 지정
    {
      "extends": "./tsconfig.json",
      "compilerOptions": {
        "types": ["@vitest/browser/providers/playwright"]
      },
      "include": [
        "**/*.test.ts",
        "**/*.test.tsx",
        "**/*.stories.tsx",
        ".storybook/vitest.setup.ts"
      ]
    }
  • 옵션 B: 위 *.shims.d.ts 파일들을 tests/ 또는 .storybook/ 등 테스트 전용 디렉터리로 이동하고, tsconfig의 include 설정에만 포함되도록 구성

현재 tsconfig 및 triple-slash 참조 위치 확인 스크립트

#!/bin/bash
# 루트 및 패키지 tsconfig에서 types/include/files 설정 점검
fd -a -t f -g "tsconfig*.json" | while read -r f; do
  echo "---- $f ----"
  jq '.compilerOptions.types, .include, .files' "$f" 2>/dev/null || cat "$f"
done

# 모든 triple-slash 타입 참조 검색
rg -nP --glob '!node_modules' --glob '!*dist*' '^///\s*<reference\s+types='
.storybook/preview.ts (1)

1-1: Preview 타입 import 경로를 '@storybook/react'로 조정 권장

SB v8/v9 기준 Preview 타입은 일반적으로 '@storybook/react'에서 제공되고, '@storybook/react-vite'는 주로 StorybookConfig 타입을 노출합니다. 현 경로에서도 동작할 수는 있으나, 향후 타입 호환성 측면에서 '@storybook/react'로 맞추는 편이 안전합니다.

-import type { Preview } from '@storybook/react-vite';
+import type { Preview } from '@storybook/react';
package.json (1)

10-12: 스토리북/브라우저 테스트 스크립트 보강 제안

CI와 로컬 확인을 위해 스토리북-스토리 기반 브라우저 테스트 스크립트를 추가해 두면 편리합니다.

   "check-types": "turbo run check-types",
   "test": "turbo run test",
   "storybook": "storybook dev -p 6006",
-  "build-storybook": "storybook build"
+  "build-storybook": "storybook build",
+  "test:ui": "vitest --browser",
+  "test:ui:coverage": "vitest --browser --coverage"
.storybook/main.ts (3)

9-11: getAbsolutePath 반환 타입을 string으로 명시

암시적 any는 추후 TS 설정 강화 시 에러가 될 수 있습니다. 반환 타입을 명시해 주세요.

-function getAbsolutePath(value: string): any {
+function getAbsolutePath(value: string): string {
   return dirname(require.resolve(join(value, 'package.json')))
 }

13-16: 스토리 검색 범위를 패키지 스토리까지 확장 제안

디자인 시스템 패키지 내부의 스토리까지 자동 수집하려면 글롭을 확장하는 편이 편리합니다.

   "stories": [
-    "../stories/**/*.mdx",
-    "../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)"
+    "../stories/**/*.mdx",
+    "../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
+    "../packages/**/src/**/*.mdx",
+    "../packages/**/src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
   ],

17-22: 애드온 구성 보완 검토(선택)
기본 액션/컨트롤/백그라운드/뷰포트 등을 한 번에 활성화하려면 @storybook/addon-essentials 사용을 고려해 볼 수 있습니다(중복 추가 시 Storybook이 내부적으로 중복 제거). 현재 요구사항에 충분하다면 유지하셔도 됩니다.

   "addons": [
     getAbsolutePath('@chromatic-com/storybook'),
     getAbsolutePath('@storybook/addon-docs'),
     getAbsolutePath("@storybook/addon-a11y"),
-    getAbsolutePath("@storybook/addon-vitest")
+    getAbsolutePath("@storybook/addon-vitest"),
+    getAbsolutePath("@storybook/addon-essentials")
   ],

tsconfig Node 타입 확인 요청

  • .storybook 디렉터리에 별도의 tsconfig.json 파일이 없습니다.
  • 앱 레벨(예: apps/client/tsconfig.json)에도 "types" 설정이 누락되어 있습니다.
  • Storybook 설정 파일(.storybook/main.ts) 내에서 require.resolve를 사용할 경우 Node 전역 타입이 필요합니다.
    → Storybook이 참조하는 tsconfig에 아래와 같이 "types": ["node"]를 명시하거나,
    .storybook/tsconfig.json을 생성하여 Node 타입을 포함시키는지 검토해 주세요.
stories/button.css (1)

1-9: 키보드 포커스 가시성 및 disabled 상태 스타일 추가 권장

접근성 관점에서 포커스 링과 비활성화 상태 스타일을 명시해 두면 좋습니다. 또한 disabled일 때 커서가 pointer로 유지되는 것도 피하는 편이 낫습니다.

 .storybook-button {
   display: inline-block;
   cursor: pointer;
   border: 0;
   border-radius: 3em;
   font-weight: 700;
   line-height: 1;
   font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
 }
+
+.storybook-button:focus-visible {
+  outline: 2px solid currentColor;
+  outline-offset: 2px;
+}
+
+.storybook-button:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
stories/Configure.mdx (4)

1-1: Storybook MDX v2/8 권장사항: Meta import 경로 최신화

구버전 경로("@storybook/addon-docs/blocks") 대신 최신 권장 경로("@storybook/blocks")를 사용하면 호환성과 타입 안정성이 좋아집니다.

아래와 같이 변경을 제안합니다:

-import { Meta } from "@storybook/addon-docs/blocks";
+import { Meta } from '@storybook/blocks';

37-41: 시맨틱 마크업: 제목은 실제 헤딩 태그로 표기

div 내부에 # Configure your project 텍스트 대신 h2를 사용하면 접근성과 문서 구조가 개선됩니다. 본문도 p 태그로 감싸는 것을 권장합니다.

-  <div className='sb-section-title'>
-    # Configure your project
-
-    Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community.
-  </div>
+  <div className='sb-section-title'>
+    <h2>Configure your project</h2>
+    <p>
+      Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup.
+      Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you
+      can ask for help from our community.
+    </p>
+  </div>

83-88: 시맨틱 마크업: 두 번째 섹션 제목도 h2 사용

두 번째 섹션도 동일한 방식으로 h2 태그 사용을 권장합니다.

-  <div className='sb-section-title'>
-    # Do more with Storybook
-
-    Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs.
-  </div>
+  <div className='sb-section-title'>
+    <h2>Do more with Storybook</h2>
+    <p>
+      Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list
+      is just to get you started. You can customise Storybook in many ways to fit your needs.
+    </p>
+  </div>

221-224: 스타일 범위 축소: 전역 img 셀렉터로 인한 부작용 가능성

img { object-fit: cover; }는 페이지 전역 이미지에 적용됩니다. 본 문서 블록으로 범위를 한정하세요.

-  img {
+  .sb-container img,
+  .sb-addon-img img,
+  .sb-socials img {
     object-fit: cover;
   }
.storybook/vitest.setup.ts (1)

2-2: Framework 패키지 import 통일 검토: @storybook/react-vite@storybook/react (선택적 리팩토링)

현재 .storybook 디렉터리 내에서 네 곳에서 @storybook/react-vite를 import 하고 있습니다. 공식 가이드에서는 프레임워크 패키지를 @storybook/react로 통일할 것을 권장하므로, 버전 업/마이그레이션 시 호환성 이슈를 줄이기 위해 변경을 검토해 보시기 바랍니다.

  • .storybook/vitest.setup.ts: setProjectAnnotations
  • .storybook/preview.ts: Preview 타입
  • .storybook/main.ts: StorybookConfig 타입
  • .storybook/main.ts: getAbsolutePath 인자

변경 예시:

--- a/.storybook/vitest.setup.ts
+++ b/.storybook/vitest.setup.ts
-import { setProjectAnnotations } from '@storybook/react-vite';
+import { setProjectAnnotations } from '@storybook/react';
--- a/.storybook/preview.ts
+++ b/.storybook/preview.ts
-import type { Preview } from '@storybook/react-vite';
+import type { Preview } from '@storybook/react';
--- a/.storybook/main.ts
+++ b/.storybook/main.ts
-import type { StorybookConfig } from '@storybook/react-vite';
+import type { StorybookConfig } from '@storybook/react';-    "name": getAbsolutePath('@storybook/react-vite'),
+    "name": getAbsolutePath('@storybook/react'),

검증 스크립트:

#!/bin/bash
# 숨김 디렉터리 포함하여 Storybook import 현황 확인
rg --hidden -n "@storybook/react-vite|@storybook/react" -g ".storybook/**" -S
stories/Page.tsx (3)

11-11: 초깃값 명시 및 타입 일관성(선택 사항)

undefined 대신 null을 사용하면 의도된 "비로그인" 상태를 더 분명히 표현할 수 있습니다.

-  const [user, setUser] = React.useState<User>();
+  const [user, setUser] = React.useState<User | null>(null);

10-10: React.FC 사용 여부 정리(선택 사항)

React.FC는 암묵적 children 포함 등 논쟁 요소가 있어 단순 함수 선언을 권장하는 코드베이스도 많습니다. 팀 컨벤션에 맞춰 통일해 주세요.

-export const Page: React.FC = () => {
+export const Page = () => {

59-67: 팁 아이콘 SVG 접근성 마무리

장식용 아이콘이면 스크린리더에서 숨기는 것이 좋습니다.

-          <svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
+          <svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
stories/Page.stories.ts (2)

1-1: Storybook 타입 import 경로 정렬

타입은 보통 @storybook/react에서 가져옵니다. 현재도 동작할 수 있으나, 빌더/프레임워크 분리 관점에서 타입은 프레임워크 패키지로 통일하는 편이 안전합니다.

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

23-33: 상호작용 테스트 안정성 개선

클릭 후 동일 DOM 노드 참조에 대한 존재 여부 검증 대신, 쿼리를 다시 수행해 확인하면 안정적입니다.

   play: async ({ canvasElement }) => {
     const canvas = within(canvasElement);
     const loginButton = canvas.getByRole('button', { name: /Log in/i });
-    await expect(loginButton).toBeInTheDocument();
+    await expect(loginButton).toBeInTheDocument();
     await userEvent.click(loginButton);
-    await expect(loginButton).not.toBeInTheDocument();
+    await expect(canvas.queryByRole('button', { name: /Log in/i })).not.toBeInTheDocument();
 
     const logoutButton = canvas.getByRole('button', { name: /Log out/i });
     await expect(logoutButton).toBeInTheDocument();
   },
stories/Header.stories.ts (1)

1-1: Storybook 타입 import 경로 정렬

여기도 타입을 @storybook/react에서 가져오도록 통일하는 편을 권장합니다.

-import type { Meta, StoryObj } from '@storybook/react-vite';
+import type { Meta, StoryObj } from '@storybook/react';
stories/page.css (3)

3-8: 타이포/스페이싱을 디자인 토큰·rem 기반으로 통일 제안

px·하드코딩 색상 대신 DS 토큰과 rem 비율을 사용하면 테마/접근성 대응과 일관성이 좋아집니다.

아래와 같이 최소 변경을 제안합니다(토큰 미정 시 우측의 폴백 사용):

-  padding: 48px 20px;
-  max-width: 600px;
-  color: #333;
-  font-size: 14px;
-  line-height: 24px;
+  padding: var(--space-12, 3rem) var(--space-5, 1.25rem);
+  max-width: 37.5rem;
+  color: var(--color-fg-muted, #333);
+  font-size: var(--font-size-body, 0.875rem);
+  line-height: var(--line-height-body, 1.714);

토큰 정의가 없다면, 아래처럼 공용 레벨에 추가하는 것을 권장합니다(별도 파일 혹은 Storybook preview 스타일):

:root {
  --space-5: 1.25rem;
  --space-12: 3rem;
  --color-fg-muted: #333;
  --font-size-body: 0.875rem;
  --line-height-body: 1.714;
  --color-accent: #1ea7fd;
}

57-64: SVG 채색은 currentColor 사용으로 테마 친화성 향상

path에 고정 색상을 지정하기보다 currentColor를 쓰고 상위 요소에 색을 지정하면 테마 일관성이 좋아집니다.

 .storybook-page .tip-wrapper svg {
   display: inline-block;
   vertical-align: top;
   margin-top: 3px;
   margin-right: 4px;
   width: 12px;
   height: 12px;
+  color: var(--color-accent, #1ea7fd);
 }
 
 .storybook-page .tip-wrapper svg path {
-  fill: #1ea7fd;
+  fill: currentColor;
 }

Also applies to: 66-68


24-26: 링크 포커스/호버 가시성 보강 제안

현재는 색상 상속만 있어 키보드 포커스 가시성이 약합니다. Storybook 페이지 용도라면 아래 보강을 권장합니다.

.storybook-page a {
  color: inherit;
  text-decoration: underline;
}

.storybook-page a:hover {
  color: var(--color-accent, #1ea7fd);
}

.storybook-page a:focus-visible {
  outline: 2px solid var(--color-accent, #1ea7fd);
  outline-offset: 2px;
}
stories/Header.tsx (3)

42-45: 시맨틱 강조는 대신 사용 권장

는 단순 시각 강조, 는 의미 강조입니다. 환영 문구에는 strong이 더 적합합니다.

-            <span className="welcome">
-              Welcome, <b>{user.name}</b>!
-            </span>
+            <span className="welcome">
+              Welcome, <strong>{user.name}</strong>!
+            </span>

10-15: 브랜딩/문구 하드코딩 제거로 재사용성·i18n 대비

디자인 시스템 관점에서 'Acme' 같은 고정 문구는 props로 추출하는 편이 좋습니다.

 export interface HeaderProps {
   user?: User;
   onLogin?: () => void;
   onLogout?: () => void;
   onCreateAccount?: () => void;
+  brand?: string;
 }
 
-export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => (
+export const Header = ({ user, onLogin, onLogout, onCreateAccount, brand = 'Acme' }: HeaderProps) => (
 ...
-        <h1>Acme</h1>
+        <h1>{brand}</h1>

원하시면 로그인/로그아웃 문구도 props(i18n)로 분리한 패치 제안 드리겠습니다.

Also applies to: 17-17, 37-37


45-51: 콜백 미지정 시 버튼 비활성화로 의도 명확화

onLogin/onCreateAccount/onLogout이 없을 때 버튼을 disabled 처리하면 UX 오해를 줄일 수 있습니다.

-            <Button size="small" onClick={onLogout} label="Log out" />
+            <Button size="small" onClick={onLogout} disabled={!onLogout} label="Log out" />
...
-            <Button size="small" onClick={onLogin} label="Log in" />
-            <Button primary size="small" onClick={onCreateAccount} label="Sign up" />
+            <Button size="small" onClick={onLogin} disabled={!onLogin} label="Log in" />
+            <Button primary size="small" onClick={onCreateAccount} disabled={!onCreateAccount} label="Sign up" />
vitest.config.ts (1)

32-33: setupFiles는 절대경로 사용 권장(CWD 의존 제거)

CI/IDE 실행 위치가 달라도 안전하도록 dirname 기반으로 해주세요.

-          setupFiles: ['.storybook/vitest.setup.ts'],
+          setupFiles: [path.join(dirname, '.storybook', 'vitest.setup.ts')],
stories/Button.stories.ts (2)

8-17: Story 카테고리 네이밍을 DS 규칙에 맞게 정리 제안

Example/Button 대신 Components/Button 같이 제품 내 탐색 체계에 맞추면 스토리 탐색성이 좋아집니다.

-  title: 'Example/Button',
+  title: 'Components/Button',

29-34: 상호작용 테스트(play) 추가로 Vitest 연동 가치 극대화

Primary 스토리에 간단한 상호작용 테스트를 추가하면 Vitest+Storybook 통합이 실제 가치(회귀 방지)를 가집니다.

예시(참고용, import 보강 필요):

// 상단에 추가:
// import { expect, userEvent, within } from '@storybook/test';

export const Primary: Story = {
  args: { primary: true, label: 'Button' },
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);
    const btn = await canvas.findByRole('button', { name: args.label });
    await userEvent.click(btn);
    // onClick은 meta.args로 fn()이 주입되어 있음
    expect(args.onClick).toHaveBeenCalled();
  },
};

원하시면 필요한 import/타입까지 포함한 패치를 만들어 드리겠습니다.

stories/header.css (2)

6-7: 스페이싱/타이포 rem·토큰화 제안

px와 하드코딩 값을 토큰/상대 단위로 전환하면 테마 일관성과 접근성이 향상됩니다.

-  padding: 15px 20px;
+  padding: var(--space-4, 1rem) var(--space-5, 1.25rem);
...
-  font-size: 20px;
+  font-size: var(--font-size-h4, 1.25rem);

Also applies to: 20-21


1-8: 다크 모드 대비 색상 토큰 사용 제안

기본 텍스트 색(#333)과 보더 rgba는 다크 모드에서 대비가 부족할 수 있습니다. 토큰으로 전환하고 prefers-color-scheme에 대응을 권장합니다.

예시:

.storybook-header {
  border-bottom: 1px solid var(--color-border-subtle, rgba(0,0,0,0.1));
}

.storybook-header .welcome {
  color: var(--color-fg-muted, #333);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-border-subtle: rgba(255,255,255,0.16);
    --color-fg-muted: #c9d1d9;
  }
}
stories/Button.tsx (3)

18-37: focus 관리/접근성 호환을 위해 ref 전달을 지원하세요

디자인 시스템의 Button은 포커스 제어나 스크롤/측정 목적의 ref 전달이 빈번합니다. forwardRef를 적용하는 것을 권장합니다.

-export const Button = ({
+export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({
   primary = false,
   size = 'medium',
   backgroundColor,
   label,
   className,
   style,
   ...rest
-}: ButtonProps) => {
+}: ButtonProps, ref) => {
   const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
   const classes = ['storybook-button', `storybook-button--${size}`, mode, className]
     .filter(Boolean)
     .join(' ');
   const mergedStyle = { ...(style ?? {}), ...(backgroundColor ? { backgroundColor } : {}) };
   return (
     <button
+      ref={ref}
       type="button"
       className={classes}
       style={mergedStyle}
       {...rest}
     >
       {label}
     </button>
   );
-};
+});

6-11: 불린 primary 대신 variant 문자열을 사용하면 확장이 쉽습니다

boolean(primary) 보다는 variant: 'primary' | 'secondary'로 명시하면 향후 'tertiary', 'danger' 등 확장이 수월합니다. 지금 단계에서는 선택 사항이지만 디자인 토큰/테마와의 매핑에도 이점이 있습니다.

-  primary?: boolean;
+  variant?: 'primary' | 'secondary';
...
-export const Button = ({
-  primary = false,
+export const Button = ({
+  variant = 'secondary',
...
-  const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
+  const mode = variant === 'primary' ? 'storybook-button--primary' : 'storybook-button--secondary';

Also applies to: 20-21, 26-31


1-1: React 자동 JSX 런타임 적용 여부 확인 요청: stories/Button.tsximport React

stories/Button.tsx 상단의

import React from 'react';

는 React 17+ 자동 JSX 런타임(react-jsx)을 사용 중이라면 불필요할 수 있습니다. 현재 프로젝트 내 주요 tsconfig.json들의 jsx 설정은 다음과 같습니다:

  • apps/extension/tsconfig.json: compilerOptions.jsx = react-jsx
  • packages/design-system/tsconfig.json: compilerOptions.jsx = react-jsx
  • apps/client/tsconfig.json: compilerOptions.jsx 미설정 (기본값 사용)
  • apps/landing/tsconfig.json: compilerOptions.jsx 미설정 (기본값 사용)

stories/Button.tsx 파일이 어떤 tsconfig.json(또는 별도 Storybook 전용 설정)을 상속받는지 확인
∘ TS 컴파일러 측에서 자동 런타임이 활성화되어 있다면 불필요한 import 제거 가능
∘ 필요 시 Babel(@babel/preset-react) 혹은 Storybook 메인 설정(.storybook/main.js)에서의 automatic runtime 설정도 함께 검토

위 사항을 점검 후, 자동 런타임이 확실히 적용된 환경이라면 해당 import React from 'react';를 제거해주세요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between be6051f and 4dcb82f.

⛔ Files ignored due to path filters (16)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • stories/assets/accessibility.png is excluded by !**/*.png
  • stories/assets/accessibility.svg is excluded by !**/*.svg
  • stories/assets/addon-library.png is excluded by !**/*.png
  • stories/assets/assets.png is excluded by !**/*.png
  • stories/assets/context.png is excluded by !**/*.png
  • stories/assets/discord.svg is excluded by !**/*.svg
  • stories/assets/docs.png is excluded by !**/*.png
  • stories/assets/figma-plugin.png is excluded by !**/*.png
  • stories/assets/github.svg is excluded by !**/*.svg
  • stories/assets/share.png is excluded by !**/*.png
  • stories/assets/styling.png is excluded by !**/*.png
  • stories/assets/testing.png is excluded by !**/*.png
  • stories/assets/theming.png is excluded by !**/*.png
  • stories/assets/tutorials.svg is excluded by !**/*.svg
  • stories/assets/youtube.svg is excluded by !**/*.svg
📒 Files selected for processing (17)
  • .storybook/main.ts (1 hunks)
  • .storybook/preview.ts (1 hunks)
  • .storybook/vitest.setup.ts (1 hunks)
  • package.json (1 hunks)
  • packages/design-system/src/components/badge/Badge.stories.tsx (1 hunks)
  • stories/Button.stories.ts (1 hunks)
  • stories/Button.tsx (1 hunks)
  • stories/Configure.mdx (1 hunks)
  • stories/Header.stories.ts (1 hunks)
  • stories/Header.tsx (1 hunks)
  • stories/Page.stories.ts (1 hunks)
  • stories/Page.tsx (1 hunks)
  • stories/button.css (1 hunks)
  • stories/header.css (1 hunks)
  • stories/page.css (1 hunks)
  • vitest.config.ts (1 hunks)
  • vitest.shims.d.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-23T18:42:42.797Z
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#22
File: packages/design-system/package.json:32-35
Timestamp: 2025-08-23T18:42:42.797Z
Learning: Storybook packages have different release cycles and stable versions. As of 2025, storybook/test and storybook/blocks have their latest stable versions in the 8.x line (8.6.14), while other packages like storybook/react-vite are stable in 9.x line (9.1.3). Mixed major versions across Storybook packages can be intentional and correct when using the latest stable version of each package.

Applied to files:

  • package.json
🧬 Code graph analysis (5)
stories/Header.stories.ts (1)
stories/Header.tsx (1)
  • Header (17-56)
stories/Page.stories.ts (2)
stories/Page.tsx (1)
  • Page (10-73)
stories/Header.stories.ts (2)
  • LoggedOut (34-34)
  • LoggedIn (26-32)
stories/Page.tsx (1)
stories/Header.tsx (1)
  • Header (17-56)
stories/Header.tsx (1)
stories/Button.tsx (1)
  • Button (19-37)
stories/Button.stories.ts (1)
stories/Button.tsx (1)
  • Button (19-37)
🪛 GitHub Check: lint
packages/design-system/src/components/badge/Badge.stories.tsx

[warning] 3-3:
'BadgeProps' is defined but never used

🔇 Additional comments (14)
packages/design-system/src/components/badge/Badge.stories.tsx (2)

9-12: 기본 args 합리적 설정 확인

기본 값 text: '알림', countNum: 3은 컴포넌트 동작을 직관적으로 보여줍니다. 이 상태 유지에 동의합니다.


1-1: import 경로 유지: @storybook/react-vite

설치된 Storybook 패키지 중 @storybook/react-vite는 9.x(현재 v9.1.3) 라인으로, stories 파일에서 사용하는 React-Vite 프레임워크에 맞는 올바른 import 경로입니다. @storybook/blocks@storybook/test만 8.x 라인이지만, 이는 별도 모듈이므로 React용 타입 import에는 영향이 없습니다.

  • packages/design-system/src/components/badge/Badge.stories.tsx (1행)
    → import type { Meta, StoryObj } from '@storybook/react-vite'; (변경 불필요)

Likely an incorrect or invalid review comment.

.storybook/preview.ts (1)

12-17: @storybook/addon-a11y ‘test’ 파라미터 지원 여부 확인 요청

사용 중인 @storybook/addon-a11y 패키지 버전과 함께, 로컬에서 아래 명령어들을 실행해 결과를 공유해 주세요.
이 정보가 있어야 ‘test: "todo"’가 공식 스키마에 포함되어 있는지, 그리고 CI 실패 여부를 결정짓는 설정인지 정확히 검증할 수 있습니다.

• 패키지 버전 확인

npm ls @storybook/addon-a11y
# 또는
yarn why @storybook/addon-a11y

• 타입 정의에서 ‘test’ 키 존재 여부 검색 (TypeScript 프로젝트의 경우)

grep -R "interface A11yParameters" -n node_modules/@storybook/addon-a11y
sed -n '1,200p' node_modules/@storybook/addon-a11y/dist/index.d.ts | sed -n '/A11yParameters/,/}/p'

• 런타임 스키마(JSON)에서 ‘test’ 키 검색

grep -R "\"test\"" -n node_modules/@storybook/addon-a11y

위 결과를 알려주시면, 해당 파라미터의 공식 지원 여부를 확정하고
‘todo’ vs ‘error’ 정책 적용 방안 또는 대안(별도 a11y 테스트 러너 등)을 제안드리겠습니다.

stories/button.css (1)

11-13: 색상 대비 AA 기준 충족 확인됨

  • 대상 파일: stories/button.css (11–13행)
  • 코드 스니펫:
      background-color: #555ab9;
      color: white;
    }
  • 측정된 대비: 5.92:1 (WCAG AA 일반 텍스트 기준 4.5:1 이상 충족)

현재 상태 그대로 유지해도 무방합니다. 추후 다크 테마 도입 시 대비만 재점검 부탁드립니다.

.storybook/vitest.setup.ts (1)

5-7: 구성 적용 순서 적절(LGTM)

애드온 애너테이션 후 프로젝트 애너테이션을 적용해 프로젝트 설정이 우선하도록 한 점이 좋습니다.

stories/Page.tsx (1)

10-20: 상태 전환 플로우 명확(LGTM)

헤더 콜백과 로컬 상태만으로 로그인/로그아웃이 깔끔히 재현됩니다. 스토리 상호작용 테스트와도 정합성이 좋습니다.

stories/Page.stories.ts (1)

10-13: 스토리 구성 합리적(LGTM)

풀스크린 레이아웃 파라미터 지정이 페이지 성격에 적절합니다.

stories/Header.stories.ts (1)

26-34: 스토리 구성 간결(LGTM)

LoggedIn/LoggedOut 스토리로 상태 차이를 명확히 드러내고 있어 문서성과 테스트 재사용성이 좋습니다.

stories/page.css (1)

1-9: 범위 한정(.storybook-page)으로 전역 오염 방지한 점 좋습니다

Storybook 전용 스타일을 상위 클래스에 네임스페이스해 전역 CSS를 오염시키지 않는 방식은 적절합니다.

stories/Header.tsx (1)

17-56: 조건부 렌더링과 props 설계가 간결합니다

user 유무에 따른 분기와 콜백 전달이 직관적입니다. Storybook 데모 컴포넌트로서 구조가 명료합니다.

vitest.config.ts (2)

18-19: extends: true 사용 의도 확인 요청

프로젝트별 설정이 상위 test 옵션을 상속하도록 한 듯하나, 현 설정엔 상속할 상위 test 옵션이 없습니다(projects만 존재). 불필요하면 제거해 단순화하세요.

확인 지침:

  • 상위 test 루트에 공통 옵션이 없다면 extends: true 제거 시 동작 동일해야 합니다.
  • 제거 후 Vitest가 동일하게 Storybook 프로젝트만 실행되는지 로컬에서 한 번 검증 부탁드립니다.

6-7: 필수 devDependencies 누락: @storybook/test 설치 필요

스크립트 실행 결과, 아래 패키지들은 모두 존재하지만 @storybook/test만 누락된 것으로 확인되었습니다. 스토리북 Vitest 통합을 위해 반드시 추가해주세요.

검사 결과 요약:

  • OK – @storybook/addon-vitest ^9.1.3
  • OK – @storybook/react-vite ^9.1.3
  • MISS – @storybook/test
  • OK – vitest ^3.2.4
  • OK – playwright ^1.55.0
  • OK – .storybook/vitest.setup.ts (파일 존재)

해결 방안:

# npm 사용 시
npm install -D @storybook/test@^9.1.3

# yarn 사용 시
yarn add -D @storybook/test@^9.1.3

설치 후 다시 의존성 및 설정 파일 로딩을 확인해주세요.

⛔ Skipped due to learnings
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#22
File: packages/design-system/package.json:32-35
Timestamp: 2025-08-23T18:42:42.797Z
Learning: Storybook packages have different release cycles and stable versions. As of 2025, storybook/test and storybook/blocks have their latest stable versions in the 8.x line (8.6.14), while other packages like storybook/react-vite are stable in 9.x line (9.1.3). Mixed major versions across Storybook packages can be intentional and correct when using the latest stable version of each package.
stories/header.css (1)

1-8: 헤더 컨테이너 스코프와 flex 레이아웃 구성 적절

컴포넌트 스코프로 네임스페이스했고 배치·정렬 속성이 명확합니다.

stories/Button.tsx (1)

1-38: PR 범위 확인: 뱃지 컴포넌트 PR에 Button 스토리 파일이 포함됨

PR 제목/목표가 Badge 컴포넌트 추가인데, 본 파일은 Storybook용 Button 구현입니다. 의도된 동반 변경인지, 템플릿 잔재인지 확인 부탁드립니다. 불필요하다면 PR에서 제외해 diff 노이즈를 줄이는 것을 권장합니다.

@@ -0,0 +1,7 @@
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

addon-a11y 애너테이션 import 방식 수정

namespace import(* as) 대신 기본(default) import를 사용해야 애너테이션 객체가 올바르게 전달됩니다.

-import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
+import a11yAddonAnnotations from '@storybook/addon-a11y/preview';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
import a11yAddonAnnotations from '@storybook/addon-a11y/preview';
🤖 Prompt for AI Agents
In .storybook/vitest.setup.ts around line 1, the file currently uses a namespace
import for the addon-a11y annotations which prevents the annotations object from
being passed correctly; replace the namespace import with a default import from
"@storybook/addon-a11y/preview" (i.e. import the default export) and ensure the
default-imported value is used where the annotations are registered or exported
so the addon receives the correct annotations object.

Comment on lines 15 to 34
"@chromatic-com/storybook": "^4.1.1",
"@pivanov/vite-plugin-svg-sprite": "^3.1.3",
"@storybook/addon-a11y": "^9.1.3",
"@storybook/addon-docs": "^9.1.3",
"@storybook/addon-vitest": "^9.1.3",
"@storybook/react-vite": "^9.1.3",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/react": "19",
"@types/react-dom": "19",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"storybook": "^9.1.3",
"turbo": "^2.5.6",
"typescript": "5.9.2",
"vite": "7.1.2"
"vite": "7.1.2",
"vitest": "^3.2.4",
"@vitest/browser": "^3.2.4",
"playwright": "^1.55.0",
"@vitest/coverage-v8": "^3.2.4"
},
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

React 타입 버전 정합성 확인(워크스페이스 전반)

루트 devDeps에 @types/react, @types/react-dom"19"로 지정되어 있습니다. 워크스페이스 내 실제 react, react-dom 런타임 메이저와 어긋나면 타입 불일치가 발생할 수 있습니다. 각 앱/패키지의 런타임 버전과 맞춰 주세요(예: 모두 18이면 타입도 18로). 루트에만 타입을 고정하면 하위 패키지의 로컬 타입 해상도에 간섭할 수 있으니, 필요 시 패키지별 devDeps로 내려 배치하는 것도 고려해 주세요.


🏁 Script executed:

#!/bin/bash
# 워크스페이스 전체의 react/react-dom 및 타입 버전 매트릭스 출력
rg -nP --glob '!node_modules' --glob '!*dist*' '"react(?:-dom)?"\s*:\s*"[^\"]+"' package.json -C2
fd -a -t f -g "package.json" apps packages | xargs -I{} sh -c 'echo "---- {} ----"; jq -r ".dependencies.react, .dependencies[\"react-dom\"], .devDependencies[\"@types/react\"], .devDependencies[\"@types/react-dom\"]" "{}" 2>/dev/null'

Length of output: 946


React 타입 버전 불일치: apps/extension 패키지 수정 필요
apps/extension/package.json의 런타임 React 버전(^19.1.1)과 로컬에 선언된 타입 패키지(@types/react ^18.3.5, @types/react-dom ^18.3.0)가 맞지 않아 타입 에러가 발생할 수 있습니다. 아래와 같이 조치해주세요.

  • apps/extension/package.json
    • devDependencies에서
      • "@types/react": "^18.3.5""^19.1.0" (또는 루트 devDependencies에 맞춘 "19")
      • "@types/react-dom": "^18.3.0""^19.1.0" (또는 "19")
    • 필요 시 해당 로컬 타입 선언을 제거하고 루트에 선언된 @types/react, @types/react-dom을 사용하도록 조정
  • (선택) 워크스페이스 전반에서 타입 동결을 원한다면 루트에만 @types/react/@types/react-dom "19"를 유지하고, 하위 패키지에 별도 선언은 제거

위 변경 후 pnpm install 또는 yarn install 실행하여 의존성 충돌이 없는지 확인 바랍니다.

Comment on lines 29 to 33
"vite": "7.1.2",
"vitest": "^3.2.4",
"@vitest/browser": "^3.2.4",
"playwright": "^1.55.0",
"@vitest/coverage-v8": "^3.2.4"
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Vite 7.1.2와 @storybook/react-vite 9.1.3 호환성 점검

pnpm.overrides로 Vite 7.1.2를 강제 고정하고 있습니다. 현재 @storybook/react-vite@^9.1.3과의 공식 호환 범위를 한 번 확인해 주세요. 호환 문제가 없으면 고정 유지, 경고가 있다면 Vite를 권장 범위로 조정하는 것이 안전합니다.


🌐 Web query:

@storybook/react-vite 9.1.3이 공식적으로 지원하는 Vite 메이저/마이너 버전 범위를 확인해 주세요. Vite 7.1.x 호환 여부도 함께 확인 바랍니다.

💡 Result:

검색을 수행해 관련 문서를 확인했습니다.

결론 요약:

  • @storybook/react-vite 9.1.3의 package.json engines / peerDependencies 등에 명시된 공식적으로 지원하는 Vite 버전 범위는 "vite": "^5.0.0 || ^6.0.0"으로 표시되어 있습니다 [1][2]. 즉 Vite 메이저 버전으로는 5.x 및 6.x 를 공식 지원 대상으로 명시하고 있습니다.
  • 따라서 Vite 7.1.x 는 공식 지원 범위에 포함되지 않습니다(호환성 보장은 없음). 실제로 Storybook 9.2 이상 또는 관련 릴리스 노트를 확인해야 Vite 7 지원 여부를 확정할 수 있습니다 [1][3].

근거(참고 사이트):

  1. npm 패키지 소스(package.json) 또는 GitHub 배포의 peerDependencies/engines 항목 — @storybook/react-vite 9.1.3 (해당 파일의 Vite 버전 범위 표기) [링크 제공 원문 소스].
  2. npmjs.org의 @storybook/react-vite@9.1.3 페이지 (peerDependencies 정보) [링크 제공 원문 소스].
  3. Storybook 릴리스 노트 / 마이그레이션 가이드 (Vite 관련 지원 변경사항 확인용) [링크 제공 원문 소스].

원하시면 제가 각 출처의 정확한 URL(패키지.json 스니펫, npm 페이지, 릴리스 노트)을 첨부해 드리겠습니다.


@storybook/react-vite@9.1.3 호환 Vite 버전 범위 확인 필요

공식 peerDependencies에 “vite”: “^5.0.0 || ^6.0.0”으로만 명시되어 있어 Vite 7.x(예: 7.1.2)는 지원 범위에 포함되지 않습니다 [1][2]. 이 상태로 유지할 경우 Storybook 실행 시 호환성 문제가 발생할 수 있으니 아래 중 하나를 적용해주세요.

• Vite 버전을 6.x (예: 6.9.5)로 다운그레이드
• 또는 Storybook을 9.2 이상으로 업그레이드하여 Vite 7.x 지원 여부를 확인 후 버전 조정

References:
[1] @storybook/react-vite@9.1.3 package.json peerDependencies
[2] npmjs.org의 @storybook/react-vite@9.1.3 peerDependencies 정보

🤖 Prompt for AI Agents
In package.json around lines 29-33, the declared Vite version "7.1.2" is outside
the peerDependency range of @storybook/react-vite@9.1.3; either pin Vite to a
compatible 6.x release (e.g., "vite": "6.9.5") or upgrade @storybook/react-vite
to a version that explicitly supports Vite 7.x (e.g., >=9.2) and then align
package.json versions accordingly, run a fresh install, and verify Storybook
starts without peerDependency warnings or runtime errors.

Comment on lines 3 to 4
import { fn } from 'storybook/test';

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

테스트 유틸 import 경로 오류: '@storybook/test' 사용 필요

storybook/test는 유효하지 않습니다. Storybook의 테스트 유틸은 @storybook/test에서 가져와야 합니다.

-import { fn } from 'storybook/test';
+import { fn } from '@storybook/test';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { fn } from 'storybook/test';
import { fn } from '@storybook/test';
🤖 Prompt for AI Agents
stories/Button.stories.ts lines 3-4: the import path is incorrect — replace the
invalid "storybook/test" import with the correct "@storybook/test" package;
update the import statement to import fn from '@storybook/test' so the Storybook
testing utilities are resolved from the proper package.

Comment on lines 5 to 16
export interface ButtonProps {
/** Is this the principal call to action on the page? */
primary?: boolean;
/** What background color to use */
backgroundColor?: string;
/** How large should the button be? */
size?: 'small' | 'medium' | 'large';
/** Button contents */
label: string;
/** Optional click handler */
onClick?: () => void;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

표준 버튼 속성(ARIA/disabled/className 등) 수용을 위해 타입을 확장하세요

현재 ButtonProps는 onClick만 선언되어 있어 JSX에서 disabled, aria-*, className 같은 표준 속성을 전달하면 타입 오류가 납니다. 또한 onClick의 타입도 () => void 보다는 React.MouseEventHandler가 적절합니다. 디자인 시스템 컴포넌트는 기본 버튼 속성을 모두 통과시킬 수 있어야 하므로 ButtonHTMLAttributes로 확장하는 편이 안전합니다.

아래와 같이 최소 수정안을 제안합니다:

-export interface ButtonProps {
+export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
   /** Is this the principal call to action on the page? */
   primary?: boolean;
   /** What background color to use */
   backgroundColor?: string;
   /** How large should the button be? */
   size?: 'small' | 'medium' | 'large';
   /** Button contents */
   label: string;
-  /** Optional click handler */
-  onClick?: () => void;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface ButtonProps {
/** Is this the principal call to action on the page? */
primary?: boolean;
/** What background color to use */
backgroundColor?: string;
/** How large should the button be? */
size?: 'small' | 'medium' | 'large';
/** Button contents */
label: string;
/** Optional click handler */
onClick?: () => void;
}
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
/** Is this the principal call to action on the page? */
primary?: boolean;
/** What background color to use */
backgroundColor?: string;
/** How large should the button be? */
size?: 'small' | 'medium' | 'large';
/** Button contents */
label: string;
}
🤖 Prompt for AI Agents
In stories/Button.tsx around lines 5 to 16, ButtonProps currently only declares
a few custom props and an onClick typed as () => void, which prevents passing
standard button attributes (disabled, aria-*, className, etc.) and uses the
wrong event type; update the interface to extend
React.ButtonHTMLAttributes<HTMLButtonElement> (or
React.ButtonHTMLAttributes<HTMLButtonElement>) so all native props are accepted,
change onClick to React.MouseEventHandler<HTMLButtonElement> (or remove it to
inherit the correct type from ButtonHTMLAttributes), and ensure React is
imported for the type references.

Comment on lines 19 to 33
export const Button = ({
primary = false,
size = 'medium',
backgroundColor,
label,
...props
}: ButtonProps) => {
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
return (
<button
type="button"
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
style={{ backgroundColor }}
{...props}
>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

props 스프레드 순서로 className/style이 덮어쓰기 됩니다 — 내부 클래스/스타일과 병합하세요

지금은 {...props}가 뒤에 와서 외부에서 전달된 className, style이 내부 계산값을 통째로 덮습니다. className은 병합하고, style은 배경색을 조건부로 덧씌우는 방식이 바람직합니다. 또한 타입 확장과 함께 className, style을 구조분해하여 rest와 분리해 주세요.

-export const Button = ({
-  primary = false,
-  size = 'medium',
-  backgroundColor,
-  label,
-  ...props
-}: ButtonProps) => {
-  const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
-  return (
-    <button
-      type="button"
-      className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
-      style={{ backgroundColor }}
-      {...props}
-    >
+export const Button = ({
+  primary = false,
+  size = 'medium',
+  backgroundColor,
+  label,
+  className,
+  style,
+  ...rest
+}: ButtonProps) => {
+  const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
+  const classes = ['storybook-button', `storybook-button--${size}`, mode, className]
+    .filter(Boolean)
+    .join(' ');
+  const mergedStyle = { ...(style ?? {}), ...(backgroundColor ? { backgroundColor } : {}) };
+  return (
+    <button
+      type="button"
+      className={classes}
+      style={mergedStyle}
+      {...rest}
+    >
       {label}
     </button>
   );
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const Button = ({
primary = false,
size = 'medium',
backgroundColor,
label,
...props
}: ButtonProps) => {
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
return (
<button
type="button"
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
style={{ backgroundColor }}
{...props}
>
export const Button = ({
primary = false,
size = 'medium',
backgroundColor,
label,
className,
style,
...rest
}: ButtonProps) => {
const mode = primary
? 'storybook-button--primary'
: 'storybook-button--secondary';
const classes = [
'storybook-button',
`storybook-button--${size}`,
mode,
className
]
.filter(Boolean)
.join(' ');
const mergedStyle = {
...(style ?? {}),
...(backgroundColor ? { backgroundColor } : {})
};
return (
<button
type="button"
className={classes}
style={mergedStyle}
{...rest}
>
{label}
</button>
);
};
🤖 Prompt for AI Agents
In stories/Button.tsx around lines 19-33, props are currently spread after
computed className/style which allows external className/style to overwrite
internal values; destructure className and style from props (e.g., const {
className: externalClassName, style: externalStyle, ...rest } = props), compute
a mergedClassName that joins internal classes with externalClassName, compute
mergedStyle by shallow-merging externalStyle with the internal style (applying
backgroundColor conditionally so internal backgroundColor overrides only when
provided), then spread rest last onto the button; also update ButtonProps/type
to include optional className and style if not already present.

Comment on lines 51 to 53
href="https://storybook.js.org/docs/configure/styling-and-css/?renderer=react&ref=configure"
target="_blank"
>Learn more<RightArrow /></a>
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

보안: target="_blank" 사용시 rel="noopener noreferrer" 추가

탭낚시(reverse tabnabbing) 예방을 위해 외부 링크에 rel 속성 추가가 필요합니다. 본 파일의 모든 target="_blank" 링크에 동일하게 적용해 주세요.

-      <a
-        href="https://storybook.js.org/docs/configure/styling-and-css/?renderer=react&ref=configure"
-        target="_blank"
-      >Learn more<RightArrow /></a>
+      <a
+        href="https://storybook.js.org/docs/configure/styling-and-css/?renderer=react&ref=configure"
+        target="_blank"
+        rel="noopener noreferrer"
+      >Learn more<RightArrow /></a>

다른 앵커에도 동일 패턴 적용 권장. 자동 점검 스크립트:


🏁 Script executed:

#!/bin/bash
# rel 누락된 target=_blank 링크 찾기
rg -nP 'target="_blank"(?![^>]*rel=)' stories/Configure.mdx

Length of output: 469


필수 보안 수정: 모든 target="_blank" 링크에 rel="noopener noreferrer" 추가하기

아래 위치에서 외부 링크에 rel 속성이 누락되어 있습니다. 탭낚시(reverse tabnabbing) 공격을 예방하려면 각 링크에 rel="noopener noreferrer"를 추가해주세요.

• stories/Configure.mdx
• 52행, 64행, 76행, 98행, 107행, 117행, 127행, 136행, 145행, 157행, 172행, 182행, 193행, 203행

예시 수정:

-      <a
-        href="https://storybook.js.org/docs/configure/styling-and-css/?renderer=react&ref=configure"
-        target="_blank"
-      >Learn more<RightArrow /></a>
+      <a
+        href="https://storybook.js.org/docs/configure/styling-and-css/?renderer=react&ref=configure"
+        target="_blank"
+        rel="noopener noreferrer"
+      >Learn more<RightArrow /></a>

다른 파일 또는 컴포넌트에서도 동일한 패턴이 있다면 마찬가지로 적용해 주세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
href="https://storybook.js.org/docs/configure/styling-and-css/?renderer=react&ref=configure"
target="_blank"
>Learn more<RightArrow /></a>
<a
href="https://storybook.js.org/docs/configure/styling-and-css/?renderer=react&ref=configure"
target="_blank"
rel="noopener noreferrer"
>Learn more<RightArrow /></a>
🤖 Prompt for AI Agents
In stories/Configure.mdx around lines 51 to 53 (and also at lines 64, 76, 98,
107, 117, 127, 136, 145, 157, 172, 182, 193, 203), external anchor tags that use
target="_blank" are missing rel="noopener noreferrer"; update each anchor
element that opens in a new tab to include rel="noopener noreferrer" alongside
target="_blank" (e.g., change <a ... target="_blank"> to <a ... target="_blank"
rel="noopener noreferrer">) ensuring all listed lines are fixed and scan for any
other target="_blank" occurrences in the file to apply the same change.

Comment on lines 21 to 23
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<path
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

SVG 접근성: 장식용이면 숨김 처리, 의미가 있다면 대체 텍스트 추가

로고가 장식용이면 스크린리더에서 숨기는 편이 바람직합니다. 의미가 있으면 role/aria-label을 부여하세요.

-        <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+        <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">

의미 있는 로고일 경우 대안:

-        <svg aria-hidden="true" focusable="false" ...
+        <svg role="img" aria-label="Acme logo" focusable="false" ...
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<path
<svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<path
Suggested change
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<path
<svg role="img" aria-label="Acme logo" focusable="false" width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<path
🤖 Prompt for AI Agents
In stories/Header.tsx around lines 21-23 the SVG logo currently lacks
accessibility attributes; if the logo is purely decorative add
aria-hidden="true" and focusable="false" to the svg element (and remove
interactive handlers), otherwise mark it as an image by adding role="img" plus a
descriptive aria-label or include a <title> element inside the svg for screen
readers and ensure focusable="false" is set; update the JSX accordingly so
decorative icons are hidden from assistive tech and meaningful logos expose an
accessible name.

Copy link
Member

@constantly-dev constantly-dev left a comment

Choose a reason for hiding this comment

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

수고하셨습니다~
처음 스토리북 세팅할 때 파일이 남아있는 것처럼 보이는데 코멘트 확인해주세요 👍

import Badge, { type BadgeProps } from './Badge';

const meta: Meta<typeof Badge> = {
title: 'UI/Badge',
Copy link
Member

Choose a reason for hiding this comment

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

title: 'Components/Badge'로 통일합시다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵! 맞춰두겠습니당

}
const BadgeTxtStyleVariants = cva('sub3-b', {
variants: {
click: {
Copy link
Member

Choose a reason for hiding this comment

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

boolean값이니 isClick으로 바꾸는 것은 어떨까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

오 boolean은 최대한 가독성 위해서 is~으로 맞춰서 작업해두겠습니다

Copy link
Member

Choose a reason for hiding this comment

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

초기 설정할 때 나오는 HeaderPage이런 부분이 지금 생성되었나요????
전에 지웠던 걸로 기억하는데! 밑에 assets도!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

제가 init 잘못 초기세팅을 중복하게 하면서 생긴 폴더 같네요..! 확인해보겠습니다!

docs: {
description: {
component:
'클릭 시 내부 state(isClick)가 true로 바뀌며 스타일이 활성화됩니다. 토글 방식은 아닙니다.!',
Copy link
Member

Choose a reason for hiding this comment

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

뒤에 .으로 사용!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/design-system/src/components/badge/Badge.stories.tsx (1)

6-6: Storybook 타이틀 컨벤션 준수 확인

타이틀을 Components/Badge로 통일한 점 좋습니다. 이전 코멘트가 잘 반영되었습니다.

🧹 Nitpick comments (4)
packages/design-system/src/components/badge/Badge.stories.tsx (4)

3-3: 불필요한 타입 import 제거로 lint 경고 해소

BadgeProps를 가져오지만 사용하지 않아 lint 경고가 발생합니다. 불필요한 import를 제거해 깨끗한 빌드 유지하세요.

-import Badge, { type BadgeProps } from './Badge';
+import Badge from './Badge';

2-2: play 함수에 어설션 추가로 회귀 탐지 강화

현재는 클릭만 수행합니다. 간단한 어설션을 추가해 상호작용의 결과가 실제로 반영되는지 검증하도록 보강하는 것을 권장합니다(속성/셀렉터는 컴포넌트 구현에 맞게 조정).

-import { within, userEvent } from '@storybook/test';
+import { within, userEvent, expect } from '@storybook/test';
@@
   play: async ({ canvasElement, args }) => {
     const canvas = within(canvasElement);
-    await userEvent.click(await canvas.findByText(String(args.text)));
+    const label = await canvas.findByText(String(args.text));
+    await userEvent.click(label);
+    // 예: 활성화 시 data-state="active"가 걸린다고 가정. 구현에 맞게 조정하세요.
+    await expect(label).toHaveAttribute('data-state', 'active');
   },

Also applies to: 45-48


35-41: 0 카운트 케이스 스토리 추가 제안

undefined0의 UX가 다를 수 있습니다(예: 0이면 표시/비표시 정책). ZeroCount 스토리를 추가해 의도를 명확히 하고 회귀를 예방하세요.

 export const NoCount: Story = {
   args: { text: '카운트 없음', countNum: undefined },
 };
 
+export const ZeroCount: Story = {
+  args: { text: '카운트 0', countNum: 0 },
+};
+
 export const LargeCount: Story = {
   args: { text: '메시지', countNum: 12000 },
 };

5-5: 타입 안전성 미세 개선: satisfies 활용

현재도 문제는 없지만, satisfies를 사용하면 meta 객체가 과도하게 확장되는 것을 방지하면서 타입 체크가 더 엄격해집니다.

-const meta: Meta<typeof Badge> = {
+const meta = {
@@
-};
+} satisfies Meta<typeof Badge>;

Also applies to: 28-28

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4dcb82f and 0a0a6d5.

📒 Files selected for processing (1)
  • packages/design-system/src/components/badge/Badge.stories.tsx (1 hunks)
🧰 Additional context used
🪛 GitHub Check: lint
packages/design-system/src/components/badge/Badge.stories.tsx

[warning] 3-3:
'BadgeProps' is defined but never used

🔇 Additional comments (1)
packages/design-system/src/components/badge/Badge.stories.tsx (1)

1-1: @storybook/react-vite 경로가 올바릅니다.

  • 모노레포 루트와 packages/design-system 모두 @storybook/react-vite@^9.1.3이 설치되어 있습니다.
  • @storybook/react 패키지는 어디에도 설치되어 있지 않습니다.
  • Vite 빌더 기반 Storybook 구성에서는 @storybook/react-vite에서 타입을 가져오는 것이 권장되는 방식이므로 변경이 필요 없습니다.

Likely an incorrect or invalid review comment.

@jllee000 jllee000 requested a review from jjangminii August 25, 2025 17:59
@jllee000 jllee000 merged commit af64dee into develop Aug 25, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 기능 개발하라 개발 달려라 달려

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] badge 공통 컴포넌트 제작

2 participants