[FEAT] 온보딩 화면 컴포넌트 구현#120
Conversation
온보딩 컴포넌트에서 사용할 ChevronSmall SVG 소스 파일 5종과 Google 로그인 버튼에 사용할 google-logo.svg 에셋을 추가했습니다. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OnboardingButton, OnboardingLoginButton, OnboardingTimeDropdown, OnboardingStepButton 컴포넌트를 구현했습니다. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Walkthrough온보딩 화면의 버튼, 단계 표시, Google 연동 버튼, 시간 선택 UI가 추가됐고, 관련 번역 키도 en/ko 메시지에 등록됐다. 전역 웹킷 스크롤바 크기도 함께 조정됐다. Changes온보딩 UI
스크롤바 스타일
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the 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. Comment |
Timo Performance ReportBundle Size — timo-web
Lighthouse — timo-web
Image Optimization — timo-web
측정 커밋: |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/timo-web/app/`[locale]/onboarding/_components/OnboardingButton.tsx:
- Around line 32-55: `OnboardingButton`의 비활성 variant 처리에서 `active`와 `disabled`가
분리되어 있지 않아, `다음`과 `시작하기_비활성`이 시각적으로만 비활성화되고 실제로는 클릭 가능한 상태입니다. `variant`에 따라
`button`의 `disabled` 속성을 설정하도록 수정하고, `이전`은 회색 스타일이어도 계속 클릭 가능해야 하므로 `active` 판정과
`disabled` 판정을 별도로 처리하세요. `OnboardingButton`의 `variant`, `active`, `LABEL` 렌더링
흐름을 기준으로 조건을 정리해 주세요.
In `@apps/timo-web/app/`[locale]/onboarding/_components/OnboardingLoginButton.tsx:
- Around line 5-15: The boolean prop in OnboardingLoginButtonProps should follow
the naming convention by renaming selected to isSelected and updating the
OnboardingLoginButton destructuring/defaults and all call sites that pass or
read this prop accordingly. Make sure the component interface, function
signature, and any conditional styling or behavior tied to selected use the new
isSelected name consistently.
In `@apps/timo-web/app/`[locale]/onboarding/_components/OnboardingStepButton.tsx:
- Around line 10-31: The step indicator in OnboardingStepButton is rendered with
non-semantic div wrappers even though it represents an ordered sequence. Update
the JSX in OnboardingStepButton to use an ordered list structure with ol and li
for the STEPS.map output, keeping the existing styling and active/inactive
classes via cn while preserving the current visual layout and keys.
In
`@apps/timo-web/app/`[locale]/onboarding/_components/OnboardingTimeDropdown.tsx:
- Around line 11-14: `TIME_OPTIONS`가 `Array.from({ length: 25 })`로 생성되어 24:00이
00:00과 중복됩니다. `OnboardingTimeDropdown`의 `TIME_OPTIONS` 생성 로직을 수정해 일반적인 선택기 범위인
00:00~23:00만 포함하도록 `length`를 24로 조정하세요.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: fd8159bf-43a4-4f3c-9b22-0f62ea6f03e6
⛔ Files ignored due to path filters (6)
packages/timo-design-system/src/assets/images/google-logo.svgis excluded by!**/*.svgpackages/timo-design-system/src/icons/source/chevron-small-down.svgis excluded by!**/*.svgpackages/timo-design-system/src/icons/source/chevron-small-left.svgis excluded by!**/*.svgpackages/timo-design-system/src/icons/source/chevron-small-right-white.svgis excluded by!**/*.svgpackages/timo-design-system/src/icons/source/chevron-small-right.svgis excluded by!**/*.svgpackages/timo-design-system/src/icons/source/chevron-small-up.svgis excluded by!**/*.svg
📒 Files selected for processing (4)
apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsxapps/timo-web/app/[locale]/onboarding/_components/OnboardingLoginButton.tsxapps/timo-web/app/[locale]/onboarding/_components/OnboardingStepButton.tsxapps/timo-web/app/[locale]/onboarding/_components/OnboardingTimeDropdown.tsx
- OnboardingButton variant를 영문 키로 변경 후 useTranslations 연결 - OnboardingLoginButton → OnboardingGoogleButton으로 rename, variant 기반 번역 적용 - OnboardingTimeDropdown "use client" 추가 - ko.json / en.json에 Onboarding 네임스페이스 추가 - 온보딩 page.tsx에 컴포넌트 테스트 렌더링 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…적용 (#118) - OnboardingButton: next/start_inactive variant에 disabled 속성 추가 - OnboardingGoogleButton: selected → isSelected boolean prop 컨벤션 적용 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
internalValue 상태를 추가해 비제어 모드에서도 선택값이 저장되도록 수정했습니다. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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 (2)
apps/timo-web/app/[locale]/onboarding/_components/OnboardingGoogleButton.tsx (2)
24-32: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
aria-pressed속성을 추가해 선택 상태를 스크린 리더에 전달해야 합니다.
isSelectedprop으로 시각적 스타일은 전환되지만, 보조 기술에는 이 상태가 전달되지 않습니다. 토글/선택 버튼으로 사용되는 경우aria-pressed={isSelected}를 추가해 접근성을 보장해야 합니다.참고: WAI-ARIA Button Pattern - Toggle Button
As per coding guidelines, "텍스트 없는 버튼에
aria-label필수" 및 접근성 규칙을 따라야 합니다.♿ 제안: aria-pressed 추가
<button type="button" onClick={onClick} + aria-pressed={isSelected} className={cn(🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/timo-web/app/`[locale]/onboarding/_components/OnboardingGoogleButton.tsx around lines 24 - 32, The OnboardingGoogleButton toggle state is only reflected in styling, so update the button in OnboardingGoogleButton to expose that state to assistive tech by adding the pressed/selected accessibility attribute based on isSelected. Keep the existing button behavior and className logic, and ensure the same component also satisfies the no-text-button accessibility rule by providing an appropriate label for the button element.Source: Path instructions
34-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win불필요한 div 래핑을 제거하고 스타일을 버튼으로 통합할 수 있습니다.
버튼이 이미
flex items-center justify-center를 가지고 있어 내부 div의 flex 속성이 중복됩니다.gap-2.5 px-2를 버튼 className으로 이동하고 line 34의 div를 제거해 DOM 중첩을 줄일 수 있습니다.As per coding guidelines, "불필요한 div 래핑 금지 — 단순 그룹핑 목적이면 Fragment
<>…</>사용" 규칙을 따라야 합니다.♻️ 제안: div 제거 및 스타일 통합
className={cn( "flex w-[305px] items-center justify-center rounded-[4px] border py-2.5", isSelected ? "border-timo-blue-300 bg-timo-blue-50" : "border-timo-gray-500 bg-white", )} > - <div className="flex items-center gap-2.5 px-2"> - <div className="flex size-[22px] items-center justify-center"> + <div className="flex size-[22px] items-center justify-center"> <Image src={googleLogo} alt="Google" width={18} height={18} /> </div> <span className="typo-headline-m-16 text-timo-blue-300"> {t(`onboardingGoogleButton.${variant}`)} </span> - </div> </button>버튼 className에
gap-2.5 px-2추가:className={cn( - "flex w-[305px] items-center justify-center rounded-[4px] border py-2.5", + "flex w-[305px] items-center justify-center gap-2.5 rounded-[4px] border px-2 py-2.5", isSelected🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/timo-web/app/`[locale]/onboarding/_components/OnboardingGoogleButton.tsx around lines 34 - 35, Remove the unnecessary wrapper div inside OnboardingGoogleButton and move its spacing styles onto the button itself: the button already handles flex centering, so `gap-2.5` and `px-2` should be applied to the Button element while the inner div at that spot can be deleted (or replaced with a Fragment if needed). Keep the existing icon/content structure intact and verify the button still renders with the same alignment using the OnboardingGoogleButton component.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/timo-web/app/`[locale]/onboarding/_components/OnboardingButton.tsx:
- Around line 41-42: The boolean locals in OnboardingButton are named without
the required prefix; rename active and disabled to isActive and isDisabled to
match the boolean naming guideline. Update the declarations in OnboardingButton
and any subsequent references in that component so the new names are used
consistently.
---
Outside diff comments:
In
`@apps/timo-web/app/`[locale]/onboarding/_components/OnboardingGoogleButton.tsx:
- Around line 24-32: The OnboardingGoogleButton toggle state is only reflected
in styling, so update the button in OnboardingGoogleButton to expose that state
to assistive tech by adding the pressed/selected accessibility attribute based
on isSelected. Keep the existing button behavior and className logic, and ensure
the same component also satisfies the no-text-button accessibility rule by
providing an appropriate label for the button element.
- Around line 34-35: Remove the unnecessary wrapper div inside
OnboardingGoogleButton and move its spacing styles onto the button itself: the
button already handles flex centering, so `gap-2.5` and `px-2` should be applied
to the Button element while the inner div at that spot can be deleted (or
replaced with a Fragment if needed). Keep the existing icon/content structure
intact and verify the button still renders with the same alignment using the
OnboardingGoogleButton component.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: b68fd815-c46b-4375-9507-640746eb5453
📒 Files selected for processing (5)
apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsxapps/timo-web/app/[locale]/onboarding/_components/OnboardingGoogleButton.tsxapps/timo-web/app/[locale]/onboarding/_components/OnboardingTimeDropdown.tsxapps/timo-web/messages/en.jsonapps/timo-web/messages/ko.json
- active → isActive, disabled → isDisabled로 변수명을 수정했습니다
- 스텝 인디케이터의 div 태그를 ol/li 시맨틱 태그로 교체했습니다
jjangminii
left a comment
There was a problem hiding this comment.
여러 컴포넌트 하시느라 번거로웠을 텐데 고생하셨습니다~
코멘트 확인해주세요~~
| export interface OnboardingTimeDropdownProps { | ||
| value?: string; | ||
| placeholder?: string; | ||
| onChange?: (time: string) => void; | ||
| } | ||
|
|
||
| export const OnboardingTimeDropdown = ({ | ||
| value, | ||
| placeholder, | ||
| onChange, | ||
| }: OnboardingTimeDropdownProps) => { | ||
| const [internalValue, setInternalValue] = useState<string | undefined>(); |
There was a problem hiding this comment.
value가 optional이라서 부모가 안 넘기면 내부 internalValue state로 동작하고, 넘기면 부모 값을 따라가는 구조로 보여요. React는 렌더 도중 controlled <-> uncontrolled로 전환되는 걸 경고하는데, 이 구조에서는 그 상황이 발생할 수 있어요.
Checkbox처럼 value를 필수로 받고 내부 state를 없애면 팀 컨벤션과 일치하고 동작도 예측 가능해져요. 온보딩에서 기상/취침 시간을 상태로 들고 있어야 하는 건 어차피 부모 컨테이너 몫이니까요-!
There was a problem hiding this comment.
처음에 구현할 때 부모에서 value를 넘기지 않아도 트리거에 선택값이 표시되도록 내부 상태를 두었어요.
정민님이 주신 아티클 보니까 제어/비제어 방식 섞어서 구현하기보다는 상태를 한 곳에서만 관리하는 것이 컴포넌트를 더 단순하게 유지하고 동작을 예측하기가 쉬운 것 같아요.
어차피 온보딩 페이지에서는 최종 선택 시간을 부모가 서버에 전달해야 하니까 부모가 상태를 관리하는 구조가 자연스러운 것 같네요.
말씀하신 것처럼 내부 상태 제거하고 순수 제어 컴포넌트로 수정하겠습니다!
|
|
||
| export interface OnboardingGoogleButtonProps { | ||
| variant: OnboardingGoogleButtonVariant; | ||
| isSelected?: boolean; |
There was a problem hiding this comment.
사소한거긴 한데 PR 설명에는 selected prop이라고 적혀있어서 pr 내용에 실제 코드랑 prop명 일치시키면 좋을 것 같아요-!
| export type OnboardingButtonVariant = | ||
| | "next" | ||
| | "next_active" | ||
| | "prev" | ||
| | "start" | ||
| | "start_inactive"; | ||
|
|
||
| export interface OnboardingButtonProps { | ||
| variant: OnboardingButtonVariant; | ||
| onClick?: () => void; | ||
| } |
There was a problem hiding this comment.
| export type OnboardingButtonVariant = | |
| | "next" | |
| | "next_active" | |
| | "prev" | |
| | "start" | |
| | "start_inactive"; | |
| export interface OnboardingButtonProps { | |
| variant: OnboardingButtonVariant; | |
| onClick?: () => void; | |
| } | |
| type OnboardingButtonVariant = "next" | "prev" | "start"; | |
| interface OnboardingButtonProps { | |
| variant: OnboardingButtonVariant; | |
| isActive?: boolean; | |
| disabled?: boolean; | |
| onClick?: () => void; | |
| } |
현재 next/next_active, start/start_inactive처럼 활성/비활성 상태가 variant로 분리되어 있는데, variant가 늘어날수록 BUTTON_TRANSLATION_KEY Record도 같이 커져서 유지보수가 어려워질 것 같아요. 활성 상태는 별도 prop으로 분리하는 게 어떨까요?
There was a problem hiding this comment.
맞네요! 말씀하신대로 variant는 버튼 종류(next/prec/start)만 담당하게 하고 활성 상태는 isActive prop으로 분리해서 반영하겠습니다!
| const PANEL_SCROLLBAR = | ||
| "[scrollbar-width:thin] [scrollbar-color:var(--color-timo-gray-600)_transparent] [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-timo-gray-600 [&::-webkit-scrollbar-thumb:hover]:bg-timo-gray-700"; |
There was a problem hiding this comment.
스크롤바.. 이미 전역에 적용해뒀는데.. 왜..?
There was a problem hiding this comment.
전역 스크롤바와 onboardingTimeDropdown 안에 스크롤바 디자인이 달라서 새로 지정했었습니다,,
해당 컴포넌트에서만 사용하는 스타일인만큼 CSS Module로 분리했는데 안되네요,, 도와주세요..
| type="button" | ||
| onClick={onClick} | ||
| className={cn( | ||
| "flex w-[305px] items-center justify-center rounded-[4px] border py-2.5", |
There was a problem hiding this comment.
w-[305px] 고정 너비로 사용하고 계신데 부모 컨테이너에 맞게 w-full로 바꾸고 너비는 소비하는 쪽에서 제어하는 게 재사용성이 높다고 생각이들어요.
| {variant === "next" && <ChevronSmallRightIcon />} | ||
| {variant === "next_active" && <ChevronSmallRightWhiteIcon />} |
There was a problem hiding this comment.
여기도 isActive/isDisabled로 분리하면 아이콘 렌더링 조건도 자연스럽게 단순해질 것 같아요~
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/timo-web/app/`[locale]/onboarding/_components/OnboardingTimeField.tsx:
- Around line 1-17: OnboardingTimeField currently uses internal state and a
client-only hook, which violates the _components/ rule that these should be pure
UI and work without "use client". Update OnboardingTimeField to be a controlled
component that receives value and onChange props and forwards them to
OnboardingTimeDropdown, removing useState and the "use client" directive; if
state ownership must stay local, move that responsibility to the parent
container instead and keep OnboardingTimeField prop-only.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 52d27919-f935-44c7-a73b-ce8c29d2607a
⛔ Files ignored due to path filters (2)
packages/timo-design-system/src/icons/source/chevron-down-gray.svgis excluded by!**/*.svgpackages/timo-design-system/src/icons/source/chevron-up-gray.svgis excluded by!**/*.svg
📒 Files selected for processing (5)
apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsxapps/timo-web/app/[locale]/onboarding/_components/OnboardingGoogleButton.tsxapps/timo-web/app/[locale]/onboarding/_components/OnboardingTimeDropdown.tsxapps/timo-web/app/[locale]/onboarding/_components/OnboardingTimeField.tsxpackages/tailwind-config/scrollbar.css
- OnboardingTimeDropdown import 경로를 ./ 에서 ../_components/로 수정했습니다
| const getAmPm = (time: string): "AM" | "PM" => { | ||
| const hour = parseInt(time.split(":")[0] ?? "0", 10); | ||
| return hour >= 12 && hour < 24 ? "PM" : "AM"; | ||
| }; |
There was a problem hiding this comment.
이거 유틸로 빼도 재사용하기 좋을 거 같아요
Jsdoc 추가해서 빼줍시당
There was a problem hiding this comment.
넵!! timo-web/utils에 유틸함수 Jsdoc 추가해서 새로 생성했습니다! 🫰🏻
| export type OnboardingButtonProps = | ||
| | { | ||
| variant: "next"; | ||
| isActive: boolean; | ||
| disabled?: boolean; | ||
| onClick?: () => void; | ||
| } | ||
| | { variant: "prev"; onClick?: () => void } | ||
| | { variant: "start"; onClick?: () => void }; |
There was a problem hiding this comment.
버튼 상태에 따른 분기 처리도 많아 보이고, 지금 컴포넌트 자체도 UI 전용이라기보단 여러 책임을 관리하고 있는 것 같아서 책임 원칙에 따른 컴포넌트 분리가 필요해 보이고,
컴포넌트 내부 폴더엔 최대한 클라이언트 로직을 제거한 순수 UI 컴포넌트만 위치하게 수정해 보면 좋겠습니다!
There was a problem hiding this comment.
onboardingButton, onboardingGoogleButton도 컨테이너/컴포넌트로 분리했습니다!
use client 사용에 항상 주의하면서 파일을 만들어야 할 것 같네요,,(컴포넌트와 로직이 한꺼번에 클라이언트 번들에 들어가면서 초기 로딩에 영향을 주니까!!)
| start: "button.start", | ||
| }; | ||
|
|
||
| export type OnboardingButtonProps = |
There was a problem hiding this comment.
원래는 버튼 3종류가 서로 다른 특성을 가지고 있어서 유니언으로 나눴었습니다!

- next (다음 버튼)
isActive: 필수 — 활성화 여부에 따라 색상/아이콘이 바뀜 (파란색 / 회색)
disabled: 선택 — 강제 비활성화 가능
스타일: 파란/회색 배경 + 오른쪽 화살표 아이콘 - prev (이전 버튼)
isActive, disabled 없음 — 항상 같은 스타일(회색 배경 + 왼쪽 화살표)
hover 효과만 있음 - start (시작 버튼)
isActive, disabled 없음 — 항상 같은 스타일(파란 배경, 아이콘 없음)
텍스트만 다름
그랬는데 props가 붙어도 큰 문제가 없고,, 컨벤션 일관성이 더 중요한 것 같네요. 수정하겠습니다!!💗
- 컴포넌트에서 "use client"와 번역 로직을 제거하고 컨테이너로 이동했습니다 - OnboardingButton, OnboardingGoogleButton에 label prop을 추가해 번역 책임을 컨테이너로 위임했습니다 - OnboardingButtonContainer, OnboardingGoogleButtonContainer, OnboardingTimeContainer를 신규 추가했습니다 - OnboardingTimeField를 OnboardingTimeContainer로 대체했습니다 - getAmPm 함수를 utils/get-am-pm.ts로 분리했습니다 - 외부에서 사용하지 않는 interface export를 제거했습니다
|
|
||
| type OnboardingGoogleButtonVariant = "login" | "connectCalendar"; | ||
|
|
||
| export interface OnboardingGoogleButtonProps { |
There was a problem hiding this comment.
| export interface OnboardingGoogleButtonProps { | |
| interface OnboardingGoogleButtonProps { |
- OnboardingGoogleButtonProps 인터페이스의 불필요한 export 키워드를 제거했습니다
yumin-kim2
left a comment
There was a problem hiding this comment.
수정사항 확인했습니다! 고생하셨서요~~❣️

ISSUE 🔗
close #118
What is this PR? 🔍
온보딩 화면에서 사용할 컴포넌트 5종(OnboardingButton, OnboardingGoogleButton, OnboardingTimeDropdown, OnboardingStepButton, OnboardingSelectCard)을 구현하고, 코드래빗 리뷰 반영 및 컴포넌트·컨테이너 계층 분리 작업을 진행했습니다.
배경
onboarding/_components에, 클라이언트 상태와 번역을 소유하는 컴포넌트는onboarding/_containers에 분리했습니다. 컴포넌트에서"use client"와useTranslations를 제거해page.tsx가 Server Component를 유지할 수 있도록 했습니다.OnboardingButton
useTranslations를 직접 호출하면"use client"선언이 강제되어 부모 Server Component에서 클라이언트 경계가 당겨집니다. 또한 버튼 텍스트를 컴포넌트 안에서 결정하면 재사용 시 i18n 키가 고정되는 제약이 생깁니다.label: stringprop을 추가해 텍스트를 외부에서 주입하고,OnboardingButtonContainer가useTranslations로 번역된 텍스트를 내려줍니다.variant: "next" | "prev" | "start",isActive,disabledprop으로 3종 버튼을 단일 인터페이스에서 처리합니다.OnboardingButtonContainer가 담당합니다.OnboardingGoogleButton
"use client"와 번역 로직을 제거했습니다.OnboardingButton과 동일하게useTranslations호출로 인해"use client"가 강제되는 구조였습니다.label: stringprop을 추가해 버튼 텍스트를 외부에서 주입합니다.OnboardingGoogleButtonContainer가login | connectCalendarvariant에 따른 번역 텍스트를 내려줍니다. 너비는w-full로 선언해 부모에서 제어합니다.OnboardingTimeDropdown
"use client"제거 및getAmPm함수를utils/get-am-pm.ts로 분리했습니다.useState나 이벤트 핸들러 없이onChangeprop을 받는 순수 제어 컴포넌트이므로"use client"선언이 불필요합니다.getAmPm함수는 온보딩 외 다른 곳에서도 재사용될 수 있어 유틸로 분리했습니다.getAmPm함수를apps/timo-web/utils/get-am-pm.ts로 추출하고 절대 경로(@/utils/get-am-pm)로 import합니다. 피그마 레이아웃의 outer panel과 inner list 2중 구조를Dropdown.Panel과 내부div로 각각 표현했습니다.OnboardingTimeContainer가 담당합니다.컨테이너 계층
OnboardingButtonContainer,OnboardingGoogleButtonContainer,OnboardingTimeContainer를_containers/에 추가했습니다."use client"와useTranslations/useState를 컴포넌트에서 분리해_components/의 순수 UI 컴포넌트 컨벤션을 유지하기 위함입니다.useTranslations또는useState를 소유하고 번역된 텍스트와 상태를 하위 컴포넌트에 주입합니다.OnboardingTimeContainer는 기존OnboardingTimeField를 대체합니다.OnboardingStepButton
stepprop(1|2|3|4)을 받아 현재 단계는bg-timo-blue-300 text-white, 나머지는bg-timo-blue-65 text-timo-blue-100으로 스타일링합니다. 단계 목록을ol/li시맨틱 태그로 구성했습니다.디자인 시스템 에셋
packages/timo-design-system/src/icons/source/에 SVG를 추가하고icons:generate스크립트로 컴포넌트를 생성했습니다.scrollbar.css
*::-webkit-scrollbar너비를 6px에서 8px로 수정했습니다.offsetWidth - clientWidth를 측정해 확인했습니다.To Reviewers
컴포넌트에서
"use client"와useTranslations를 제거하고 컨테이너로 위임하는 구조로 전환했습니다._components/는 순수 UI만,_containers/는 상태·번역을 담당하는 분리 기준이 온보딩 페이지 구현 시에도 유지되는지 확인 부탁드립니다.OnboardingButton의"next"variant 사용 시 이제labelprop이 필수입니다. 기존 코드에서"next_active","start_inactive"variant를 사용 중인 곳이 있다면 마이그레이션이 필요합니다.Screenshot 📷
Timo.-.Chrome.2026-07-08.12-52-38.mp4
Test Checklist ✔
/onboarding라우트 렌더링 확인 —OnboardingTimeDropdownAM/PM 표시 및 드롭다운 동작 확인pnpm check-types— 미실행pnpm build— 미실행