feat: 버튼 컴포넌트 구현 및 디자인 토큰 생성 및 크로마틱 워크플로우 생성 (#49) #51
Conversation
|
""" Walkthrough이 변경사항은 Button 컴포넌트와 관련 스타일, 스토리북 스토리, 디자인 토큰(color, radius), 글로벌 CSS 리셋을 새로 도입하고, Storybook Chromatic 배포를 위한 GitHub Actions 워크플로우를 추가합니다. 또한, Storybook과 Vite 설정을 Vanilla Extract와 TypeScript 경로 플러그인으로 확장합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Developer
participant GitHub Actions
participant Chromatic
participant Storybook
Developer->>GitHub Actions: PR 생성(main 브랜치 대상)
GitHub Actions->>Repository: 코드 체크아웃
GitHub Actions->>GitHub Actions: pnpm, Node.js, 의존성 설치
GitHub Actions->>Chromatic: Storybook 빌드 및 배포
Chromatic->>GitHub Actions: 빌드 결과 및 URL 반환
GitHub Actions->>GitHub PR: Storybook/Chromatic 링크 코멘트 작성
Assessment against linked issues
Assessment against linked issues: Out-of-scope changes해당 이슈(#49)의 요구사항과 무관한 변경사항은 발견되지 않았습니다. Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
src/components/ui/Button/Button.css.tsOops! Something went wrong! :( ESLint: 9.27.0 ESLint couldn't find the plugin "eslint-plugin-react-hooks". (The package "eslint-plugin-react-hooks" was not found when loaded as a Node module from the directory "".) It's likely that the plugin isn't installed correctly. Try reinstalling by running the following: The plugin "eslint-plugin-react-hooks" was referenced from the config file in " » eslint-config-next/core-web-vitals » /node_modules/.pnpm/eslint-config-next@15.3.2_eslint@9.27.0_jiti@2.4.2__typescript@5.8.3/node_modules/eslint-config-next/index.js". If you still can't figure out the problem, please see https://eslint.org/docs/latest/use/troubleshooting. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
|
🎨 Storybook Preview: https://685a32a1c0bbd269fdb67af4-urzrnnhvtx.chromatic.com/ |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (8)
src/styles/layers.css.ts (1)
1-3:reset레이어 네이밍이 모호할 수 있습니다
향후 다른 전역 레이어가 추가될 경우reset만으로는 역할이 불분명해질 수 있습니다.globalReset또는baseReset처럼 의도를 좀 더 명확히 드러내는 이름을 고려해 보세요.package.json (1)
19-26: 런타임 번들 크기 최소화를 위해@vanilla-extract/recipes위치 재검토 필요
@vanilla-extract/recipes는 빌드-타임(compile-time) 전용 의존성입니다. 런타임에 포함될 이유가 없으므로"devDependencies"로 이동하면 번들 크기와 설치 시간이 줄어듭니다.- "@vanilla-extract/recipes": "^0.5.7", +// devDependencies 블록으로 이동src/styles/colors.css.ts (1)
3-7:common팔레트의 스케일 키 일관성
다른 팔레트는 100, 95 … 식으로 감소하는데common은100과0두 개만 존재합니다. 필요하다면 0 대신 0 or 5 스텝을 유지하거나black/white상수로 분리하여 스케일 규칙을 일관성 있게 유지해 주세요.src/app/layout.tsx (1)
1-2: 전역 reset 경로 확정 필요 (.css.tsvs.css)
reset.css.ts파일을 side-effect import 하려면 일반적으로import "@/styles/reset.css.ts";로 작성하고, vanilla-extract 플러그인이 빌드시.css로 치환합니다.
지금처럼.css확장자를 직접 지정하면 IDE 경로 인식이 안 되거나 Jest 모킹 시 경로 resolve 실패가 발생할 수 있습니다.src/components/ui/Button/Button.css.ts (2)
38-58: 고정 너비 값보다 최소 너비 사용을 고려하세요.버튼 크기에 고정 너비(
width)를 사용하면 텍스트 길이에 따라 내용이 잘릴 수 있습니다.minWidth를 사용하면 더 유연한 레이아웃을 제공할 수 있습니다.small: { - width: "63px", + minWidth: "63px", height: "32px", padding: "9px 20px", fontSize: "13px", borderRadius: radius[80], }, medium: { - width: "80px", + minWidth: "80px", height: "40px", padding: "9px 20px", fontSize: "15px", borderRadius: radius[100], }, large: { - width: "98px", + minWidth: "98px", height: "48px", padding: "12px 28px", fontSize: "16px", borderRadius: radius[120], },
31-36: disabled 변형에 호버 상태 비활성화 추가를 고려하세요.disabled 변형에서 마우스 호버 때 스타일 변화를 방지하기 위해 명시적으로 호버 상태를 비활성화하는 것이 좋습니다.
disabled: { backgroundColor: colors.coolNeutral[96], color: colors.neutral[70], cursor: "not-allowed", + ":hover": { + backgroundColor: colors.coolNeutral[96], + }, },src/components/ui/Button/Button.tsx (1)
14-27: disabled 변형 사용 시 자동으로 disabled 속성 설정을 고려하세요.현재
variant="disabled"를 사용해도 버튼이 실제로 비활성화되지 않습니다. 사용자 경험을 위해 disabled 변형일 때 자동으로disabled속성을 설정하는 것을 고려해보세요.export const Button = ({ children, variant = "primary", size = "medium", ...props }: ButtonProps) => { const variantClass = button({ variant, size }); return ( - <button className={variantClass} {...props}> + <button + className={variantClass} + disabled={variant === "disabled" || props.disabled} + {...props} + > {children} </button> ); };src/styles/reset.css.ts (1)
47-58: 전역 버튼 커서 설정이 disabled 버튼과 충돌할 수 있습니다.모든 버튼에
cursor: pointer를 설정하면 disabled 버튼의cursor: not-allowed스타일과 충돌할 수 있습니다. 좀 더 구체적인 선택자 사용을 고려해보세요.globalStyle("button", { "@layer": { [reset]: { padding: 0, border: "none", background: "none", outline: "none", boxShadow: "none", - cursor: "pointer", }, }, }); +globalStyle("button:not(:disabled)", { + "@layer": { + [reset]: { + cursor: "pointer", + }, + }, +});
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (14)
.github/workflows/chromatic.yml(1 hunks).storybook/main.ts(2 hunks).storybook/preview.ts(1 hunks)package.json(2 hunks)src/app/layout.tsx(1 hunks)src/components/ui/Button/Button.css.ts(1 hunks)src/components/ui/Button/Button.stories.tsx(1 hunks)src/components/ui/Button/Button.tsx(1 hunks)src/components/ui/Button/index.ts(1 hunks)src/styles/colors.css.ts(1 hunks)src/styles/index.ts(1 hunks)src/styles/layers.css.ts(1 hunks)src/styles/radius.css.ts(1 hunks)src/styles/reset.css.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
`src/styles/*.{ts,tsx}`: 전역 스타일 파일은 src/styles 폴더에 배치한다.
src/styles/*.{ts,tsx}: 전역 스타일 파일은 src/styles 폴더에 배치한다.
src/styles/index.tssrc/styles/layers.css.tssrc/styles/colors.css.tssrc/styles/reset.css.tssrc/styles/radius.css.ts
`**/*.css.ts`: vanilla-extract 스타일 파일은 해당 컴포넌트와 같은 폴더에 배치한다.
**/*.css.ts: vanilla-extract 스타일 파일은 해당 컴포넌트와 같은 폴더에 배치한다.
src/styles/layers.css.tssrc/styles/colors.css.tssrc/components/ui/Button/Button.css.tssrc/styles/reset.css.tssrc/styles/radius.css.ts
🧬 Code Graph Analysis (2)
src/styles/reset.css.ts (1)
src/styles/layers.css.ts (1)
reset(3-3)
src/components/ui/Button/Button.tsx (1)
src/components/ui/Button/Button.css.ts (1)
button(5-72)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: test
🔇 Additional comments (16)
package.json (2)
46-48: Vite 플러그인 버전 호환성 확인
@vanilla-extract/vite-plugin@5.0.7과 Vite, Next-js-vite 설정(Storybook 포함)이 모두 5.x 호환인지 확인해 주세요. 최근 Next 15 & Vite 플러그인 조합에서 5.0.8 이상이 요구되는 이슈가 있었습니다.
51-52: Chromatic 라이선스/토큰 설정 누락 여부 점검
chromatic패키지만 추가되고 환경 변수(CHROMATIC_PROJECT_TOKEN) 설정이나 npm script 호출이 보이지 않습니다. 워크플로 정의 파일과 함께 토큰이 올바르게 주입되는지 확인해 주세요.src/app/layout.tsx (1)
8-11: 타이틀 변경에 따른 SEO 영향 확인
Timeat→Eat-da변경으로 기존 검색 색인에 영향이 있을 수 있습니다. 릴리스 노트에 기록하고 메타 태그(og:title 등)도 함께 업데이트했는지 확인 바랍니다.src/styles/index.ts (1)
1-2: LGTM! 디자인 토큰 중앙화가 잘 구현되었습니다.디자인 토큰을 중앙화하는 배럴 파일 패턴이 적절하게 적용되어 있으며, 코딩 가이드라인에 따라
src/styles폴더에 배치되었습니다. 이는 컴포넌트 전반에서 일관된 스타일 사용을 가능하게 합니다.src/components/ui/Button/index.ts (1)
1-2: LGTM! 컴포넌트 구조가 잘 설계되었습니다.Button 컴포넌트와 스타일을 명확하게 분리하여 내보내는 배럴 파일 패턴이 적절하게 적용되어 있습니다. 이는 컴포넌트의 public API를 명확하게 정의하고 사용하기 쉽게 만듭니다.
.storybook/preview.ts (1)
1-1: LGTM! CSS 리셋이 적절하게 적용되었습니다.CSS 리셋을 Storybook에 import하여 모든 스토리에 일관된 기본 스타일을 적용하는 것이 좋은 접근입니다. 이는 컴포넌트들이 예측 가능한 스타일 환경에서 렌더링되도록 보장합니다.
.storybook/main.ts (2)
2-3: LGTM! 필요한 Vite 플러그인들이 올바르게 import되었습니다.Vanilla Extract와 TypeScript 경로 해석을 위한 플러그인들이 적절하게 import되어 있습니다.
13-20: LGTM! Vite 설정이 올바르게 확장되었습니다.
viteFinal함수를 통해 Storybook의 Vite 설정을 확장하는 방식이 적절합니다. 기존 플러그인들을 유지하면서 새로운 플러그인들을 추가하는 방식이 안전합니다..github/workflows/chromatic.yml (2)
33-34: 설정 확인 요청: 자동 승인 옵션 검토
onlyChanged: true와autoAcceptChanges: true설정이 의도된 것인지 확인이 필요합니다.
onlyChanged: true: 변경된 스토리만 배포 (성능상 좋음)autoAcceptChanges: true: 모든 변경사항을 자동으로 승인 (검토 프로세스 생략)
autoAcceptChanges: true는 시각적 회귀 테스트의 목적을 무효화할 수 있습니다. 이 설정이 의도된 것인지 확인해 주세요.
36-43: LGTM! PR 댓글 기능이 잘 구현되었습니다.
if: always()조건과 함께 PR에 Storybook 및 Chromatic 링크를 댓글로 추가하는 기능이 잘 구현되어 있습니다. 고유한 태그를 사용하여 PR별로 댓글을 관리하는 것도 좋은 접근입니다.src/components/ui/Button/Button.css.ts (1)
1-72: 전체적으로 잘 구현된 버튼 스타일 레시피입니다.vanilla-extract recipe를 활용한 구조화된 스타일링이 잘 되어 있고, 디자인 토큰을 적절히 활용하고 있습니다. 다양한 변형과 크기 옵션도 잘 정의되어 있습니다.
src/components/ui/Button/Button.tsx (1)
1-27: 깔끔하고 타입 안전한 Button 컴포넌트 구현입니다.TypeScript 타입 정의가 잘 되어 있고, props 전달 방식도 적절합니다. vanilla-extract 스타일링과의 연동도 잘 구현되어 있습니다.
src/components/ui/Button/Button.stories.tsx (2)
5-26: 포괄적이고 잘 구성된 Storybook 메타 설정입니다.모든 주요 props에 대한 컨트롤이 적절히 설정되어 있고, argTypes 정의도 명확합니다. onClick 액션 로깅도 잘 추가되어 있습니다.
61-70: 다양한 크기를 한 번에 비교할 수 있는 유용한 스토리입니다.Sizes 스토리는 모든 크기 변형을 시각적으로 비교할 수 있어 디자인 검토에 매우 유용합니다. flexWrap 속성으로 반응형 레이아웃도 고려되어 있습니다.
src/styles/reset.css.ts (2)
1-126: 포괄적이고 잘 구성된 CSS 리셋입니다.CSS 레이어를 활용한 체계적인 구성과 다양한 요소들에 대한 일관된 스타일 초기화가 잘 되어 있습니다. 브라우저 간 일관성을 보장하는 데 도움이 될 것입니다.
103-103: CSS 사용자 정의 속성이 정의되어 있는지 확인하세요.
var(--font-family)를 사용하고 있지만 이 변수가 정의되어 있는지 확인이 필요합니다. 정의되지 않은 경우 브라우저 기본 글꼴이 사용됩니다.다음 스크립트로 --font-family 변수가 정의된 위치를 확인해보세요:
#!/bin/bash # Description: --font-family 변수가 정의된 위치를 찾습니다. # CSS 파일에서 --font-family 정의 검색 rg -A 3 -B 3 "--font-family" # TypeScript/JavaScript 파일에서 font-family 관련 코드 검색 rg -A 3 -B 3 "font-family"
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/components/ui/Button/Button.css.ts (1)
43-72: size variant의 수치 설정에 개선 여지가 있습니다.각 size별로 width가 고정되어 있어 텍스트 길이에 따른 유연성이 부족할 수 있습니다. 특히 다국어 지원 시 문제가 될 수 있습니다.
다음과 같이 개선을 고려해보세요:
small: { - width: "63px", + minWidth: "63px", height: "32px", padding: "9px 20px", fontSize: "13px", borderRadius: radius[80], }, medium: { - width: "80px", + minWidth: "80px", height: "40px", padding: "9px 20px", fontSize: "15px", borderRadius: radius[100], }, large: { - width: "98px", + minWidth: "98px", height: "48px", padding: "12px 28px", fontSize: "16px", borderRadius: radius[120], },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/ui/Button/Button.css.ts(1 hunks)src/components/ui/Button/Button.stories.tsx(1 hunks)src/components/ui/Button/Button.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/ui/Button/Button.stories.tsx
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.css.ts`: vanilla-extract 스타일 파일은 해당 컴포넌트와 같은 폴더에 배치한다.
**/*.css.ts: vanilla-extract 스타일 파일은 해당 컴포넌트와 같은 폴더에 배치한다.
📄 Source: CodeRabbit Inference Engine (.cursor/rules/nextjs-folder-structure.mdc)
List of files the instruction was applied to:
src/components/ui/Button/Button.css.ts
🧬 Code Graph Analysis (1)
src/components/ui/Button/Button.tsx (1)
src/components/ui/Button/Button.css.ts (1)
button(5-78)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: test
🔇 Additional comments (7)
src/components/ui/Button/Button.tsx (3)
1-3: 타입 전용 import 사용으로 번들 크기 최적화가 잘 되었습니다.
ComponentPropsWithoutRef를 타입 전용으로 import하여 런타임에 불필요한 코드가 포함되지 않도록 최적화했습니다.
5-12: 타입 정의가 명확하고 확장성이 뛰어납니다.ButtonVariant와 ButtonSize 타입을 별도로 정의하고, ComponentPropsWithoutRef를 통해 네이티브 button 속성을 모두 상속받도록 구현했습니다. 타입 안전성과 재사용성이 우수합니다.
14-27: 컴포넌트 구현이 깔끔하고 props 처리가 적절합니다.기본값 설정과 props spreading을 통해 유연한 컴포넌트를 구현했습니다. vanilla-extract의 recipe 함수를 활용한 동적 클래스 생성도 잘 구현되었습니다.
src/components/ui/Button/Button.css.ts (4)
1-3: 디자인 토큰 import가 올바르게 구성되었습니다.vanilla-extract recipes와 디자인 토큰을 적절히 import하여 일관된 디자인 시스템을 구축할 수 있는 기반이 마련되었습니다.
5-24: base 스타일과 disabled 상태 처리가 우수합니다.flexbox를 활용한 레이아웃, 부드러운 transition 효과, 접근성을 고려한 disabled 상태 스타일링이 잘 구현되었습니다.
cursor: "not-allowed"로 사용자 경험도 향상시켰습니다.
25-42: variant별 색상 조합이 명확하고 hover 상태 처리가 적절합니다.primary, dark, assistive 각 variant의 색상 조합이 디자인 토큰을 활용하여 일관성 있게 정의되었고, hover 상태의 색상 변화도 자연스럽습니다.
74-78: 기본값 설정이 적절합니다.primary variant와 medium size를 기본값으로 설정한 것이 일반적인 사용 패턴에 적합합니다.
<button className={`${variantClass} ${className ?? ""}`}>{children}</button>사용할 땐 <Button variant="primary" className={myCustomStyle}>
커스텀~~~~~~~~~~~~~
</Button>이런 식으로 사용해주면 되는거 같아요!! |
|
@wkdtnqls0506 오오옹 고렇게 쓰는군요,,! 감사합니다! |
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/components/ui/Button/Button.tsx (1)
14-20: children을 명시적으로 destructuring할 필요가 없습니다.ComponentProps<"button">에 이미 children이 포함되어 있으므로, 별도로 children을 destructuring하지 않아도 됩니다. 이는 이전 리뷰에서도 지적된 사항입니다.
다음과 같이 수정할 수 있습니다:
export const Button = ({ - children, variant = "primary", size = "medium", className, ...props }: ButtonProps) => {
🧹 Nitpick comments (1)
src/components/ui/Button/Button.tsx (1)
21-28: 구현이 깔끔하지만 className 합성에서 작은 개선 여지가 있습니다.전반적으로 잘 구현되었습니다. 다만 className 합성에서 불필요한 공백을 방지하기 위해 다음과 같이 개선할 수 있습니다:
- <button className={`${variantClass} ${className ?? ""}`} {...props}> + <button className={[variantClass, className].filter(Boolean).join(" ")} {...props}>또는 더 간단하게:
- <button className={`${variantClass} ${className ?? ""}`} {...props}> + <button className={`${variantClass}${className ? ` ${className}` : ""}`} {...props}>하지만 현재 구현도 충분히 동작하므로 선택사항입니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/components/ui/Button/Button.tsx(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/ui/Button/Button.tsx (1)
src/components/ui/Button/Button.css.ts (1)
button(5-78)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: test
🔇 Additional comments (2)
src/components/ui/Button/Button.tsx (2)
1-3: 깔끔한 import 구조입니다.React의 ComponentProps를 사용하여 타입 안전성을 확보하고, 스타일링을 위한 button recipe를 적절히 import하고 있습니다.
5-12: 타입 정의가 잘 구성되어 있습니다.ButtonVariant와 ButtonSize의 리터럴 타입이 CSS recipe와 일치하며, ButtonProps가 적절히 export되어 있어 외부에서 사용할 수 있습니다. ComponentProps<"button">을 확장하여 모든 네이티브 button 속성을 포함한 것도 좋습니다.
✅ 이슈 번호
close #49
🪄 작업 내용 (변경 사항)
src/styles/colors.css.tssrc/styles/radius.css.tssrc/styles/reset.css.tssrc/components/ui/Button.github/workflows/chromatic.yml📸 스크린샷
💡 설명
1. 디자인 토큰(color, radius) 및 글로벌 스타일 생성
버튼 컴포넌트 구현에 필요한
color,radius값을 디자인 토큰으로 생성하였습니다.또한
reset.css.ts를 생성하여 브라우저 기본 스타일을 초기화하였습니다.2. Button 컴포넌트 생성
src/components/ui/Button경로에 Button 컴포넌트를 구현하였습니다.버튼 컴포넌트가 필요한 곳에 다음과 같이 사용하시면 됩니다.
속성을 지정해두지 않으면 기본적으로
variant='primary' size='medium'값이 적용됩니다.vanilla-extract의
recipe를 사용하여Button.css.ts를 구현하였습니다!variant와size에 따라 자동으로 스타일이 조합되도록recipe를 사용해 CSS 클래스를 구성했습니다.variant="dark"와size="large"조합 시,recipe에 의해 생성된 클래스입니다.variant와size에 따라 클래스가 조합되며, 디자인 토큰이 CSS 변수로 적용된 것을 확인할 수 있습니다.3. Chromatic Workflow 생성
Storybook을 배포하기 위해 Chromatic Workflow를 생성했습니다.
🗣️ 리뷰어에게 전달 사항
📍 트러블 슈팅
Storybook에서 vanilla-extract 스타일 적용 오류 해결
Storybook 실행 시 아래와 같은 오류가 발생하였습니다.

이는 vanilla-extract 스타일이 Storybook 내에서 올바르게 처리되지 않아 CSS 변수가 생성되지 못한 것이 원인이었습니다.
vanilla-extract는 빌드 타임에 CSS를 추출하는 방식인데, Storybook은 기본 설정만으로는
.css.ts파일을 인식하거나 처리하지 못합니다.따라서 Storybook 공식 문서 를 참고하여
@vanilla-extract/vite-plugin을 설치하고,.storybook/main.ts에 Vite 설정을 추가하여 해결했습니다.vanillaExtractPlugin()은 Vite가.css.ts파일을 읽고 실제 CSS로 추출할 수 있도록 도와줍니다.Summary by CodeRabbit
New Features
Style
Documentation
Chores
Refactor