Skip to content

[Feat/#58] feat(web): migrate Radio#69

Merged
seungdeok merged 3 commits into
mainfrom
feat/#58
Sep 24, 2025
Merged

[Feat/#58] feat(web): migrate Radio#69
seungdeok merged 3 commits into
mainfrom
feat/#58

Conversation

@seungdeok

@seungdeok seungdeok commented Sep 23, 2025

Copy link
Copy Markdown
Member

📝 PR 유형

  • 🚀 feature 기능 추가
  • 🐞 버그 발생
  • 🔨 리팩토링
  • 📋 문서작성
  • 🌍 빌드 설정 및 문제
  • ETC

📝 PR 설명

Radio 컴포넌트 마이그레이션 작업입니다.

관련된 이슈 넘버

close #58

✅ 작업 목록

  • Radio 컴포넌트 마이그레이션(pure css -> pandacss)
  • Radio storybook

MR하기 전에 확인해주세요

  • local code lint 검사를 진행하셨나요?
  • loca ci test를 진행하셨나요?

📚 논의사항

📚 ETC

Summary by CodeRabbit

  • 신기능
    • 새로운 라디오 선택 컴포넌트 추가: 옵션 목록, 기본값 설정, 변경 이벤트 및 비활성화 지원. 체크·호버·비활성화 상태에 따른 시각적 표기 제공.
  • 문서
    • Storybook 스토리 추가: 기본 사례와 비활성화 사례로 구성되며 옵션과 기본값을 조절 가능.
  • 작업
    • 런타임 의존성 추가: @radix-ui/react-radio-group.

@seungdeok seungdeok requested a review from widse September 23, 2025 14:53
@seungdeok seungdeok self-assigned this Sep 23, 2025
@coderabbitai

coderabbitai Bot commented Sep 23, 2025

Copy link
Copy Markdown

Walkthrough

apps/web에 Radix UI 라디오 그룹 의존성을 추가하고, Radio 컴포넌트(타입 및 구현)와 해당 Storybook 스토리를 신규로 추가했습니다.

Changes

Cohort / File(s) Summary of Changes
Dependencies
apps/web/package.json
추가: @radix-ui/react-radio-group@^1.3.8
Radio Component
apps/web/src/shared/components/Radio/Radio.type.ts, apps/web/src/shared/components/Radio/Radio.tsx
신규 추가: RadioProps 타입 및 Radio 컴포넌트 구현 (options, defaultValue, onChange, disabled)
Storybook
apps/web/src/shared/components/Radio/Radio.stories.tsx
신규 추가: Storybook 메타 및 스토리(Base, Disabled) 정의

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Radio as Radio (Radix RadioGroup)
  participant App as Consumer

  User->>Radio: 옵션 클릭
  Radio->>Radio: 내부 value 업데이트
  Radio-->>App: onChange(value)

  alt disabled = true
    User--xRadio: 입력 차단
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • widse

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed 제목 '[Feat/#58] feat(web): migrate Radio'은 PR의 핵심 작업인 Radio 컴포넌트 마이그레이션을 명확하게 나타내며 간결합니다. 접두사와 이슈 번호가 포함되어 있어 관련 이슈 추적에 도움이 됩니다.
Linked Issues Check ✅ Passed 링크된 이슈 #58의 목표인 'Radio 컴포넌트 이관'과 비교했을 때 PR은 Radio.tsx, Radio.type.ts, Radio.stories.tsx 추가 및 Radix 의존성 추가 등으로 이관 목적을 충족합니다. 다만 원본(이전) 컴포넌트와의 기능·스타일 동등성에 대한 구체적 검증 기준이 이슈에 없으므로 해당 동등성 확인은 리뷰 과정에서 별도 검토가 필요합니다.
Out of Scope Changes Check ✅ Passed 변경 내역은 Radio 관련 컴포넌트 파일과 story, 그리고 apps/web/package.json에 Radix 의존성 추가로 한정되어 있어 링크된 이슈 범위를 벗어나는 변경은 발견되지 않습니다.
Description Check ✅ Passed PR 설명은 제공된 템플릿을 충실히 따르고 있으며 PR 유형, 간단한 설명, 관련 이슈(close #58), 작업 목록 및 체크리스트가 포함되어 있어 대부분의 요구사항을 충족합니다. 다만 마이그레이션으로 인한 API/사용법 변경이나 간단한 검증 결과(예: 스토리북 스냅샷, 수동 확인)를 추가하면 리뷰어가 변경 영향을 더 빠르게 이해할 수 있습니다.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#58

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 and usage tips.

@github-actions

Copy link
Copy Markdown

📚 Storybook is ready for review!
🔗 Preview: https://68ceb72871701a8b3beb1d8f-umryruiwvo.chromatic.com/

@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

🧹 Nitpick comments (5)
apps/web/src/shared/components/Radio/Radio.tsx (3)

2-2: 취약한 상대 경로 대신 alias import 권장

프로젝트 루트 기준의 alias(styled-system/css) 또는 TS path alias 사용을 권장합니다. 리팩터 시:

-import { css } from "../../../../styled-system/css";
+import { css } from "styled-system/css";

5-6: id 충돌 방지: useId로 고유 ID 사용 권장

옵션 값이 동일하거나 다국어/공백 포함 시 ID 충돌 가능성이 있습니다.

+import { useId } from "react";
@@
-export function Radio({ options, defaultValue, onChange, disabled = false }: RadioProps) {
-  return (
+export function Radio({ options, defaultValue, onChange, disabled = false }: RadioProps) {
+  const baseId = useId();
+  return (
@@
-            id={`radio-${option.value}`}
+            id={`${baseId}-${option.value}`}
@@
-            htmlFor={`radio-${option.value}`}
+            htmlFor={`${baseId}-${option.value}`}

Also applies to: 42-43, 69-69


58-58: 중복 인디케이터 제거 또는 스타일 지정

커스텀 점(div)으로 체크 상태를 표현하고 있어 <RadioGroupBase.Indicator />는 불필요합니다. 제거하거나 스타일을 부여하세요.

-            <RadioGroupBase.Indicator />
apps/web/src/shared/components/Radio/Radio.stories.tsx (2)

1-1: Storybook 타입 임포트 경로 확인 필요

일반적으로 타입은 @storybook/react에서 임포트합니다. 현재 프레임워크 패키지에서 타입이 재노출되는지 확인하거나 아래로 변경을 고려해 주세요.

-import type { Meta, StoryObj } from "@storybook/nextjs-vite";
+import type { Meta, StoryObj } from "@storybook/react";

12-33: 컨트롤 UX 개선 제안

  • options는 객체 배열이므로 select 대신 object 컨트롤이 적합합니다.
  • 변경 이벤트 가시화를 위해 onChange 액션 추가를 권장합니다.
   argTypes: {
     options: {
-      control: "select",
-      options: [
-        [
-          { label: "프로젝트1", value: "프로젝트1" },
-          { label: "프로젝트2", value: "프로젝트2" },
-          { label: "프로젝트3", value: "프로젝트3" },
-        ],
-      ],
+      control: "object",
       description: "radio의 옵션을 지정",
     },
+    onChange: {
+      action: "change",
+      description: "선택 값 변경 핸들러",
+    },
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18ae79d and 48627e5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/web/package.json (1 hunks)
  • apps/web/src/shared/components/Radio/Radio.stories.tsx (1 hunks)
  • apps/web/src/shared/components/Radio/Radio.tsx (1 hunks)
  • apps/web/src/shared/components/Radio/Radio.type.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web/src/shared/components/Radio/Radio.stories.tsx (2)
apps/web/src/shared/components/Radio/Radio.tsx (1)
  • Radio (5-77)
apps/web/src/shared/components/Radio/Radio.type.ts (1)
  • RadioProps (1-9)
apps/web/src/shared/components/Radio/Radio.tsx (1)
apps/web/src/shared/components/Radio/Radio.type.ts (1)
  • RadioProps (1-9)
🔇 Additional comments (1)
apps/web/package.json (1)

18-18: Radix RadioGroup 의존성 추가 LGTM

React 19/Next 15 조합과도 호환 문제 없어 보입니다. 워크스페이스 전반에서 잠재 중복 설치/버전 상이만 한 번 확인해 주세요.

Comment thread apps/web/src/shared/components/Radio/Radio.tsx
Comment thread apps/web/src/shared/components/Radio/Radio.type.ts
Comment thread apps/web/src/shared/components/Radio/Radio.tsx
@github-actions

Copy link
Copy Markdown

📚 Storybook is ready for review!
🔗 Preview: https://68ceb72871701a8b3beb1d8f-ajzakuqnii.chromatic.com/

@seungdeok seungdeok merged commit 7ee564d into main Sep 24, 2025
3 of 4 checks passed
@seungdeok seungdeok deleted the feat/#58 branch September 24, 2025 14:01

@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: 1

🧹 Nitpick comments (3)
apps/web/src/shared/components/Radio/Radio.tsx (3)

7-16: 키보드 탐색 일관성: orientation="vertical" 지정 권장

세로 스택 레이아웃에 맞춰 Radix의 roving focus 동작을 명확히 하려면 orientation을 명시하는 것이 안전합니다.

     <RadioGroupBase.Root
       defaultValue={defaultValue}
       onValueChange={onChange}
       disabled={disabled}
+      orientation="vertical"
       className={css({
         display: "flex",
         alignItems: "center",
         flexDirection: "column",
         gap: "8px",
       })}
     >

추가로, 폼 통합이 필요하다면 Root에 name 전달을 지원하도록 RadioProps에 name?: string 추가도 고려해주세요.


44-59: 중복된 표시 요소 정리: 커스텀 점(div) 대신 Indicator만 스타일링

현재 커스텀 점(div)과 Radix Indicator가 함께 존재합니다. Indicator만 스타일링하면 DOM 중복을 줄이고 상태 연동(checked 시 표시)이 자연스럽습니다.

-          >
-            <div
-              className={css({
-                width: "10px",
-                height: "10px",
-                borderRadius: "50%",
-                position: "absolute",
-                top: "50%",
-                left: "50%",
-                transform: "translate(-50%, -50%)",
-                backgroundColor: "transparent",
-                "[data-state=checked] &": { backgroundColor: "#3A8DFF" },
-                "[data-disabled] &": { backgroundColor: "#B7C2D0" },
-              })}
-            />
-            <RadioGroupBase.Indicator />
+          >
+            <RadioGroupBase.Indicator
+              className={css({
+                width: "10px",
+                height: "10px",
+                borderRadius: "50%",
+                position: "absolute",
+                top: "50%",
+                left: "50%",
+                transform: "translate(-50%, -50%)",
+                backgroundColor: "#3A8DFF",
+                "[data-disabled] &": { backgroundColor: "#B7C2D0" },
+              })}
+            />

61-69: label의 aria-disabled 제거 및 그룹 상태 기반 스타일로 대체

label에 aria-disabled를 부여해도 의미론적 비활성화가 되지 않습니다. 그룹/아이템의 disabled 상태에 따라 스타일만 반영하면 충분합니다.

           <label
             className={css({
               display: "flex",
               alignItems: "center",
               cursor: "pointer",
               userSelect: "none",
-              '&[aria-disabled="true"]': { opacity: 0.5, cursor: "not-allowed" },
+              "[data-disabled] &": { opacity: 0.5, cursor: "not-allowed" },
             })}
-            aria-disabled={disabled}
             htmlFor={`radio-${option.value}`}
           >
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48627e5 and e74eea7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/web/package.json (1 hunks)
  • apps/web/src/shared/components/Radio/Radio.stories.tsx (1 hunks)
  • apps/web/src/shared/components/Radio/Radio.tsx (1 hunks)
  • apps/web/src/shared/components/Radio/Radio.type.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/package.json
  • apps/web/src/shared/components/Radio/Radio.stories.tsx
  • apps/web/src/shared/components/Radio/Radio.type.ts
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/shared/components/Radio/Radio.tsx (1)
apps/web/src/shared/components/Radio/Radio.type.ts (1)
  • RadioProps (3-11)
🔇 Additional comments (2)
apps/web/src/shared/components/Radio/Radio.tsx (2)

27-40: 포커스 가시성 개선(접근성): _focusVisible 아웃라인 추가

키보드 사용자에게 포커스 위치가 명확히 보여야 합니다. Item에 포커스 링을 추가하는 것을 권장합니다.

           <RadioGroupBase.Item
             className={css({
               margin: "11px",
               width: "20px",
               height: "20px",
               borderRadius: "50%",
               border: "1px solid #B7C2D0",
               cursor: "pointer",
               position: "relative",
               flexShrink: 0,
               _hover: { borderColor: "#3A8DFF" },
               '&[data-state="checked"]': { borderColor: "#3A8DFF" },
               "&[data-disabled]": { borderColor: "#B7C2D0", cursor: "not-allowed" },
+              _focusVisible: { outline: "2px solid #3A8DFF", outlineOffset: "2px" },
             })}
             value={option.value}
             id={`radio-${option.value}`}
           >

또한 컬러/스페이싱에 테마 토큰(예: 색상 토큰, space/size 토큰) 사용을 검토하면 디자인 일관성과 다크모드 대응성이 좋아집니다.


3-3: 부정확한 지적 — Radio.type.ts에 이미 ReactNode가 import 되어 있습니다.
apps/web/src/shared/components/Radio/Radio.type.ts:1 — import type { ReactNode } from "react";

Likely an incorrect or invalid review comment.

Comment on lines +41 to +43
value={option.value}
id={`radio-${option.value}`}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

중복 id 위험: useId로 id 안정화

여러 Radio 컴포넌트가 동일 value를 가질 경우 id={radio-${value}}가 페이지 전역에서 중복될 수 있어 라벨 연결이 깨질 수 있습니다. React useId로 고유 prefix를 부여하세요.

-            id={`radio-${option.value}`}
+            id={`${baseId}-${option.value}`}
-            htmlFor={`radio-${option.value}`}
+            htmlFor={`${baseId}-${option.value}`}

컴포넌트 내부에 baseId 선언 및 import 추가(파일 외 변경):

import { useId } from "react";

export function Radio(props: RadioProps) {
  const baseId = useId();
  // ...
}

Also applies to: 69-69

🤖 Prompt for AI Agents
In apps/web/src/shared/components/Radio/Radio.tsx around lines 41-43 (and also
at line 69), the component currently builds IDs as `radio-${option.value}` which
can collide across multiple Radio instances; import React's useId, create a
baseId inside the component (e.g. const baseId = useId()), and prefix the
generated id with it (for example `${baseId}-radio-${option.value}`) so each
Radio instance has a unique id; update both the input id and the corresponding
label htmlFor to use the new prefixed id and add the useId import at the top of
the file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Radio] fireeren-component to Time-align Code 이관

2 participants