Conversation
Walkthrough온보딩 페이지와 라우트가 추가되고, 시/분/AM·PM 휠 기반 TimePicker UI가 도입되며 디자인시스템에 WheelPicker 래퍼·타입·의존성·레지스트리 추가와 tsconfig/vite 설정 및 공통 색상 토큰·import 경로 조정이 적용되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Router
participant OnBoarding
participant TimePicker
participant DS as DesignSystem/WheelPicker
User->>Router: /onBoarding 요청
Router->>OnBoarding: <OnBoarding/> 렌더
OnBoarding->>TimePicker: <TimePicker/> 렌더
TimePicker->>DS: WheelPickerWrapper & WheelPicker 렌더 (hour, minute, meridiem)
User->>TimePicker: 휠 조작 / 확인 → onSave(selected) 또는 취소 → onCancel()
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🧪 Generate unit tests
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. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
|
✅ Storybook chromatic 배포 확인: |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (10)
packages/design-system/package.json (1)
54-54: @ncdai/react-wheel-picker peerDependencies 호환성 확인 완료
"^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"로 React ^19.1.0과 충돌 없음- license: MIT, dependencies 없음
- 선택: 디자인 시스템 번들러에 CSS sideEffects 설정 추가 (
"sideEffects": ["**/*.css"]등)- 선택: 번들 크기 점검
apps/client/src/routes/routesConfig.ts (1)
18-21: 경로 네이밍을 kebab-case로 통일다른 경로들이 모두 소문자·kebab-case(
/my-bookmarks)를 사용하는 만큼, 온보딩 경로도/onboarding으로 변경을 권장합니다.onBoarding: { title: '온보딩', - path: '/onBoarding', + path: '/onboarding', },apps/client/src/pages/onBoarding/OnBoarding.tsx (1)
1-5: 폴더/경로 케이스 통일(onBoarding → onboarding) 및 문구 현지화페이지·폴더·라우트 키를 모두
onboarding으로 통일하면 추후 혼선을 줄일 수 있습니다. 화면 문구는 임시라면 OK이나, 병합 전 i18n 적용 또는 최종 문구로 교체를 권장합니다.apps/client/src/routes/router.tsx (2)
4-4: 온디맨드 로딩으로 초기 번들 최적화OnBoarding 페이지를 lazy import로 전환해 초기 로드를 줄이는 것을 권장합니다.
아래와 같이 변경 제안:
-import OnBoarding from '@pages/onBoarding/OnBoarding'; +import { lazy, Suspense } from 'react'; +const OnBoarding = lazy(() => import('@pages/onBoarding/OnBoarding'));
31-34: Lazy 컴포넌트는 Suspense로 감싸기lazy 도입 시 Suspense로 감싸주세요. Fallback은 스켈레톤 등으로 교체 가능.
{ - path: ROUTES_CONFIG.onBoarding.path, - element: <OnBoarding />, + path: ROUTES_CONFIG.onBoarding.path, + element: ( + <Suspense fallback={null}> + <OnBoarding /> + </Suspense> + ), },packages/design-system/src/components/wheelPicker/WheelPicker.tsx (1)
27-38: selected 가시성 개선 여지: selectedOptionItem 스타일도 오버라이드 가능하도록 기본값을 제안현재
optionItem과highlightWrapper만 지정되어 있어 선택 항목의 대비가 약할 수 있습니다. 기본 selected 스타일을 살짝 부여하고, 외부에서 덮어쓸 수 있게 병합해두면 UX가 선명해집니다.예시 diff:
<WheelPickerPrimitive.WheelPicker classNames={{ ...classNames, optionItem: cn( 'text-gray300 !font-medium !text-[1.6rem] !leading-[150%] !tracking-[-0.025em]', classNames?.optionItem ), + selectedOptionItem: cn( + 'text-font-black-1', + classNames?.selectedOptionItem + ), highlightWrapper: cn( 'bg-white border-y-[0.5px] border-gray300 !font-medium !text-[1.6rem] !leading-[150%] !tracking-[-0.025em]', classNames?.highlightWrapper ), }}packages/design-system/src/components/index.ts (1)
12-13: WheelPickerClassNames 타입도 re-export 해주세요.외부에서 커스텀 스타일 키 입력 시 타입 보조가 됩니다. 현재
WheelPickerOption만 노출되어 있어 아쉽습니다.적용 diff:
export { WheelPicker, WheelPickerWrapper } from './wheelPicker/WheelPicker'; -export type { WheelPickerOption } from './wheelPicker/WheelPicker'; +export type { WheelPickerOption, WheelPickerClassNames } from './wheelPicker/WheelPicker';apps/client/src/pages/onBoarding/components/timePicker/TimePicker.tsx (3)
29-31: z-index 유틸 오타 및 장식 문자 접근성 처리
z-2는 스케일에 없을 가능성이 큽니다. 임의 값으로z-[2]를 사용하고, 콜론 문자는 스크린리더에 불필요하므로aria-hidden을 추가해 주세요.적용 diff:
- <p className="body2-m z-2 mx-[0.8rem] flex h-[5.6rem] items-center justify-center"> + <p aria-hidden="true" className="body2-m z-[2] mx-[0.8rem] flex h-[5.6rem] items-center justify-center"> : </p>
27-35: 휠 별 ARIA 레이블 추가로 접근성 보완각 휠에 의미 있는 레이블을 부여해 스크린리더 탐색성을 개선하세요.
적용 diff:
- <WheelPicker options={hourOptions} infinite optionItemHeight={56} /> + <WheelPicker aria-label="시간(시)" options={hourOptions} infinite optionItemHeight={56} /> ... - <WheelPicker options={minuteOptions} infinite optionItemHeight={56} /> + <WheelPicker aria-label="시간(분)" options={minuteOptions} infinite optionItemHeight={56} /> ... - <WheelPicker options={meridiemOptions} optionItemHeight={56} /> + <WheelPicker aria-label="오전/오후" options={meridiemOptions} optionItemHeight={56} />
8-23: 렌더링마다 옵션 배열 재생성 회피(미세 최적화)빈번한 재렌더링 시 불필요할 수 있습니다. 컴포넌트 밖 상수로 승격하거나
useMemo사용을 고려해 주세요.예시:
// 컴포넌트 밖 const createArray = (length: number, add = 0): WheelPickerOption[] => Array.from({ length }, (_, i) => { const value = i + add; return { label: value.toString().padStart(2, '0'), value: value.toString() }; }); const HOUR_OPTIONS = createArray(12, 1); const MINUTE_OPTIONS = createArray(60); const MERIDIEM_OPTIONS: WheelPickerOption[] = [{ label: 'AM', value: 'AM' }, { label: 'PM', value: 'PM' }]; // 컴포넌트 내부에서는 위 상수 사용
📜 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.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
apps/client/src/pages/onBoarding/OnBoarding.tsx(1 hunks)apps/client/src/pages/onBoarding/components/timePicker/TimePicker.tsx(1 hunks)apps/client/src/routes/router.tsx(2 hunks)apps/client/src/routes/routesConfig.ts(1 hunks)packages/design-system/components.json(1 hunks)packages/design-system/package.json(1 hunks)packages/design-system/src/components/index.ts(1 hunks)packages/design-system/src/components/wheelPicker/WheelPicker.tsx(1 hunks)packages/tailwind-config/shared-styles.css(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
🧬 Code graph analysis (2)
packages/design-system/src/components/wheelPicker/WheelPicker.tsx (1)
packages/design-system/src/lib/utils.ts (1)
cn(108-110)
apps/client/src/routes/router.tsx (1)
apps/client/src/routes/routesConfig.ts (1)
ROUTES_CONFIG(1-22)
🪛 Biome (2.1.2)
packages/tailwind-config/shared-styles.css
[error] 165-165: expected , but instead found --color-white
Remove --color-white
(parse)
[error] 165-165: Unexpected value or character.
Expected one of:
(parse)
[error] 165-165: expected , but instead found ;
Remove ;
(parse)
[error] 166-166: expected , but instead found --color-black
Remove --color-black
(parse)
[error] 166-166: Unexpected value or character.
Expected one of:
(parse)
[error] 166-166: Expected an identifier but instead found '000000'.
Expected an identifier here.
(parse)
[error] 167-167: expected , but instead found --color-transparent
Remove --color-transparent
(parse)
[error] 167-167: expected , but instead found ;
Remove ;
(parse)
[error] 167-167: Unexpected unknown pseudo-class transparent
See MDN web docs for more details.
(lint/correctness/noUnknownPseudoClass)
🔇 Additional comments (3)
packages/tailwind-config/shared-styles.css (1)
169-177: 색 토큰 추가 방향은 LGTM.
bg-white/text-black/투명 레이어 등의 DS 일관성 확보에 유용합니다. 위 parse 회피만 적용하면 문제없습니다.packages/design-system/src/components/wheelPicker/WheelPicker.tsx (2)
10-20: Wrapper의 기본 flex 부여와 cn 병합 방식은 적절합니다.props 포워딩과
className병합이 깔끔해 재사용성 좋습니다.
1-1: 라이브러리 글로벌 CSS import의 번들러 호환성 확인 권장라이브러리 컴포넌트 파일에서 글로벌 CSS를 side-effect import 하면 Next.js 설정(특히 pages 라우터)에서는 제약이 있을 수 있습니다. 현 레포의 앱 번들러(Vite/Next app 라우터 등)에서 문제없는지 한번 확인 부탁드립니다. 이슈 시 DS 루트에서 한 번만 import 하거나 consumer 앱 엔트리에서 가져오는 방식을 고려해볼 수 있습니다.
| "iconLibrary": "lucide", | ||
| "registries": { | ||
| "@ncdai": "https://chanhdai.com/r/{name}.json" | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
shadcn components.json의 registries 커스텀 URL 사용 — CLI 호환성 확인
해당 필드는 개발 시 스캐폴딩 용도로만 쓰이는 게 이상적입니다. 사용 중인 shadcn CLI 버전이 "registries" 맵 형태와 커스텀 레지스트리 URL을 지원하는지 확인해주세요. 외부 도메인(https://chanhdai.com) 신뢰·가용성도 한 번 검토하면 좋겠습니다.
최신 스키마 확인용 웹 질의:
🌐 Web query:
shadcn components.json "registries" field schema and support for custom registries in current CLI
💡 Result:
Short answer
- components.json "registries" is an array of registry objects (minimum fields: name and baseUrl) that tell the CLI where to fetch registries from. [1][2]
- You can host custom registries as JSON (registry.json / registry-item.json) that conform to shadcn's registry schemas; the CLI expects those JSON endpoints and will resolve registry dependencies automatically. [3][5]
- The CLI supports adding/using custom registries (e.g., shadxn/shadcn CLI: registry add / registry init / registry build; and add components with -r ). Auth must be handled by your registry server (token query param is suggested). [4][5]
Relevant docs
- components.json / registries usage. [1]
- Registry overview (requirements, hosting JSON, run-your-own). [2]
- registry-item.json schema (item/files/registryDependencies and automatic resolution). [3]
- CLI registry commands (registry add/init/build and using -r to install from a registry). [4]
- Getting started / auth and publishing guidance. [5]
If you want, I can fetch the exact JSON schema URLs (registry.json and registry-item.json $schema links) and show an example components.json + sample registry.json you can deploy.
components.json의 registries 필드 스키마 및 CLI 지원 확인 및 형식 수정
- 최신 shadcn CLI 스키마에 따르면
registries는 맵이 아니라 레지스트리 객체 배열이어야 하며, 각 객체에 필수로name과baseUrl을 지정해야 합니다. - 예시 (packages/design-system/components.json):
"registries": [ { "name": "@ncdai", "baseUrl": "https://chanhdai.com/r" } ]
- 사용 중인 shadcn CLI 버전이 커스텀 레지스트리 기능(
registry add/init/build및-r <registry-name>플래그)을 지원하는지 검증 - 호스트 도메인의 신뢰성·가용성 및 인증 방식(token 쿼리 파라미터 권장) 점검 필요
🤖 Prompt for AI Agents
In packages/design-system/components.json around lines 20–23, the registries
field currently uses a map but should be an array of registry objects per the
latest shadcn CLI schema; replace the map with an array of objects each
containing required "name" and "baseUrl" (e.g. { "name": "@ncdai", "baseUrl":
"https://chanhdai.com/r" }), update any code/CI that reads this file to the new
shape, verify the installed shadcn CLI version supports custom registries and
the registry-related flags/commands, and validate the registry host for
availability/trust and recommend using a token passed as a query parameter for
authentication.
| /* default */ | ||
| --color-white: #ffffff; | ||
| --color-black: #000000; | ||
| --color-transparent: transparent; | ||
|
|
There was a problem hiding this comment.
Biome 파서 오류로 CI 깨짐: @theme 블록 안의 커스텀 프로퍼티 선언을 :root로 감싸주세요.
Biome(2.1.2)가 Tailwind v4 @theme의 루트 레벨 커스텀 프로퍼티 선언을 제대로 파싱하지 못해 parse 에러가 납니다. 동일 파일의 기존 토큰들은 변경 라인이 아니라 리포팅되지 않았을 뿐, 새로 추가된 3개 라인에서 빌드가 막힙니다. :root로 한 번 감싸면 Biome이 “스타일 규칙”으로 인식해 통과합니다(Tailwind v4도 스코프드 토큰을 지원하므로 동작 동일).
적용 diff:
/* default */
- --color-white: #ffffff;
- --color-black: #000000;
- --color-transparent: transparent;
+ :root {
+ --color-white: #ffffff;
+ --color-black: #000000;
+ --color-transparent: transparent;
+ }📝 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.
| /* default */ | |
| --color-white: #ffffff; | |
| --color-black: #000000; | |
| --color-transparent: transparent; | |
| /* default */ | |
| :root { | |
| --color-white: #ffffff; | |
| --color-black: #000000; | |
| --color-transparent: transparent; | |
| } |
🧰 Tools
🪛 Biome (2.1.2)
[error] 165-165: expected , but instead found --color-white
Remove --color-white
(parse)
[error] 165-165: Unexpected value or character.
Expected one of:
(parse)
[error] 165-165: expected , but instead found ;
Remove ;
(parse)
[error] 166-166: expected , but instead found --color-black
Remove --color-black
(parse)
[error] 166-166: Unexpected value or character.
Expected one of:
(parse)
[error] 166-166: Expected an identifier but instead found '000000'.
Expected an identifier here.
(parse)
[error] 167-167: expected , but instead found --color-transparent
Remove --color-transparent
(parse)
[error] 167-167: expected , but instead found ;
Remove ;
(parse)
[error] 167-167: Unexpected unknown pseudo-class transparent
See MDN web docs for more details.
(lint/correctness/noUnknownPseudoClass)
🤖 Prompt for AI Agents
In packages/tailwind-config/shared-styles.css around lines 164 to 168, the three
custom property declarations currently at root level inside the @theme block
must be wrapped in a :root selector; update that section so the declarations
(--color-white, --color-black, --color-transparent) are placed inside a :root {
... } block within the existing @theme block so the parser accepts them.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
packages/design-system/src/components/card/RemindCard.tsx (1)
31-38: 이미지 대체 텍스트 및 지연 로딩 추가로 접근성/성능 개선동적 이미지에 alt가 없어 스크린리더 노출이 누락됩니다. lazy 로딩도 함께 권장합니다.
- {imageUrl ? ( - <img src={imageUrl} className="h-full w-full object-cover" /> + {imageUrl ? ( + <img + src={imageUrl} + alt={`${title} 이미지`} + loading="lazy" + className="h-full w-full object-cover" + /> ) : ( <img src={chippiNoImage} alt="이미지 없을 경우 logo" className="h-[12rem]" /> )}packages/design-system/src/components/card/MyBookmarkCard.tsx (1)
5-11: img 대체 텍스트 및 지연 로딩 추가실제 이미지 분기에서 alt가 누락되어 접근성 검증에 실패합니다. 또한 lazy 로딩을 추가하면 성능에 유리합니다.
interface MyBookmarkCardProps { title: string; content?: string; category?: string; imageUrl?: string; date: string; + imageAlt?: string; } @@ - {imageUrl ? ( - <img src={imageUrl} className="h-full w-full object-cover" /> + {imageUrl ? ( + <img + src={imageUrl} + alt={imageAlt ?? '북마크 이미지'} + loading="lazy" + decoding="async" + className="h-full w-full object-cover" + /> ) : ( <img src={chippiNoImage} alt="이미지 없을 경우 logo" + loading="lazy" + decoding="async" className="h-[12rem]" /> )}Also applies to: 23-31
packages/design-system/vite.config.ts (1)
44-69: Playwright 의존성 추가 필요
- packages/design-system/package.json에
@playwright/test를 devDependencies로 추가하세요.alias import 미검출, tsconfig 경로 매핑 및
.storybook디렉토리는 정상 확인되었습니다.
🧹 Nitpick comments (10)
packages/design-system/src/components/badge/Badge.stories.tsx (2)
5-5: CSF 타입 안전성 향상:satisfies패턴 적용 제안과도한 타입 확장을 막고 오타를 조기에 잡기 위해
const meta = { ... } satisfies Meta<typeof Badge>패턴을 권장합니다.다음 diff 제안:
-const meta: Meta<typeof Badge> = { +const meta = { title: 'Components/Badge', component: Badge, tags: ['autodocs'], args: { @@ -}; +} satisfies Meta<typeof Badge>;Also applies to: 28-28
45-48: 상호작용 테스트의 선택자 안정성 개선텍스트 매칭은 사소한 라벨 변경에 취약합니다. 버튼 역할 기반 쿼리로 견고하게 바꾸는 것을 추천합니다(컴포넌트가 실제 button을 렌더링한다는 전제).
- await userEvent.click(await canvas.findByText(String(args.text))); + await userEvent.click( + await canvas.findByRole('button', { name: String(args.text) }) + );버튼이 아니라면 해당 역할에 맞는 접근성 선택자로 조정해 주세요.
packages/design-system/src/components/button/Button.tsx (1)
33-47: disabled vs. isDisabled 이중화 해소 제안현재 {...props}가 disabled를 덮어쓸 수 있어 혼동 여지가 있습니다. 단일 계산 값으로 일관되게 처리해 주세요.
-const Button = ({ - variant = 'primary', - size, - children, - className, - isDisabled = false, - ...props -}: ButtonProps) => { +const Button = ({ + variant = 'primary', + size, + children, + className, + isDisabled = false, + disabled, + ...props +}: ButtonProps) => { + const computedDisabled = isDisabled || disabled; return ( <button type="button" - className={cn(buttonVariants({ variant, size }), className)} - disabled={isDisabled} + className={cn(buttonVariants({ variant, size }), className)} + disabled={computedDisabled} {...props} > {children} </button> ); }packages/design-system/src/components/card/RemindCard.tsx (1)
23-24: 장식용 아이콘은 스크린리더에서 숨기기버튼에 aria-label이 있으므로 내부 아이콘은 aria-hidden 처리해 중복 낭독을 방지하세요.
- <Icon name="ic_clock_active" /> + <Icon name="ic_clock_active" aria-hidden="true" /> ... - <Icon name="ic_details_category" /> + <Icon name="ic_details_category" aria-hidden="true" />Also applies to: 50-51
packages/design-system/src/components/toast/Toast.tsx (1)
10-12: role/aria-live 의미 충돌 정리role="alert"는 기본적으로 assertive입니다. polite를 원하면 role="status"가 더 적절합니다.
- role="alert" - aria-live="polite" + role="status" + aria-live="polite"packages/design-system/src/components/toast/hooks/uesFadeOut.tsx (1)
1-50: 파일명 오타 및 네이밍 정리
packages/design-system/src/components/toast/hooks/uesFadeOut.tsx파일명을 컴포넌트명에 맞춰AutoDismissToast.tsx로 변경하거나, 훅으로 분리할 경우useFadeOut.tsx로 수정- 변경된 파일명에 맞게 아래 경로의 import 문 업데이트
packages/design-system/src/components/toast/Toast.stories.tsx(4행)packages/design-system/src/components/index.ts(10행)packages/design-system/src/components/textarea/Textarea.tsx (1)
9-31: ref 전달을 위해 forwardRef로 전환 제안폼 포커스 관리/스크롤 복원 등을 위해 ref 노출이 필요할 수 있습니다. 아래처럼 forwardRef로 감싸두면 재사용성이 올라갑니다.
-export function Textarea({ - className, - maxLength, - style, - ...props -}: TextareaProps) { - return ( - <textarea +export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>( + ({ className, maxLength, style, ...props }, ref) => { + return ( + <textarea + ref={ref} data-slot="textarea" maxLength={maxLength} className={cn( 'h-[12rem] w-full', 'resize-none overflow-y-auto', 'body3-r border-gray200 bg-white-bg text-font-gray-3 rounded-[0.4rem] border px-[0.8rem] py-[1.2rem] pr-[1.4rem]', 'focus:border-input outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0', 'ds-scrollbar', className )} style={{ scrollbarGutter: 'stable', ...style }} {...props} /> - ); -} + ); + } +);packages/design-system/src/components/switch/Switch.tsx (1)
10-10: 불필요한 wrapper 제거 제안텍스트 색상을 지정하는 wrapper div가 실제로는 의미가 없어 보이고, 상위 컨텍스트에 의도치 않은 색상 상속을 야기할 수 있습니다. Root를 최상위로 두는 편이 안전합니다.
- return ( - <div className="text-main600"> - <SwitchPrimitive.Root + return ( + <SwitchPrimitive.Root data-slot="switch" className={cn( 'data-[state=checked]:bg-main400 data-[state=unchecked]:bg-gray200 focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 shadow-xs peer inline-flex h-[2rem] w-[4rem] shrink-0 items-center rounded-full border border-transparent outline-none transition-all focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50', className )} {...props} > <SwitchPrimitive.Thumb data-slot="switch-thumb" className={cn( 'bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-[1.6rem] rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[2.1rem] data-[state=unchecked]:translate-x-[0.1rem]' )} /> - </SwitchPrimitive.Root> - </div> + </SwitchPrimitive.Root> );packages/design-system/src/components/card/MyBookmarkCard.tsx (1)
50-55: 카테고리 배지 컴포넌트화 TODO 상기재사용성을 위해 배지 컴포넌트 추출 시 디자인 토큰(색상/사이즈)을 prop으로 받는 형태로 정리 권장.
원하시면 디자인 토큰 기반 Badge 컴포넌트 초안 드리겠습니다.
packages/design-system/vite.config.ts (1)
21-26: svg 스프라이트 경로의 절대화 제안테스트/툴 실행 디렉터리에 따라 상대 경로 해석이 달라질 수 있어 절대 경로로 고정하는 편이 안전합니다.
- svgSpritePlugin({ - iconDirs: ['src/icons/source'], + svgSpritePlugin({ + iconDirs: [path.resolve(dirname, 'src/icons/source')], symbolId: 'icon-[name]', inject: 'body-last', }),
📜 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.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (13)
packages/design-system/package.json(1 hunks)packages/design-system/src/components/badge/Badge.stories.tsx(1 hunks)packages/design-system/src/components/button/Button.tsx(1 hunks)packages/design-system/src/components/card/MyBookmarkCard.tsx(1 hunks)packages/design-system/src/components/card/RemindCard.tsx(1 hunks)packages/design-system/src/components/progress/Progress.tsx(1 hunks)packages/design-system/src/components/switch/Switch.tsx(1 hunks)packages/design-system/src/components/textarea/Textarea.tsx(1 hunks)packages/design-system/src/components/toast/Toast.tsx(1 hunks)packages/design-system/src/components/toast/hooks/uesFadeOut.tsx(1 hunks)packages/design-system/src/components/wheelPicker/WheelPicker.tsx(1 hunks)packages/design-system/tsconfig.json(1 hunks)packages/design-system/vite.config.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/design-system/package.json
- packages/design-system/src/components/wheelPicker/WheelPicker.tsx
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
📚 Learning: 2025-07-08T11:47:10.642Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#30
File: apps/extension/src/App.tsx:10-21
Timestamp: 2025-07-08T11:47:10.642Z
Learning: In apps/extension/src/App.tsx, the InfoBox component currently uses a hardcoded external URL for the icon prop as a temporary static placeholder. The plan is to replace this with dynamic favicon extraction from bookmarked websites in future iterations.
Applied to files:
packages/design-system/src/components/card/MyBookmarkCard.tsx
🔇 Additional comments (14)
packages/design-system/src/components/badge/Badge.stories.tsx (2)
3-3: 기본 import 전환 OK스토리 파일 입장에서
Badge의 default export 사용은 일관성 있고 문제 없어 보입니다.
1-1: import 경로 유지 필요 디자인 시스템 패키지가 Storybook 9의@storybook/react-vite@9.1.3을 사용 중이므로 수정 불필요합니다.Likely an incorrect or invalid review comment.
packages/design-system/src/components/button/Button.tsx (1)
2-2: 상대 경로로의 전환, 이상 없습니다.패키지 경계(DS → 앱)에서 tsconfig 경로별칭 의존도를 낮추는 목적에 부합합니다.
packages/design-system/src/components/card/RemindCard.tsx (1)
2-2: 아이콘 import 경로 수정, 일관성 OK동일한 패턴으로 다른 컴포넌트도 정리된 점 확인했습니다.
packages/design-system/src/components/toast/Toast.tsx (1)
1-1: cn 경로 변경 문제 없습니다.packages/design-system/src/components/toast/hooks/uesFadeOut.tsx (1)
2-2: 경로 변경 OKpackages/design-system/src/components/textarea/Textarea.tsx (1)
2-2: 상대 경로 변경 일관성 OK다른 DS 컴포넌트들과 동일한 경로 규칙으로 맞춰졌습니다. 빌드 설정(tsconfigPaths)과도 일치합니다.
packages/design-system/src/components/switch/Switch.tsx (2)
2-3: import 순서/경로 정리 좋습니다경로 alias 정리와 함께 React import 이동이 문제없습니다.
11-26: 접근성 라벨 확인 요청컴포넌트 자체에 시각적 라벨이 없으므로 소비 측에서 aria-label/aria-labelledby를 반드시 제공하는지 확인 부탁드립니다.
packages/design-system/src/components/card/MyBookmarkCard.tsx (1)
2-2: Icon 경로 정리 OKalias 제거 정책과 일관됩니다.
packages/design-system/src/components/progress/Progress.tsx (2)
3-4: 경로 정리 및 import 순서 OK동일 규칙 적용 확인했습니다.
47-58: 접근성 라벨 제공 여부 확인Radix Progress는 value/max는 처리하지만 라벨은 소비 측 책임입니다. 사용처에서 aria-label 또는 aria-labelledby를 제공하는지 점검 부탁드립니다. 예)
packages/design-system/vite.config.ts (2)
20-21: tsconfigPaths 도입 적절수동 alias 제거와 함께 경로 해석 일관성이 좋아집니다.
10-14: ESM 환경에서 dirname 계산 방식 적절fileURLToPath fallback으로 CI/Node 버전 차이에도 안전합니다.
| "@/*": ["./src/*"] | ||
| }, |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
경로별칭 축소(@lib 제거) 일관성 확인 필요
@/는 유지되지만, 본 PR에서는 상대경로로 전환 중입니다. 잔존 @lib/, '@/lib/*' 참조가 없는지 확인해 주세요.
🏁 Script executed:
#!/bin/bash
# design-system 내 잔존 별칭 검색
rg -nP "@lib/|@/lib/" packages/design-system -g '!**/dist/**' -S
# 앱 레벨에서도 혹시 참조 중인지 확인
rg -nP "@lib/|@/lib/" apps -g '!**/dist/**' -SLength of output: 185
@lib 별칭 제거 후 경로 일관성 확보
packages/design-system/components.json (15행)에 "@/lib/utils" 참조가 남아 있으니 상대경로 또는 @/* 별칭으로 교체해주세요.
🤖 Prompt for AI Agents
packages/design-system/tsconfig.json around lines 9-10: components.json still
references "@/lib/utils" after removing any @lib alias; open
packages/design-system/components.json (line ~15) and replace the "@/lib/utils"
import with a consistent relative path (e.g., "./src/lib/utils" or
"../src/lib/utils" as appropriate to that file's location) so paths are
consistent with the current "@/*": ["./src/*"] mapping, or alternatively add a
specific "@/lib/*": ["./src/lib/*"] entry to tsconfig.json if you prefer to keep
the "@/lib/..." alias.
jjangminii
left a comment
There was a problem hiding this comment.
타임피커 어려우셨을텐데 고생하셨습니다-! 비슷한 라이브러리가 있다해도 스타일을 완전히 공개하지 않아 복잡했을텐데 잘 구현하셨네요 !!
추가적으로 저번에 있던 글자 넘김시 그림자처럼 두개로 보이던 효과 어떻게 제거하셨는지 궁금합니다
| "iconLibrary": "lucide", | ||
| "registries": { | ||
| "@ncdai": "https://chanhdai.com/r/{name}.json" | ||
| } |
There was a problem hiding this comment.
"lucide" 아이콘 라이브러리를 사용하고, "@ncdai"라는 스코프를 가진 컴포넌트를 특정 URL에서 가져오도록 설정하는 내용이라고 하네요!
| classNames={{ | ||
| ...classNames, | ||
| optionItem: cn( | ||
| 'text-gray300 !font-medium !text-[1.6rem] !leading-[150%] !tracking-[-0.025em]', |
There was a problem hiding this comment.
! 붙여서 스타일 지정하는게 아쉽지만 스타일이 숨김처리된 부분이 있어서 이렇게밖에 하지 못하는게 어쩔 수 없네요 다른 방법이 있나 저도 더 찾아보겠습니다-!
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
apps/client/src/pages/onBoarding/components/timePicker/TimePicker.tsx (5)
35-40: WheelPicker를 제어 컴포넌트로 사용(가능 시) — 초기값/동기화 보장라이브러리가
value(또는 유사 prop)를 지원한다면 바인딩해 UI와 상태의 불일치를 예방하세요. 미지원이면 이 코멘트는 스킵 가능합니다.<WheelPicker options={hourOptions} infinite optionItemHeight={56} + value={selectedHour} onValueChange={(value: string) => setSelectedHour(value)} /> ... <WheelPicker options={minuteOptions} infinite optionItemHeight={56} + value={selectedMinute} onValueChange={(value: string) => setSelectedMinute(value)} /> ... <WheelPicker options={meridiemOptions} optionItemHeight={56} + value={selectedMeridiem} onValueChange={(value: string) => setSelectedMeridiem(value)} />Also applies to: 44-49, 51-55
41-41: Tailwind z-index 클래스 비표준 사용 — z-[2]로 교체기본 Tailwind 스케일에는
z-2가 없습니다.z-[2]또는 사전에 정의된 토큰(z-10등)으로 변경하세요.- <p className="body2-m z-2 mx-[0.8rem] flex h-[5.6rem] items-center justify-center"> + <p className="body2-m z-[2] mx-[0.8rem] flex h-[5.6rem] items-center justify-center">
35-40: 접근성 향상: 휠에는 레이블, 콜론은 장식 처리각 휠에 목적을 설명하는 접근성 레이블을 부여하고, 콜론은 스크린리더에서 무시되도록 설정을 권장합니다.
<WheelPicker options={hourOptions} infinite optionItemHeight={56} + aria-label="시간 선택" onValueChange={(value: string) => setSelectedHour(value)} /> - <p className="body2-m z-2 mx-[0.8rem] flex h-[5.6rem] items-center justify-center"> + <p aria-hidden="true" role="presentation" className="body2-m z-[2] mx-[0.8rem] flex h-[5.6rem] items-center justify-center"> : </p> <WheelPicker options={minuteOptions} infinite optionItemHeight={56} + aria-label="분 선택" onValueChange={(value: string) => setSelectedMinute(value)} /> ... <WheelPicker options={meridiemOptions} optionItemHeight={56} + aria-label="오전/오후 선택" onValueChange={(value: string) => setSelectedMeridiem(value)} />Also applies to: 41-43, 44-49, 51-55
20-23: 로케일 정합성: 라벨만 한국어로 노출(값은 AM/PM 유지)한국어 UI 맥락에서 라벨은 ‘오전/오후’가 자연스럽습니다. 내부 값은 기존 AM/PM 유지로 로직 영향 최소화.
- { label: 'AM', value: 'AM' }, - { label: 'PM', value: 'PM' }, + { label: '오전', value: 'AM' }, + { label: '오후', value: 'PM' },
9-16: 네이밍 가독성: createArray → createTimeOptions의도를 드러내는 이름이 유지보수에 유리합니다.
-const createArray = (length: number, add = 0): WheelPickerOption[] => +const createTimeOptions = (length: number, add = 0): WheelPickerOption[] => Array.from({ length }, (_, i) => { const value = i + add; return { label: value.toString().padStart(2, '0'), value: value.toString(), }; }); -const hourOptions = createArray(12, 1); -const minuteOptions = createArray(60); +const hourOptions = createTimeOptions(12, 1); +const minuteOptions = createTimeOptions(60);Also applies to: 18-19
📜 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.
📒 Files selected for processing (1)
apps/client/src/pages/onBoarding/components/timePicker/TimePicker.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
🪛 GitHub Check: lint
apps/client/src/pages/onBoarding/components/timePicker/TimePicker.tsx
[warning] 28-28:
'selectedMeridiem' is assigned a value but never used
[warning] 27-27:
'selectedMinute' is assigned a value but never used
[warning] 26-26:
'selectedHour' is assigned a value but never used
🪛 GitHub Actions: ci
apps/client/src/pages/onBoarding/components/timePicker/TimePicker.tsx
[error] 26-26: TypeScript error TS6133: 'selectedHour' is declared but its value is never read.
apps/client/src/pages/onBoarding/components/timePicker/TimePicker.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
apps/client/src/pages/onBoarding/components/timePicker/TimePicker.tsx (1)
67-82: 이전 리뷰 피드백 반영 확인: 상태 사용 및 콜백 연결 완료
selectedHour/minute/meridiem이 실제로 소비되고, 취소/확인 버튼에 핸들러가 연결되었습니다. CI 경고(TS6133) 원인이 해소된 것으로 보입니다.Also applies to: 31-36
🧹 Nitpick comments (5)
apps/client/src/pages/onBoarding/components/timePicker/TimePicker.tsx (5)
39-41: 루트 컨테이너 onClick 전파로 인한 오동작 가능성휠/버튼 클릭이 모두 상위로 버블되어 부모가 바깥 클릭으로 오인할 수 있습니다(모달 닫힘 등). 컨테이너 내부 상호작용을 보호하려면 전파 차단 또는 명시적 백드롭 핸들링으로 분리하는 것을 권장합니다.
예시:
- <div - onClick={onClick} + <div + onClick={(e) => { e.stopPropagation(); onClick?.(e); }} className="common-shadow flex w-[26rem] flex-col items-center px-[1.6rem]" >혹은 상위에서 백드롭 영역에만 onClick을 부여하고, 이 컴포넌트는 onClick을 제거하세요.
45-49: WheelPicker를 “controlled”로 유지하여 상태와 UI 동기화 보장현재 선택값 state는 저장(확인) 시에만 쓰이고, 휠의 초기/외부 변경과 동기화를 보장하지 않습니다. 라이브러리/래퍼가
value(또는 동등 prop)를 지원한다면 전달하여 단일 소스 유지가 좋습니다.예시(래퍼가
value를 지원한다고 가정):<WheelPicker options={hourOptions} infinite optionItemHeight={56} + value={selectedHour} onValueChange={(value: string) => setSelectedHour(value)} /> ... <WheelPicker options={minuteOptions} infinite optionItemHeight={56} + value={selectedMinute} onValueChange={(value: string) => setSelectedMinute(value)} /> ... <WheelPicker options={meridiemOptions} optionItemHeight={56} + value={selectedMeridiem} onValueChange={(value: string) => setSelectedMeridiem(value)} />지원 prop 명이 상이하면 래퍼 정의에 맞춰 조정 바랍니다.
Also applies to: 54-58, 61-64
50-52: 타이포그래피/레이어 유틸 검증 필요
body2-m,z-2가 Tailwind 설정에 존재하는지 확인이 필요합니다. 기본 스케일에는z-2가 없습니다. 미정의일 경우 적용되지 않아 시각적 이슈가 생길 수 있습니다.대안: 디자인 토큰 기반 유틸 또는
z-10등 정의된 값 사용.
9-16: 값 포맷 일관화(라벨=값)로 후처리 혼란 최소화라벨은 2자리(
'01','00')인데 값은 비패딩('1','0')이라 포맷팅 시 혼선이 생길 수 있습니다. 저장/표시 일관성을 위해 값도 패딩하거나, 반대로 값은 number로 보관하는 방식을 권장합니다.패딩 일치 예:
- Array.from({ length }, (_, i) => { - const value = i + add; - return { - label: value.toString().padStart(2, '0'), - value: value.toString(), - }; - }); + Array.from({ length }, (_, i) => { + const n = i + add; + const s = n.toString().padStart(2, '0'); + return { label: s, value: s }; + });
18-23: 타입 명시 강화(AM/PM 리터럴, 콜백 시그니처 구체화)
meridiem을 문자열 리터럴 합집합으로 제한하면 오타/외부 입력 오류를 줄일 수 있습니다.예시:
+type Meridiem = 'AM' | 'PM'; const meridiemOptions: WheelPickerOption[] = [ { label: 'AM', value: 'AM' }, { label: 'PM', value: 'PM' }, ]; interface TimePickerProps { - onSave: (time: { hour: string; minute: string; meridiem: string }) => void; + onSave: (time: { hour: string; minute: string; meridiem: Meridiem }) => void;Also applies to: 25-29
📜 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.
📒 Files selected for processing (1)
apps/client/src/pages/onBoarding/components/timePicker/TimePicker.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
| interface TimePickerProps { | ||
| onSave: (time: { hour: string; minute: string; meridiem: string }) => void; | ||
| onCancel: () => void; | ||
| onClick: React.MouseEventHandler<HTMLDivElement>; | ||
| } |
There was a problem hiding this comment.
TS2503 가능성: React 네임스페이스 타입 사용으로 컴파일 실패 위험
onClick: React.MouseEventHandler는 React 네임스페이스를 import하지 않아 TS가 실패할 수 있습니다. 타입 전용 import로 네임스페이스 의존을 제거하세요. 또한 외부에서 반드시 전달되지 않아도 되는 prop이면 optional로 두는 편이 안전합니다.
적용 diff:
-import { useState } from 'react';
+import { useState, type MouseEventHandler } from 'react';
interface TimePickerProps {
onSave: (time: { hour: string; minute: string; meridiem: string }) => void;
onCancel: () => void;
- onClick: React.MouseEventHandler<HTMLDivElement>;
+ onClick?: MouseEventHandler<HTMLDivElement>;
}Also applies to: 7-7
🤖 Prompt for AI Agents
In apps/client/src/pages/onBoarding/components/timePicker/TimePicker.tsx around
lines 7 and 25-29, the prop type uses the React namespace
(React.MouseEventHandler) which can cause TS2503 if React isn't imported as a
namespace; change it to a type-only import and make the prop optional if it may
not be provided: add an import type for MouseEventHandler from 'react' at the
top, replace React.MouseEventHandler<HTMLDivElement> with
MouseEventHandler<HTMLDivElement> and mark onClick as optional (onClick?: ...).


📌 Related Issues
📄 Tasks
⭐ PR Point (To Reviewer)
📍 React Wheel Picker 도입
일단 해당 picker 구현이 리소스가 많이 들 것 같고, 기존 ios 스타일 기반의 shadcn 기반 쾨드를 발견하여 그걸 사용해서 구현하게 되었어요.
[사용 이유]
-> 사용 라이브러리
📍 구조
물론 현재 디자인 기준으로는 time picker가 onboarding 한 개의 도메인에서만 사용되지만, 이후 재사용이 될 것을 생각해서 pds에 추가를 했어요. 하지만 정확하게 똑같은 디자인이 다 구현된 상태로 pds에 넣기에는 공통의 의미가 많이 없어질 수 있다고 생각했어요. 디자인이 같은 방식으로 재사용되도록 디자인 파트분들께 말씀드려도 되지만, 혹시나 약간의 디자인 변경을 허용할 수 있도록 확장성을 조금 추가해봤어요.
그래서 최소한의 디자인을 pds내
WheelPicker.tsx에 두고, 사용하는 곳에서 이 컴포넌트를 가져와 추가적인 디자인을 주입할 수 있도록 설계했어요 (ex. margin, layout, color 등)client/src/pages/onBoarding/components/timePicker/TimePicker.tsx
아래와 같이 필요한 디자인에 따라 커스텀 사용
📍 해당 코드 사용의 커스텀 한계
shadcn기반이라고 해서 모든 add 했는데 사실 완벽하게 코드 기반이 아닌 것이 사실 가장 큰 문제였어요. 사실상 의존성이 생기는 것을 보아 shadcn 기반이라고 하지만 라이브러리라고 봐야 할 것 같아요. 아래와 같은
import문이 생겼기 때문이에요!그러다 보니 완벽하게 해당 컴포넌트를 커스텀하기가 불가능한 부분이 있었어요.
WheelPickerWrapper에는 props로className를 줘서 string 형식으로 스타일을 자유롭게 추가가 가능하기는 하지만,WheelPicker에는classNames라는 이름으로 기존 tailwind의 클래스를 추가하는 것이 아닌 해당 라이브러리에서 정의한 인터페이스만 추가가 가능한 형태였기 때문이에요.따라서 picker 내부의 태그들에 완벽하게 커스텀이 쉽지는 않았고, 특히
cnutil 등으로 스타일 우선순위에 따른 병합이 되지 않았기 때문에! important사용을 하게 되었어요.! important사용이 css의 우선순위 체계를 파괴한다는 점에서 지양하는 것이 좋지만 해당 라이브러리에 숨겨진 코드 내 스타일이 우선순위가 높아서 커스텀이 불가능해서 사용을 피할 수 없다고 생각했고, 이러한 이유라면 크게 문제가 없을 것이라고 생각했어요.📍 tailwind
@utility의 스타일 적용 문제WheelPicker는 위에서 언급한 것처럼 완벽하게 모든 스타일을 커스텀 할 수 없지만, 제공하는optionItem등의 인터페이스에는 스타일 추가가 가능했어요. 저희 pds의 token으로 color 등이 잘 적용이 되었지만 이상하게 typo 관련 token만 적용이 안되는 것을 확인했어요.color token은 잘 적용되니
shared-styles.css는 잘 불러오는 것 같았고, color와 typo의 차이를 살펴보니 결국 이전에 자동완성에도 문제가 되었던@utility키워드가 문제인 것 같았어요.color에는
@theme을 사용해서 tailwind 기존 클래스로 정의하는 의미를 갖지만@utility는 기존 클래스에 추가하는 것이 아닌 완전 그 자체로 새로운 유틸리티 클래스를 추가하기 때문에 이를WheelPicker컴포넌트로 넣을 때 인식을 제대로 못한다고... 추측했어요.그래서 저는 해결책으로
body2-m유틸리티 클래스 대신 이 내부 속성 값들을 다 하나하나 적어줬어요. 그리고 이 또한 우선순위 문제 때문에 !를 사용해서 해결했습니다.사실 뭔가 완벽하게 해결한 느낌이 아니라.. 찝찝한데 이외에 더 좋은 방법이 생각나지 않고 찾지 못해서..!!
혹시나 더 좋은 방법을 찾게 되면 편하게 의견 공유해주세요!!!!!!!
📷 Screenshot
bandicam.2025-09-01.23-14-14-187.mp4
Summary by CodeRabbit