Skip to content

[FEAT] 온보딩 화면 OnboardingSelectCard 컴포넌트 구현#117

Merged
ehye1 merged 2 commits into
developfrom
feat/web/108-onboarding-select-card
Jul 8, 2026
Merged

[FEAT] 온보딩 화면 OnboardingSelectCard 컴포넌트 구현#117
ehye1 merged 2 commits into
developfrom
feat/web/108-onboarding-select-card

Conversation

@ehye1

@ehye1 ehye1 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

ISSUE 🔗

close #108



What is this PR? 🔍

온보딩 화면에서 언어 및 활동 유형 선택 시 공통으로 사용하는 OnboardingSelectCard 컴포넌트를 구현했습니다.

배경

  • 기존 구조: 온보딩 선택 카드 컴포넌트가 없어 온보딩 flow 구현이 불가능한 상태였습니다.
  • 발생 문제: 언어 선택·활동 유형 선택처럼 반복되는 선택 UI를 매번 개별 구현해야 하는 상황이었습니다.
  • 해결 방향: size, selected, label, sublabel props로 상태와 레이아웃을 외부에서 주입받는 순수 UI 컴포넌트로 구현했습니다.

OnboardingSelectCard

  • 변경 요약: sm/lg 두 가지 사이즈와 selected 상태를 props로 제어하는 선택 카드 컴포넌트를 추가했습니다.
  • 이유: 언어 선택(sm, 150px column)과 활동 유형 선택(lg, 304px row)은 레이아웃이 다르지만 색상·선택 로직이 동일해 단일 컴포넌트로 처리했습니다.
  • 구현 방식: size="sm"은 column 레이아웃(텍스트 좌상단, 라디오 우하단), size="lg"는 row + justify-between 레이아웃으로 분기합니다. 라디오 아이콘은 별도 아이콘 컴포넌트 없이 border-[5px] 두꺼운 테두리로 선택 상태의 링(도넛) 모양을 직접 구현했습니다.
  • 경계 · 제약: 선택 상태 관리는 포함하지 않으며, selected·onClick을 부모에서 주입받는 순수 UI로 유지했습니다.



To Reviewers

라디오 아이콘을 아이콘 컴포넌트 없이 CSS border-[5px]로 링 모양을 직접 구현했습니다. <button> 요소의 UA 기본 스타일(text-align: center)을 text-left로 재정의했습니다.



Screenshot 📷

Timo.-.Chrome.2026-07-08.04-19-10.mp4

Test Checklist ✔

  • TypeScript 타입 체크 통과 (pnpm check-types)
  • ESLint 통과 (pnpm lint)
  • 브라우저에서 sm/lg 사이즈 레이아웃 확인
  • selected 상태 전환 시 색상·라디오 아이콘 변경 확인

- sm/lg 사이즈 props로 레이아웃을 제어했습니다
- selected 상태에 따라 배경·보더·텍스트 색상을 변경했습니다
- 라디오 아이콘을 별도 컴포넌트 없이 직접 구현했습니다
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
timo Ready Ready Preview, Comment Jul 8, 2026 4:45pm

@github-actions github-actions Bot requested review from jjangminii and kimminna July 7, 2026 19:34
@github-actions github-actions Bot added the ⏰ Timo-web Timo 웹 서비스 label Jul 7, 2026
@github-actions github-actions Bot added ✨ Feature 새로운 기능(기능성) 구현 ♥️ 혜원 혜원양 labels Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cc3f2743-e443-4f91-83ea-470e7e02fb64

📥 Commits

Reviewing files that changed from the base of the PR and between 8d6de18 and 4dec136.

📒 Files selected for processing (1)
  • apps/timo-web/app/[locale]/onboarding/_components/OnboardingSelectCard.tsx

Walkthrough

온보딩 화면용 OnboardingSelectCard 컴포넌트를 추가했다. size, selected, label, sublabel, onClick props로 선택 상태와 sm/lg 레이아웃을 제어하며, 상태에 따라 카드 스타일과 라디오 아이콘 표시를 분기한다.

Changes

OnboardingSelectCard 컴포넌트

Layer / File(s) Summary
Props 타입 및 컴포넌트 골격 정의
apps/timo-web/app/[locale]/onboarding/_components/OnboardingSelectCard.tsx
OnboardingSelectCardProps 인터페이스를 추가하고, selected에 따라 라디오 아이콘 테두리 스타일을 구성한다.
버튼 렌더링 및 sm/lg 레이아웃 분기
apps/timo-web/app/[locale]/onboarding/_components/OnboardingSelectCard.tsx
selectedsize에 따라 버튼 테두리, 배경, 폭을 분기하고, smlg에 맞는 텍스트/아이콘 배치를 렌더링한다. onClick도 버튼에 연결한다.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning 핵심 컴포넌트는 추가됐지만, 이슈 #108의 Storybook 스토리 작성 요구는 제공된 변경 요약에서 확인되지 않습니다. Storybook 스토리를 추가하고 sm/lg 및 selected 상태를 예시로 보여주는 항목을 포함하세요.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed 변경은 온보딩 선택 카드 컴포넌트 추가에 집중되어 있어 범위를 벗어난 수정이 보이지 않습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed 핵심 변경인 온보딩용 OnboardingSelectCard 컴포넌트 구현을 명확히 드러냅니다.
Description check ✅ Passed 설명은 선택 카드 컴포넌트의 용도, props, 레이아웃, 상태 분리까지 변경 내용과 잘 맞습니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web/108-onboarding-select-card

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

❤️ Share

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

@ehye1 ehye1 changed the title [FEAT] OnboardingSelectCard 컴포넌트 구현 [FEAT] 온보딩 화면 OnboardingSelectCard 컴포넌트 구현 Jul 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/OnboardingSelectCard.tsx:
- Around line 18-27: The selected state is only conveyed visually in
OnboardingSelectCard, so make the card’s interactive element expose selection to
assistive tech by adding either aria-pressed={selected} or role="radio" with
aria-checked={selected} on the button wrapper used in this component. Also mark
the radioIcon element as purely decorative with aria-hidden="true" so screen
readers ignore it. Update the related rendering logic in OnboardingSelectCard to
keep the accessible state in sync wherever selected is used.
- Around line 43-87: The label/sublabel JSX in OnboardingSelectCard is
duplicated across the sm and lg branches, so extract the shared content into a
single reusable fragment/variable and keep only the wrapper layout conditional;
update the selected/text color logic once in that shared block so future changes
to label, sublabel, or cn usage stay in sync.
🪄 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: 4e7647ad-9c51-41cb-9e43-ae72d191f181

📥 Commits

Reviewing files that changed from the base of the PR and between cf96425 and 8d6de18.

📒 Files selected for processing (1)
  • apps/timo-web/app/[locale]/onboarding/_components/OnboardingSelectCard.tsx

Comment on lines +18 to +27
const radioIcon = (
<div
className={cn(
"size-[18px] shrink-0 rounded-full bg-white",
selected
? "border-timo-blue-300 border-[5px]"
: "border-timo-gray-500 border-2",
)}
/>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

선택 상태를 스크린리더에도 알려주면 더 좋아요.

현재는 색상/테두리로만 selected 상태를 표현해서, 스크린리더 사용자는 카드가 선택됐는지 알 방법이 없습니다. 라디오 형태의 선택 UI이므로 buttonaria-pressed={selected} (혹은 role="radio" aria-checked={selected})를 추가하고, 순수 장식용인 radioIcon에는 aria-hidden="true"를 붙여주는 걸 제안합니다.

♿ 제안 diff
   const radioIcon = (
     <div
+      aria-hidden="true"
       className={cn(
     <button
       type="button"
+      aria-pressed={selected}
       onClick={onClick}

관련해서 WAI-ARIA의 button role 패턴을 참고하시면 좋아요.

[accessibility]

Also applies to: 30-42

🤖 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/OnboardingSelectCard.tsx
around lines 18 - 27, The selected state is only conveyed visually in
OnboardingSelectCard, so make the card’s interactive element expose selection to
assistive tech by adding either aria-pressed={selected} or role="radio" with
aria-checked={selected} on the button wrapper used in this component. Also mark
the radioIcon element as purely decorative with aria-hidden="true" so screen
readers ignore it. Update the related rendering logic in OnboardingSelectCard to
keep the accessible state in sync wherever selected is used.

Comment on lines +43 to +87
{size === "sm" ? (
<div className="flex w-full flex-col items-end gap-1">
<div className="flex w-full flex-col">
<span
className={cn(
"typo-headline-b-16",
selected ? "text-timo-blue-300" : "text-timo-gray-900",
)}
>
{label}
</span>
<span
className={cn(
"typo-body-m-12",
selected ? "text-timo-blue-300" : "text-timo-gray-700",
)}
>
{sublabel}
</span>
</div>
{radioIcon}
</div>
) : (
<>
<div className="flex flex-col">
<span
className={cn(
"typo-headline-b-16",
selected ? "text-timo-blue-300" : "text-timo-gray-900",
)}
>
{label}
</span>
<span
className={cn(
"typo-body-m-12",
selected ? "text-timo-blue-300" : "text-timo-gray-700",
)}
>
{sublabel}
</span>
</div>
{radioIcon}
</>
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

label/sublabel 렌더링 블록 중복 제거를 제안합니다.

sm(라인 45-62)과 lg(라인 67-84) 분기의 내부 콘텐츠가 wrapper만 다르고 완전히 동일합니다. 한쪽만 수정하고 다른 쪽을 놓치는 실수가 생기기 쉬운 구조입니다. 공통 콘텐츠를 변수로 뽑아 wrapper만 분기하면 훨씬 안전합니다.

♻️ 제안 리팩터
+  const content = (
+    <div className={cn("flex", size === "sm" ? "w-full flex-col" : "flex-col")}>
+      <span
+        className={cn(
+          "typo-headline-b-16",
+          selected ? "text-timo-blue-300" : "text-timo-gray-900",
+        )}
+      >
+        {label}
+      </span>
+      <span
+        className={cn(
+          "typo-body-m-12",
+          selected ? "text-timo-blue-300" : "text-timo-gray-700",
+        )}
+      >
+        {sublabel}
+      </span>
+    </div>
+  );
+
   return (
     <button
       ...
     >
       {size === "sm" ? (
         <div className="flex w-full flex-col items-end gap-1">
-          <div className="flex w-full flex-col">
-            <span ...>{label}</span>
-            <span ...>{sublabel}</span>
-          </div>
+          {content}
           {radioIcon}
         </div>
       ) : (
         <>
-          <div className="flex flex-col">
-            <span ...>{label}</span>
-            <span ...>{sublabel}</span>
-          </div>
+          {content}
           {radioIcon}
         </>
       )}
     </button>
   );

참고로 React 공식 문서의 Conditional Rendering 가이드에서도 공통 JSX는 변수로 추출해 재사용하는 패턴을 권장합니다.

📝 Committable suggestion

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

Suggested change
{size === "sm" ? (
<div className="flex w-full flex-col items-end gap-1">
<div className="flex w-full flex-col">
<span
className={cn(
"typo-headline-b-16",
selected ? "text-timo-blue-300" : "text-timo-gray-900",
)}
>
{label}
</span>
<span
className={cn(
"typo-body-m-12",
selected ? "text-timo-blue-300" : "text-timo-gray-700",
)}
>
{sublabel}
</span>
</div>
{radioIcon}
</div>
) : (
<>
<div className="flex flex-col">
<span
className={cn(
"typo-headline-b-16",
selected ? "text-timo-blue-300" : "text-timo-gray-900",
)}
>
{label}
</span>
<span
className={cn(
"typo-body-m-12",
selected ? "text-timo-blue-300" : "text-timo-gray-700",
)}
>
{sublabel}
</span>
</div>
{radioIcon}
</>
)}
const content = (
<div className={cn("flex", size === "sm" ? "w-full flex-col" : "flex-col")}>
<span
className={cn(
"typo-headline-b-16",
selected ? "text-timo-blue-300" : "text-timo-gray-900",
)}
>
{label}
</span>
<span
className={cn(
"typo-body-m-12",
selected ? "text-timo-blue-300" : "text-timo-gray-700",
)}
>
{sublabel}
</span>
</div>
);
return (
<button>
{size === "sm" ? (
<div className="flex w-full flex-col items-end gap-1">
{content}
{radioIcon}
</div>
) : (
<>
{content}
{radioIcon}
</>
)}
</button>
);
🤖 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/OnboardingSelectCard.tsx
around lines 43 - 87, The label/sublabel JSX in OnboardingSelectCard is
duplicated across the sm and lg branches, so extract the shared content into a
single reusable fragment/variable and keep only the wrapper layout conditional;
update the selected/text color logic once in that shared block so future changes
to label, sublabel, or cn usage stay in sync.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Timo Performance Report

Bundle Size — timo-web
라우트 크기 First Load JS
/[locale] 0 B 🟡 205.79 kB
/[locale]/focus 0 B 🟡 205.79 kB
/[locale]/home 0 B 🟡 205.79 kB
/[locale]/onboarding 0 B 🟡 205.79 kB
/[locale]/settings 0 B 🟡 205.79 kB
/[locale]/settings/account 0 B 🟡 205.79 kB
/[locale]/settings/policy 0 B 🟡 205.79 kB
/[locale]/statistics 0 B 🟡 205.79 kB
/[locale]/today 0 B 🟡 205.79 kB

공유 번들: 205.79 kB
🟢 < 200kB  |  🟡 < 350kB  |  🔴 ≥ 350kB (First Load JS · gzip)

Lighthouse — timo-web

⚠️ Lighthouse 결과를 가져오지 못했습니다.

Image Optimization — timo-web

public/ 디렉토리에 이미지가 없습니다.

측정 커밋: 69547af

@jjangminii jjangminii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

깔끔하고 이쁘네요 간단한 코멘트만 확인해주세요~

const radioIcon = (
<div
className={cn(
"size-[18px] shrink-0 rounded-full bg-white",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

18px만 4.5로 바꿔주세요~

Comment on lines +39 to +40
? "w-[150px]"
: "w-[304px] flex-row items-center justify-between",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

이것도 px말고 테일윈드 토큰 사이즈로 바꿔주세여

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

네!!!! 변경했습니다!!

- size-[18px]을 size-4.5로 교체했습니다
- w-[150px]을 w-37.5로 교체했습니다
- w-[304px]을 w-76으로 교체했습니다
@ehye1 ehye1 merged commit 6e082b2 into develop Jul 8, 2026
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능(기능성) 구현 ⏰ Timo-web Timo 웹 서비스 ♥️ 혜원 혜원양

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 온보딩 선택 카드 컴포넌트 구현

3 participants