Repository navigation
[feat] 버튼 섹션 컴포넌트들 추가 - #4
Conversation
Walkthrough이 PR은 컴포넌트 추가·리팩터링과 개발 도구 설정 변경을 적용합니다. Button 변형 시스템 도입, FloatingButton·Switch·PlusIcon 추가, Storybook/Vitest 설정·경로 해상도 변경 및 관련 테스트/스토리 추가가 포함됩니다. (49단어) Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 분 검토 시 주의할 파일/영역:
Possibly related PRs
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (12)
src/icons/PlusIcon.tsx (1)
28-29: 빈 문자열aria-label에 대한 엣지 케이스를 고려해 주세요.
aria-label=""이 전달되면!ariaLabel이false가 되어aria-hidden={false}가 설정되지만, 실제 레이블은 비어있게 됩니다. 의도된 동작인지 확인이 필요합니다.더 명확한 처리를 원하시면 다음과 같이 수정할 수 있습니다:
- aria-label={ariaLabel} - aria-hidden={!ariaLabel} + aria-label={ariaLabel || undefined} + aria-hidden={!ariaLabel}src/components/FloatingButton/FloatingButton.test.tsx (2)
10-10: 사용되지 않는 모킹 제거를 권장합니다.
iconWrapper는FloatingButton.css.ts에서 export되지 않습니다. 실제로는floatingButton과contentWrapper만 export됩니다.다음과 같이 수정하세요:
vi.mock("./FloatingButton.css", () => ({ floatingButton: "mock-floating-button", - iconWrapper: "mock-icon-wrapper", contentWrapper: "mock-content-wrapper", }));
103-107: 테스트 설명이 부정확합니다.테스트명이 "icon이 없을 때도 렌더링되어야 합니다"이지만,
FloatingButton컴포넌트는 항상PlusIcon을 렌더링합니다(FloatingButton.tsx의 63번째 줄 참조). 이 테스트는 실제로 children이 있을 때 렌더링을 확인합니다.테스트명을 다음과 같이 수정하는 것을 권장합니다:
- it("icon이 없을 때도 렌더링되어야 합니다", () => { + it("children과 함께 렌더링되어야 합니다", () => { render(<FloatingButton>텍스트만</FloatingButton>); const button = screen.getByRole("button", { name: /텍스트만/ }); expect(button).toBeInTheDocument(); });src/components/FloatingButton/FloatingButton.tsx (3)
27-27: 불필요한 타입 강제 변환을 제거할 수 있습니다.
disabled는 이미boolean | undefined타입이므로!!연산자는 불필요합니다. 다만 명시성을 위해 유지할 수도 있습니다.다음과 같이 단순화할 수 있습니다:
- const isDisabled = !!disabled; + const isDisabled = disabled;
62-62: 불필요한 null 병합을 제거할 수 있습니다.JSX는
undefinedchildren을 자동으로 처리하므로|| null은 불필요합니다.다음과 같이 단순화할 수 있습니다:
- <span className={contentWrapper}>{children || null}</span> + <span className={contentWrapper}>{children}</span>
63-63: PlusIcon이 하드코딩되어 유연성이 제한됩니다.
FloatingButton은 항상PlusIcon을 렌더링하므로, 다른 아이콘이나 아이콘 없이 사용할 수 없습니다. 디자인 의도가 고정 아이콘이라면 JSDoc으로 명시하는 것을 권장합니다. 유연성이 필요하다면iconprop을 추가하는 것을 고려하세요.옵션 1: JSDoc으로 의도 명시 (고정 아이콘이 의도된 경우)
FloatingButtonProps 인터페이스에 다음을 추가:
+/** + * FloatingButton 컴포넌트 + * + * @remarks + * 이 버튼은 항상 PlusIcon을 포함하며, 아이콘을 변경할 수 없습니다. + */ export interface FloatingButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {}옵션 2: 유연한 아이콘 지원 (권장)
인터페이스에 icon prop 추가:
+import type { ReactNode } from "react"; + export interface FloatingButtonProps - extends ButtonHTMLAttributes<HTMLButtonElement> {} + extends ButtonHTMLAttributes<HTMLButtonElement> { + /** + * 표시할 아이콘 요소 + * @default <PlusIcon /> + */ + icon?: ReactNode; +}컴포넌트에서 사용:
( { children, + icon = <PlusIcon aria-label="+" />, type = "button", className, disabled,그리고 렌더링 부분:
> <span className={contentWrapper}>{children}</span> - <PlusIcon aria-label="+" /> + {icon} </button>src/components/Button/Button.stories.ts (1)
155-173: 아이콘으로 이모지 대신 실제 아이콘 컴포넌트 사용을 권장합니다.WithIcon 스토리에서 이모지 "🔔"를 사용하고 있습니다. 프로젝트에
PlusIcon과 같은 아이콘 컴포넌트가 있으므로, 실제 사용 사례를 더 잘 반영하기 위해 React 아이콘 컴포넌트 사용을 고려해 보세요.+import { PlusIcon } from "../../icons/PlusIcon"; + export const WithIcon: Story = { args: { children: "알림", variant: "primary", - icon: "🔔", + icon: <PlusIcon />, style: { width: "300px" }, },참고: JSX를 사용하려면 파일 확장자를
.tsx로 변경해야 합니다.src/components/FloatingButton/FloatingButton.stories.ts (1)
32-46: 버튼 이름에 " +" 접미사가 포함된 이유를 명확히 해주세요.
{ name: "Text +" }쿼리가 작동하는 이유는 FloatingButton.tsx에서<PlusIcon aria-label="+" />를 렌더링하기 때문입니다. 이는 컴포넌트 구현에 의존적이므로, 주석으로 이 동작을 문서화하면 유지보수에 도움이 됩니다.export const Default: Story = { args: { children: "Text", }, play: isTestEnvironment ? async ({ canvasElement }) => { const expect = getExpect(); const canvas = within(canvasElement); + // FloatingButton은 PlusIcon(aria-label="+")을 포함하여 접근성 이름이 "Text +"가 됨 const button = canvas.getByRole("button", { name: "Text +" });src/components/Button/Button.tsx (2)
72-74: 아이콘이 없을 때 빈 iconWrapper가 렌더링됩니다.
iconprop이 없어도iconWrapperspan이 항상 렌더링되어 불필요한 DOM 요소가 생성되고, 24px 너비의 빈 공간이 생길 수 있습니다.- <span className={iconWrapper} data-icon> - {icon || null} - </span> + {icon && ( + <span className={iconWrapper} data-icon> + {icon} + </span> + )}
19-79: 키보드 접근성: onKeyDown/onKeyUp 이벤트도 pressed 상태에 반영하는 것을 고려해 보세요.현재 마우스 이벤트만 pressed 상태를 변경합니다. 키보드(Enter/Space) 활성화 시에도 동일한 시각적 피드백을 제공하면 접근성이 향상됩니다.
src/components/Button/Button.css.ts (1)
96-100:contentWrapper스타일이FloatingButton.css.ts와 중복됩니다.
FloatingButton.css.ts(lines 37-41)에 동일한contentWrapper스타일이 존재합니다. 향후 유지보수를 위해 공통 스타일 유틸리티로 추출하는 것을 고려해 보세요.vite.config.ts (1)
48-57: unit 프로젝트에서 설정이 중복되어 있습니다.
environment와setupFiles가 기본 테스트 설정(lines 27, 29)과 unit 프로젝트(lines 52, 55)에서 중복 정의되어 있습니다. 프로젝트 설정이 기본 설정을 오버라이드하므로 동작에는 문제가 없지만, 유지보수를 위해 중복을 제거하거나 의도를 명확히 하는 것이 좋습니다.projects: [ { test: { name: "unit", - environment: "jsdom", include: ["**/*.test.{ts,tsx}"], exclude: ["**/*.stories.{ts,tsx}", "node_modules/**"], - setupFiles: ["./vitest.setup.ts"], }, },
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (41)
.biomeignore(1 hunks).coderabbit.yaml(1 hunks).github/ACCESSIBILITY.md(1 hunks).github/HUSKY_SETUP.md(1 hunks).github/ISSUE_TEMPLATE/bug_report.md(1 hunks).github/ISSUE_TEMPLATE/feature_request.md(1 hunks).github/RELEASE_TEMPLATE.md(1 hunks).storybook/main.ts(2 hunks).storybook/tsconfig.json(1 hunks).storybook/utils.ts(1 hunks)CHANGELOG.md(1 hunks)LICENSE(1 hunks)biome.json(1 hunks)docs/STYLING.md(1 hunks)docs/THEMING.md(1 hunks)package.json(1 hunks)src/components/Button/Button.css.ts(1 hunks)src/components/Button/Button.stories.ts(3 hunks)src/components/Button/Button.test.tsx(2 hunks)src/components/Button/Button.tsx(1 hunks)src/components/FloatingButton/FloatingButton.css.ts(1 hunks)src/components/FloatingButton/FloatingButton.stories.ts(1 hunks)src/components/FloatingButton/FloatingButton.test.tsx(1 hunks)src/components/FloatingButton/FloatingButton.tsx(1 hunks)src/components/Switch/Switch.css.ts(1 hunks)src/components/Switch/Switch.stories.ts(1 hunks)src/components/Switch/Switch.test.tsx(1 hunks)src/components/Switch/Switch.tsx(1 hunks)src/icons/PlusIcon.tsx(1 hunks)src/icons/index.ts(1 hunks)src/index.ts(1 hunks)src/stories/tokens/Colors.stories.tsx(1 hunks)src/stories/tokens/Radius.stories.tsx(1 hunks)src/stories/tokens/Shadows.stories.tsx(1 hunks)src/stories/tokens/TextStyles.stories.tsx(1 hunks)src/stories/tokens/Typography.stories.tsx(1 hunks)src/tokens/index.ts(2 hunks)src/tokens/textStyles.css.ts(1 hunks)tsconfig.json(1 hunks)vite.config.ts(1 hunks)vitest.config.ts(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (13)
src/components/Switch/Switch.tsx (2)
src/components/Switch/Switch.css.ts (3)
switchButton(9-22)switchTrack(24-43)switchThumb(45-60)src/components/Button/Button.css.ts (1)
button(86-86)
src/components/FloatingButton/FloatingButton.test.tsx (2)
src/components/FloatingButton/FloatingButton.tsx (1)
FloatingButton(9-67)src/components/Button/Button.css.ts (1)
button(86-86)
src/components/Switch/Switch.css.ts (3)
src/tokens/index.ts (2)
colors(5-5)radius(7-7)src/tokens/colors.css.ts (1)
colors(1-54)src/tokens/radius.css.ts (1)
radius(1-9)
src/components/Switch/Switch.test.tsx (1)
src/components/Switch/Switch.tsx (1)
Switch(18-44)
src/components/Button/Button.test.tsx (2)
src/components/Button/Button.tsx (1)
Button(19-79)src/components/Button/Button.css.ts (2)
button(86-86)iconWrapper(89-94)
src/components/FloatingButton/FloatingButton.stories.ts (3)
src/components/FloatingButton/FloatingButton.tsx (1)
FloatingButton(9-67)src/components/Button/Button.stories.ts (3)
Default(37-51)WithText(53-67)Disabled(69-86).storybook/utils.ts (2)
isTestEnvironment(10-12)getExpect(22-35)
src/icons/PlusIcon.tsx (1)
src/icons/index.ts (1)
PlusIcon(1-1)
src/components/FloatingButton/FloatingButton.css.ts (5)
src/tokens/textStyles.css.ts (1)
textStyles(21-76)src/tokens/radius.css.ts (1)
radius(1-9)src/tokens/colors.css.ts (1)
colors(1-54)src/tokens/shadows.css.ts (1)
shadows(1-7)src/components/Button/Button.css.ts (1)
contentWrapper(96-100)
src/components/FloatingButton/FloatingButton.tsx (4)
src/components/FloatingButton/FloatingButton.css.ts (2)
floatingButton(4-36)contentWrapper(38-42)src/components/Button/Button.css.ts (2)
button(86-86)contentWrapper(96-100)src/icons/index.ts (1)
PlusIcon(1-1)src/icons/PlusIcon.tsx (1)
PlusIcon(16-46)
src/components/Button/Button.tsx (1)
src/components/Button/Button.css.ts (4)
buttonVariants(20-83)button(86-86)iconWrapper(89-94)contentWrapper(96-100)
src/components/Switch/Switch.stories.ts (2)
src/components/Switch/Switch.tsx (1)
Switch(18-44).storybook/utils.ts (2)
isTestEnvironment(10-12)getExpect(22-35)
src/components/Button/Button.css.ts (5)
src/tokens/textStyles.css.ts (1)
textStyles(21-76)src/tokens/radius.css.ts (1)
radius(1-9)src/tokens/colors.css.ts (1)
colors(1-54)src/tokens/shadows.css.ts (1)
shadows(1-7)src/components/FloatingButton/FloatingButton.css.ts (1)
contentWrapper(38-42)
src/components/Button/Button.stories.ts (2)
.storybook/utils.ts (2)
isTestEnvironment(10-12)getExpect(22-35)src/components/Button/Button.css.ts (1)
button(86-86)
🔇 Additional comments (52)
.biomeignore (1)
1-3: trailing newline 추가는 형식적 정리입니다.이는 순수 형식 변경으로 기능에 영향을 주지 않습니다. lint 검사를 통과했으므로 문제 없습니다.
.github/ISSUE_TEMPLATE/feature_request.md (1)
58-58: 파일 끝의 빈 줄 추가이것은 순수 포맷팅 변경입니다.
.github/ISSUE_TEMPLATE/bug_report.md (1)
72-72: 파일 끝의 공백라인 추가.GitHub 이슈 템플릿 파일 끝에 공백라인이 추가되었습니다. 이는 저장소의 일반적인 파일 구조 관례와 일치합니다.
.github/RELEASE_TEMPLATE.md (1)
106-106: 포매팅 개선 확인파일 끝에 trailing newline을 추가하여 POSIX 표준을 준수합니다.
.github/ACCESSIBILITY.md (1)
317-317: 포매팅 개선 확인파일 끝에 trailing newline을 추가하여 일관성을 유지합니다.
docs/THEMING.md (1)
226-226: 포매팅 개선 확인파일 끝에 trailing newline을 추가하여 일관성을 유지합니다.
LICENSE (1)
21-21: 포매팅 개선 확인파일 끝에 trailing newline을 추가하여 일관성을 유지합니다.
docs/STYLING.md (1)
221-221: 포매팅 개선 확인파일 끝에 trailing newline을 추가하여 일관성을 유지합니다.
CHANGELOG.md (1)
30-30: 포매팅 개선 확인파일 끝에 trailing newline을 추가하여 일관성을 유지합니다.
src/icons/index.ts (1)
1-1: PlusIcon export is correctly implementedPlusIcon.tsx exists in src/icons/ and is properly exported through the barrel file. The export statement is accurate and the implementation is in place. Currently no usages of PlusIcon are found in the codebase, but the public API extension is valid and ready for integration with components like FloatingButton when needed.
tsconfig.json (1)
20-20: Path aliases successfully simplified and verifiedTypeScript path aliases have been simplified to
"@/*": ["src/*"]. All previously removed aliases (@/components, @/tokens, @/icons, @/utils) are no longer used in the codebase. The migration from alias-based imports to relative paths is complete with no remaining references..storybook/utils.ts (1)
22-34: 타입 안전성과 런타임 동작 간의 합리적인 트레이드오프입니다.jest-dom 모듈 확장이 함수 반환 타입에서 직접 참조될 수 없는 TypeScript의 제한으로 인해
any타입 단언을 사용한 접근 방식은 적절합니다. 주석에서 이유를 잘 설명하고 있습니다..coderabbit.yaml (1)
1-20: CodeRabbit 설정이 스키마에 맞게 올바르게 구성되었습니다.
language: "ko-KR"- 유효한 언어 코드profile: "chill"- 유효한 프로필 옵션base_branches- main과 develop 브랜치에서 자동 리뷰 활성화auto_reply: true- 채팅 자동 응답 활성화설정이 스키마 요구사항을 충족합니다.
src/icons/PlusIcon.tsx (1)
16-46: 접근성 패턴이 잘 구현되었습니다.
aria-label제공 여부에 따른 조건부aria-hidden처리가 적절합니다currentColor를 사용하여 테마 호환성을 확보했습니다displayName설정으로 디버깅이 용이합니다package.json (1)
90-90:vite-tsconfig-paths추가가 적절합니다.tsconfig의 경로 별칭을 Vite와 Vitest 환경에서 사용할 수 있게 해주는 필수 플러그인입니다. devDependencies에 올바르게 배치되었습니다.
.github/HUSKY_SETUP.md (1)
134-134: 파일 끝 개행 추가 - 변경 사항 없음..storybook/tsconfig.json (1)
1-9: Storybook TypeScript 설정이 적절합니다.메인 tsconfig를 확장하고, React JSX 트랜스폼과 필요한 타입 정의들을 올바르게 포함하고 있습니다. include 패턴도 소스 및 Storybook 파일을 적절히 커버합니다.
src/stories/tokens/Typography.stories.tsx (1)
2-3: 임포트 경로 변경이 올바릅니다.CSS 모듈 경로로의 마이그레이션이 다른 토큰 스토리 파일들과 일관되게 적용되었습니다.
src/tokens/textStyles.css.ts (1)
8-8: 토큰 간 임포트 경로가 적절히 업데이트되었습니다.상대 경로 CSS 모듈 임포트로 변경이 올바르게 적용되었습니다.
src/stories/tokens/Shadows.stories.tsx (1)
2-3: 임포트 경로 마이그레이션 확인.CSS 모듈 경로 변경이 일관되게 적용되었습니다.
src/stories/tokens/Radius.stories.tsx (1)
2-3: 임포트 경로 변경 완료.다른 토큰 파일들과 동일한 패턴으로 CSS 모듈 임포트가 적용되었습니다.
src/stories/tokens/TextStyles.stories.tsx (1)
2-3: 임포트 마이그레이션이 올바릅니다.CSS 모듈 경로 변경이 일관되게 적용되었습니다.
.storybook/main.ts (2)
3-3: tsconfigPaths 플러그인 추가가 적절합니다.플러그인이 올바르게 구성되었으며, 프로젝트 루트를 정확히 참조하고 있습니다.
Also applies to: 25-29
40-40: 새 "@/icons" alias 추가 확인.새로운 아이콘 컴포넌트를 지원하기 위한 alias 추가가 적절합니다.
src/stories/tokens/Colors.stories.tsx (1)
2-2: Import path is correct for Vanilla Extract setup.The file
src/tokens/colors.css.tsexists and properly exports bothColorPalettetype andcolorsobject. The import path../../tokens/colors.csscorrectly resolves to the.css.tsfile via the Vanilla Extract Vite plugin configured in the project. This is the standard pattern for Vanilla Extract imports.vitest.config.ts (1)
4-4: tsconfigPaths 플러그인 추가는 적절합니다.TypeScript 경로 해석과 Vite의 모듈 해석을 일관되게 유지하는 표준 관행입니다.
Also applies to: 16-16
src/components/Switch/Switch.tsx (1)
1-46: Switch 컴포넌트 구현이 우수합니다.제어 컴포넌트 패턴, ARIA 속성(
role="switch",aria-checked), forwardRef 지원이 모두 올바르게 구현되어 있습니다. 키보드 접근성은 button 요소의 기본 동작으로 처리됩니다.src/index.ts (1)
1-5: 공개 API export가 명확하고 일관적입니다.모든 새 컴포넌트(Button, FloatingButton, Switch)와 관련 모듈(icons, tokens)이 적절하게 export되어 있습니다.
src/components/FloatingButton/FloatingButton.css.ts (1)
1-42: 스타일 구현이 우수합니다.디자인 토큰을 일관되게 사용하며, hover/pressed/disabled 상태가 모두 적절히 처리되어 있습니다.
src/components/Switch/Switch.test.tsx (1)
1-237: 테스트 커버리지가 매우 포괄적입니다.렌더링, ARIA 속성, 이벤트 핸들러, 키보드 탐색, 제어/비제어 동작 등 모든 주요 기능을 철저히 검증하고 있습니다.
src/components/Switch/Switch.stories.ts (1)
1-58: Storybook 스토리가 잘 구성되어 있습니다.메타 설정, argTypes, 조건부 play 함수가 모두 적절하게 구현되어 있으며, ARIA 속성 검증이 포함되어 있습니다.
src/components/Button/Button.stories.ts (2)
18-22: LGTM! variant argType 설정이 적절합니다.Button.css.ts의
buttonVariants키와 일치하는 옵션으로 variant control이 올바르게 구성되었습니다.
104-119: LGTM! Variant 스토리 패턴이 일관성 있게 구현되었습니다.VariantDefault, VariantGray, VariantPrimary 스토리들이 동일한 패턴을 따르며,
data-variant속성 검증을 통해 variant 적용을 테스트합니다.src/components/FloatingButton/FloatingButton.stories.ts (2)
6-27: LGTM! meta 설정이 적절합니다.FloatingButton에 대한 Storybook 설정이 올바르게 구성되었으며, autodocs 태그와 적절한 argTypes가 포함되어 있습니다.
48-62: LGTM! WithText와 Disabled 스토리가 잘 구현되었습니다.기존 Button 스토리 패턴과 일관성 있게 구현되었으며, 접근성 속성 테스트가 포함되어 있습니다.
Also applies to: 64-81
src/components/Switch/Switch.css.ts (2)
4-7: LGTM! 상수 정의가 적절합니다.스위치 크기와 thumb 위치 계산을 위한 상수가 명확하게 정의되어 있어 유지보수가 용이합니다.
9-22: LGTM! switchButton 베이스 스타일이 잘 구현되었습니다.키보드 접근성을 위한
focus-visible스타일이 적절하게 적용되어 있으며, 디자인 토큰을 올바르게 사용하고 있습니다.src/components/Button/Button.tsx (2)
5-17: LGTM! ButtonProps 인터페이스가 잘 정의되었습니다.JSDoc 문서화가 적절하며,
ButtonVariant타입이 CSS 변형과 타입 안전하게 연결되어 있습니다.
38-53: LGTM! 마우스 이벤트 핸들러가 올바르게 구현되었습니다.원본 핸들러를 유지하면서 pressed 상태를 관리하고, disabled 상태에서 pressed 변경을 방지합니다.
src/tokens/index.ts (1)
1-23: LGTM! 토큰 내보내기 경로가 상대 경로로 표준화되었습니다.별칭 경로에서 상대 경로(
./*.css)로의 변경이 일관성 있게 적용되었으며, 타입과 값 내보내기가 모두 유지됩니다.src/components/Button/Button.css.ts (2)
1-17: 기본 버튼 스타일 구조가 잘 설계되었습니다.
baseButton을 내부 스타일로 분리하고styleVariants와 조합하는 패턴이 vanilla-extract 모범 사례를 잘 따르고 있습니다. 토큰 시스템 활용도 일관성 있습니다.
85-86: 하위 호환성을 위한 적절한 처리입니다.기존
buttonexport를 유지하면서 새로운 variant 시스템으로 마이그레이션할 수 있도록 한 점이 좋습니다.src/components/Button/Button.test.tsx (6)
7-16: 모킹 설정이 컴포넌트 구현과 잘 일치합니다.
buttonVariants,iconWrapper,contentWrapper모킹이 실제 CSS 파일의 export 구조를 정확히 반영하고 있습니다.
97-109: variant 테스트가data-variant속성을 통해 정확하게 검증합니다.
rerender를 활용한 variant 전환 테스트 패턴이 효과적입니다.
181-222: pressed 상태 테스트가 마우스 상호작용을 포괄적으로 검증합니다.
userEvent.pointerAPI를 사용하여 mousedown/mouseup/mouseleave 시나리오를 정확하게 시뮬레이션하고 있습니다.
224-242: ref 전달 테스트 구현이 유효합니다.
rerender를 통해 ref 설정을 확인하는 방식은 동작하지만,waitFor나useEffect기반 검증이 더 명확할 수 있습니다. 현재 구현도 테스트 목적에 충분합니다.
308-343: 키보드 접근성 테스트가 잘 구성되었습니다.Enter/Space 키 활성화 및 disabled 상태에서의 키보드 비활성화 테스트가 접근성 요구사항을 적절히 검증합니다.
356-395: 엣지 케이스 테스트가 충분히 포괄적입니다.다중 버튼 독립성, 빈 children, 복잡한 ReactNode 렌더링 테스트가 컴포넌트의 견고성을 잘 검증합니다.
vite.config.ts (4)
10-13: ESM 환경을 위한__dirname처리가 적절합니다.ESM 모듈에서
__dirname을 사용할 수 없는 경우를 대비한 폴백 처리가 잘 되어 있습니다.
17-25: Storybook 관련 의존성 제외 설정이 적절합니다.
@storybook/addon-vitest내부 모듈들을 pre-bundling에서 제외하여 호환성 문제를 방지합니다.
30-47: 커버리지 설정이 프로젝트에 적합합니다.80% 임계값과 다양한 리포터(
text,json,html,lcov) 설정이 CI 통합 및 로컬 개발에 모두 유용합니다.
58-79: Storybook browser test project configuration is well-structured.The Playwright-based headless Chromium test environment is properly configured. The
extends: truepattern for inheriting base configuration is appropriate. Package version compatibility is confirmed:@storybook/addon-vitest10.0.8,@vitest/browser-playwright4.0.13, andvitest4.0.13 are compatible with the current setup. Theplaywright({})empty options are valid and will use sensible defaults.
| config.resolve.alias = { | ||
| ...config.resolve.alias, | ||
| "@": path.resolve(dirname, "../src"), | ||
| "@/components": path.resolve(dirname, "../src/components"), | ||
| "@/tokens": path.resolve(dirname, "../src/tokens"), | ||
| "@/icons": path.resolve(dirname, "../src/icons"), | ||
| "@/stories": path.resolve(dirname, "../src/stories"), | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if tsconfig.json exists and examine its paths configuration
if [ -f "tsconfig.json" ]; then
echo "=== tsconfig.json paths configuration ==="
cat tsconfig.json | jq '.compilerOptions.paths // "No paths found"' 2>/dev/null || rg -B 2 -A 10 '"paths"' tsconfig.json
else
echo "No tsconfig.json found in root"
fiRepository: Team-NumberOne/daepyro-design-system
Length of output: 152
🏁 Script executed:
# Check main.ts to see if tsconfigPaths plugin is being used
echo "=== Checking .storybook/main.ts for tsconfigPaths usage ==="
cat -n .storybook/main.ts | head -50Repository: Team-NumberOne/daepyro-design-system
Length of output: 2165
🏁 Script executed:
# Check for any tsconfig files in .storybook directory
echo "=== Checking for tsconfig files in .storybook directory ==="
find .storybook -name "tsconfig*.json" -type f 2>/dev/nullRepository: Team-NumberOne/daepyro-design-system
Length of output: 164
🏁 Script executed:
# Check .storybook/tsconfig.json paths configuration
echo "=== .storybook/tsconfig.json ==="
cat .storybook/tsconfig.json | jq '.compilerOptions.paths // "No paths found"' 2>/dev/null || cat .storybook/tsconfig.jsonRepository: Team-NumberOne/daepyro-design-system
Length of output: 129
Remove redundant alias entries that are already handled by tsconfigPaths.
The root tsconfig.json defines "@/*": ["src/*"], which the tsconfigPaths plugin (line 26) will automatically resolve. The manual alias entries for @/components, @/tokens, @/icons, and @/stories (lines 38-41) duplicate this behavior and can be removed. Only the bare @ alias (line 37) is necessary since it's not covered by the @/* pattern in tsconfig.
🤖 Prompt for AI Agents
In .storybook/main.ts around lines 35 to 42, the alias entries "@/components",
"@/tokens", "@/icons", and "@/stories" are redundant because tsconfigPaths
(configured earlier) already resolves the "@/*" pattern; remove those four
specific alias lines and keep only the bare "@" alias mapping to "../src" while
leaving the spread of config.resolve.alias intact so tsconfigPaths continues to
handle subpaths.
| "recommended": true, | ||
| "a11y": { | ||
| "noSvgWithoutTitle": "off" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find and examine biome.json
find . -name "biome.json" -type f | head -5Repository: Team-NumberOne/daepyro-design-system
Length of output: 92
🏁 Script executed:
# List files to understand repo structure
git ls-files | head -30Repository: Team-NumberOne/daepyro-design-system
Length of output: 827
🏁 Script executed:
# Search for SVG components mentioned (PlusIcon, Button, Switch, FloatingButton)
fd -e tsx -e ts -e jsx -e js | grep -i "plus\|button\|switch" | head -20Repository: Team-NumberOne/daepyro-design-system
Length of output: 607
🏁 Script executed:
# Examine biome.json around lines 18-21
cat -n ./biome.jsonRepository: Team-NumberOne/daepyro-design-system
Length of output: 949
🏁 Script executed:
# Check PlusIcon.tsx for SVG implementation and a11y attributes
cat -n ./src/icons/PlusIcon.tsxRepository: Team-NumberOne/daepyro-design-system
Length of output: 1296
🏁 Script executed:
# Check if other components contain SVG elements
rg -A 10 "<svg" src/components/ src/icons/Repository: Team-NumberOne/daepyro-design-system
Length of output: 529
🏁 Script executed:
# Check accessibility guidelines in the repo
cat -n ./.github/ACCESSIBILITY.mdRepository: Team-NumberOne/daepyro-design-system
Length of output: 7940
🏁 Script executed:
# Check Button.tsx for SVG usage
cat -n ./src/components/Button/Button.tsxRepository: Team-NumberOne/daepyro-design-system
Length of output: 2405
🏁 Script executed:
# Check FloatingButton.tsx for SVG usage
cat -n ./src/components/FloatingButton/FloatingButton.tsxRepository: Team-NumberOne/daepyro-design-system
Length of output: 2102
🏁 Script executed:
# Check Switch.tsx for SVG usage
cat -n ./src/components/Switch/Switch.tsxRepository: Team-NumberOne/daepyro-design-system
Length of output: 1510
🏁 Script executed:
# Search for any accessibility checklist or PR comments that might mention accessibility verification
find . -name "*.md" -type f | xargs grep -l "accessibility\|a11y\|checklist" 2>/dev/null | head -10Repository: Team-NumberOne/daepyro-design-system
Length of output: 208
🏁 Script executed:
# Check CHECKLIST.md
cat -n ./.github/CHECKLIST.mdRepository: Team-NumberOne/daepyro-design-system
Length of output: 4777
🏁 Script executed:
# Check pull_request_template.md for accessibility mentions
cat -n ./.github/pull_request_template.mdRepository: Team-NumberOne/daepyro-design-system
Length of output: 1994
🏁 Script executed:
# Verify: search for <title> elements in SVG files in the repo
rg "<title" src/icons/ src/components/Repository: Team-NumberOne/daepyro-design-system
Length of output: 62
Reconsider disabling noSvgWithoutTitle globally; use local fixes instead.
The PlusIcon and other SVG components in this PR actually implement proper accessibility through aria-label and aria-hidden attributes (see PlusIcon.tsx lines 28-29, and FloatingButton.tsx line 63). The components are accessible.
However, disabling this linter rule globally contradicts both the PR's accessibility checklist requirements (pull_request_template.md lines 52-56) and the accessibility guidelines in ACCESSIBILITY.md. The issue is that noSvgWithoutTitle specifically checks for <title> child elements in SVGs, not aria-label attributes, even though aria-label is a valid accessibility approach.
Better approaches:
- Add
<title>elements inside SVG elements (e.g.,<title>Icon label</title>) - Or add inline
/* biome-ignore lint/a11y/noSvgWithoutTitle: aria-label provides accessibility */comments at specific SVG usages - Document the rationale if using aria-label exclusively
Global rule disabling weakens linter enforcement across the entire codebase.
🤖 Prompt for AI Agents
In biome.json around lines 18 to 21 the a11y rule "noSvgWithoutTitle" was turned
off globally; instead revert this global disable and restore the rule, then fix
the specific SVGs in the PR by either adding <title> elements inside the SVGs
with appropriate labels or adding inline biome-ignore comments at the exact SVG
usage (/* biome-ignore lint/a11y/noSvgWithoutTitle: aria-label provides
accessibility */) where aria-label is intentionally relied upon, and update
ACCESSIBILITY.md or the PR notes to document the chosen approach so the global
lint enforcement remains intact.
| primary: [ | ||
| baseButton, | ||
| { | ||
| backgroundColor: colors.orange[500], | ||
| color: colors.base.white, | ||
| ":hover": { | ||
| backgroundColor: colors.orange[600], | ||
| }, | ||
| selectors: { | ||
| '&[data-pressed="true"]': { | ||
| backgroundColor: colors.orange[600], | ||
| boxShadow: shadows["btn-pressed"], | ||
| }, | ||
| "&:disabled": { | ||
| backgroundColor: colors.orange[100], | ||
| cursor: "not-allowed", | ||
| }, | ||
| }, | ||
| }, | ||
| ], |
There was a problem hiding this comment.
primary variant의 disabled 상태에 텍스트 색상이 누락되었습니다.
default와 gray variant는 disabled 상태에서 color 속성을 명시적으로 지정하지만, primary variant는 지정하지 않습니다. 이로 인해 orange[100] 배경에 흰색 텍스트가 표시되어 접근성 대비 기준을 충족하지 못할 수 있습니다.
"&:disabled": {
backgroundColor: colors.orange[100],
+ color: colors.orange[300],
cursor: "not-allowed",
},📝 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.
| primary: [ | |
| baseButton, | |
| { | |
| backgroundColor: colors.orange[500], | |
| color: colors.base.white, | |
| ":hover": { | |
| backgroundColor: colors.orange[600], | |
| }, | |
| selectors: { | |
| '&[data-pressed="true"]': { | |
| backgroundColor: colors.orange[600], | |
| boxShadow: shadows["btn-pressed"], | |
| }, | |
| "&:disabled": { | |
| backgroundColor: colors.orange[100], | |
| cursor: "not-allowed", | |
| }, | |
| }, | |
| }, | |
| ], | |
| primary: [ | |
| baseButton, | |
| { | |
| backgroundColor: colors.orange[500], | |
| color: colors.base.white, | |
| ":hover": { | |
| backgroundColor: colors.orange[600], | |
| }, | |
| selectors: { | |
| '&[data-pressed="true"]': { | |
| backgroundColor: colors.orange[600], | |
| boxShadow: shadows["btn-pressed"], | |
| }, | |
| "&:disabled": { | |
| backgroundColor: colors.orange[100], | |
| color: colors.orange[300], | |
| cursor: "not-allowed", | |
| }, | |
| }, | |
| }, | |
| ], |
🤖 Prompt for AI Agents
In src/components/Button/Button.css.ts around lines 63-82, the primary variant's
disabled selector omits a text color which can produce poor contrast on the
orange[100] background; update the '&:disabled' (or '&[data-pressed="true"]'
disabled case if present) selector to explicitly set a readable color (use the
same color token used by default/gray variants, e.g. colors.base.gray[400] or
the existing disabled text token) so the button meets contrast/accessibility
requirements.
| export const switchTrack = style({ | ||
| position: "relative", | ||
| display: "inline-block", | ||
| width: SWITCH_WIDTH, | ||
| height: SWITCH_HEIGHT, | ||
| borderRadius: radius.full, | ||
| backgroundColor: colors.grey[100], | ||
| transition: "background-color 0.2s", | ||
| selectors: { | ||
| [`${switchButton}[data-checked="true"] &`]: { | ||
| backgroundColor: colors.orange[500], | ||
| }, | ||
| [`${switchButton}:active[data-checked="false"] &`]: { | ||
| backgroundColor: colors.grey[200], | ||
| }, | ||
| [`${switchButton}:active[data-checked="true"] &`]: { | ||
| backgroundColor: colors.orange[600], | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| export const switchThumb = style({ | ||
| position: "absolute", | ||
| top: THUMB_OFFSET, | ||
| left: THUMB_OFFSET, | ||
| width: THUMB_SIZE, | ||
| height: THUMB_SIZE, | ||
| borderRadius: radius.full, | ||
| backgroundColor: colors.base.white, | ||
| transition: "transform 0.2s", | ||
| transform: "translateX(0)", | ||
| selectors: { | ||
| [`${switchButton}[data-checked="true"] &`]: { | ||
| transform: `translateX(calc(${SWITCH_WIDTH} - ${THUMB_SIZE} - ${THUMB_OFFSET} * 2))`, | ||
| }, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for disabled prop usage in Switch component
rg -n "disabled" src/components/Switch/Repository: Team-NumberOne/daepyro-design-system
Length of output: 62
🏁 Script executed:
#!/bin/bash
# Find Switch component files
fd -e tsx -e ts "Switch" src/components/Repository: Team-NumberOne/daepyro-design-system
Length of output: 226
🏁 Script executed:
#!/bin/bash
# Check the Switch.tsx file for disabled prop
rg -n "disabled" src/components/Switch/ -B 2 -A 2Repository: Team-NumberOne/daepyro-design-system
Length of output: 62
🏁 Script executed:
#!/bin/bash
# Check the size of Switch.tsx
wc -l src/components/Switch/Switch.tsxRepository: Team-NumberOne/daepyro-design-system
Length of output: 115
🏁 Script executed:
#!/bin/bash
# Read Switch.tsx to see component props and implementation
cat -n src/components/Switch/Switch.tsxRepository: Team-NumberOne/daepyro-design-system
Length of output: 1510
🏁 Script executed:
#!/bin/bash
# Check the full CSS file
cat -n src/components/Switch/Switch.css.tsRepository: Team-NumberOne/daepyro-design-system
Length of output: 1969
Add disabled state styling for the Switch component.
The Switch component supports the disabled attribute through ButtonHTMLAttributes, but currently lacks visual styling for the disabled state in Switch.css.ts. When a Switch is disabled, it appears identical to its enabled state, creating poor accessibility and UX. Add disabled state styles to switchButton, switchTrack, and switchThumb selectors using ${switchButton}:disabled to provide clear visual feedback (e.g., reduced opacity, cursor not-allowed, muted colors).
🤖 Prompt for AI Agents
In src/components/Switch/Switch.css.ts around lines 24 to 60, add disabled-state
rules for switchButton, switchTrack and switchThumb using the
`${switchButton}:disabled` selector: update switchButton to set cursor:
"not-allowed", reduced opacity and disable pointer events for the disabled
selector; update switchTrack to use muted/greyed background colors and reduced
opacity when `${switchButton}:disabled &` (and also
`${switchButton}:disabled[data-checked="true"] &` if needed); update switchThumb
to reduce contrast (muted backgroundColor), remove pointer interactions and
ensure transform remains correct under the `${switchButton}:disabled &` selector
so the visual position still reflects checked state. Ensure transitions remain
but disabled state is visually distinct (lower opacity, muted colors, cursor
not-allowed).
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
vite.config.ts (2)
44-45: globals 매핑 값이 올바르지 않습니다.
globals는 UMD 빌드에서 외부 모듈을 전역 변수 이름으로 매핑하는 용도입니다."react/jsx-runtime": "react/jsx-runtime"형태는 올바른 전역 변수 이름이 아닙니다. 현재 ES/CJS 포맷만 사용하므로 실제 영향은 없지만, 향후 UMD 지원 시 문제가 될 수 있습니다.globals: { react: "React", "react-dom": "ReactDOM", - "react/jsx-runtime": "react/jsx-runtime", + "react/jsx-runtime": "jsxRuntime", },
46-51: assetFileNames 폴백 값에 확장자가 누락되었습니다.CSS가 아닌 에셋에 대해
"asset"만 반환하면 확장자가 없어 파일 처리에 문제가 생길 수 있습니다. 원본 파일명을 유지하거나 적절한 폴백을 사용하는 것이 좋습니다.assetFileNames: (assetInfo) => { if (assetInfo.name?.endsWith(".css")) { return "styles.css"; } - return assetInfo.name || "asset"; + return assetInfo.name || "assets/[name]-[hash][extname]"; },
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
vite.config.ts(1 hunks)
🔇 Additional comments (7)
vite.config.ts (7)
1-9: Import 구성이 적절합니다.
vitest/config에서defineConfig를 가져오는 것은 Vitest 테스트 설정을 통합하는 데 올바른 접근 방식입니다. 추가된 모든 import가 설정된 기능에 필요합니다.
11-14: ESM/CJS 호환성을 위한 올바른 패턴입니다.
__dirname이 ESM 환경에서 사용할 수 없을 때를 대비한 폴백 처리가 적절합니다.
17-30: 플러그인 설정이 적절합니다.
tsconfigPaths()추가로 수동 alias 설정이 불필요해졌고,dts플러그인에서 테스트/스토리 파일을 제외하는 것은 라이브러리 빌드에 적합한 구성입니다.
56-64: Storybook 통합을 위한 올바른 optimizeDeps 설정입니다.Storybook 내부 모듈을 pre-bundling에서 제외하는 것은
@storybook/addon-vitest통합에 필요한 구성입니다.
65-86: 테스트 커버리지 설정이 적절합니다.v8 프로바이더와 80% 임계값은 합리적인 기본 설정입니다. 테스트/스토리/설정 파일을 커버리지에서 제외한 것도 적절합니다.
87-96: unit 프로젝트 설정이 적절합니다.jsdom 환경에서
*.test.*파일을 대상으로 단위 테스트를 실행하는 구성이 올바릅니다.
97-118: Consider simplifying the playwright provider invocation.The
playwright({})syntax is valid per Vitest documentation, but passing an empty object is unnecessary. Useplaywright()without arguments unless you need to configurelaunchOptionsoractionTimeout. Refer to the Vitest Playwright configuration guide for available options.
변경 사항
변경 유형
관련 이슈
체크리스트
컴포넌트 관련 (컴포넌트 추가/수정 시)
src/components/디렉토리에 올바르게 위치해 있습니다src/index.ts에서 export 되었습니다스토리북 관련
*.stories.ts)이 생성/업데이트되었습니다autodocs태그가 포함되어 있습니다품질 관리
pnpm run lint통과pnpm run check(타입 체크) 통과pnpm test --run통과pnpm run build)접근성
문서화
테스트
스크린샷 / 데모
추가 정보
Summary by CodeRabbit
새로운 기능
문서화
Chores
✏️ Tip: You can customize this high-level summary in your review settings.