Skip to content

[FEAT] TogglePanel 컴포넌트 추가#76

Open
jjangminii wants to merge 4 commits into
developfrom
feat/ui/63-toggle-panel
Open

[FEAT] TogglePanel 컴포넌트 추가#76
jjangminii wants to merge 4 commits into
developfrom
feat/ui/63-toggle-panel

Conversation

@jjangminii

Copy link
Copy Markdown
Contributor

ISSUE 🔗

close #63



What is this PR? 🔍

Timebox/Timer 두 옵션을 전환하는 TogglePanel 컴포넌트를 디자인 시스템에 추가했습니다.

배경

  • 기존 구조: 디자인 시스템에 세그먼트 형태로 두 옵션을 전환하는 토글 컴포넌트가 없었습니다.
  • 발생 문제: 피그마에 toggle_panel(268×30 고정)과 toggle_panel_big(404 Hug×30 Hug) 두 사이즈 variant가 있어, 이를 컴포넌트 레벨에서 어떻게 표현할지 결정이 필요했습니다.
  • 해결 방향: 처음엔 cva 또는 Record 기반 사이즈 variant로 구현했지만, 논의 끝에 컴포넌트는 w-full만 갖고 실제 크기는 부모가 wrapper 너비로 제어하는 방향으로 단순화했습니다.

TogglePanel 컴포넌트

  • 변경 요약: value: "timebox" | "timer", onChange만 받는 controlled 컴포넌트로 구현했습니다.
  • 이유: variant 축(size)이 실제로는 색상·폰트에 영향을 주지 않고 너비에만 영향을 줬는데, 너비는 부모 레이아웃이 결정하는 게 더 자연스러워서 컴포넌트 자체의 size prop을 없앴습니다. cva 도입도 검토했지만, 이미 PriorityIcon 등에서 쓰는 cn() + 조건부 클래스 패턴만으로 충분해 새 의존성을 추가하지 않았습니다.
  • 구현 방식: 두 <button>flex로 나란히 배치하고, 각 버튼은 flex-1로 폭을 균등 분배합니다. 활성 옵션은 bg-timo-black text-white, 비활성은 bg-timo-gray-300 text-timo-blackcn() 조건부 클래스로 적용했습니다. 폰트는 typo-body-sb-12, 활성 상태는 aria-pressed로 노출했습니다. 컨테이너는 w-full이라 크기는 상위에서 wrapper 너비(w-67, w-101 등)로 제어합니다.

Storybook 스토리

  • 변경 요약: Default(268px wrapper), Big(404px wrapper), AllSizes(두 크기를 라벨과 함께 한 화면에 나열) 세 스토리를 추가했습니다.
  • 이유: 컴포넌트 자체엔 사이즈 prop이 없어졌기 때문에, 실제 사용 시 부모 wrapper로 크기를 어떻게 제어하는지 예시로 보여줄 필요가 있었습니다.



To Reviewers

size prop을 없애고 부모가 너비를 결정하는 방식이 실제 페이지에서 쓰기에 자연스러운지 한번 봐주세요. 이 컴포넌트를 실제 화면에 배치하는 작업은 이번 PR 범위에 포함되지 않았습니다.

Screenshot 📷



Test Checklist ✔

  • pnpm --filter @repo/timo-design-system check-types 통과
  • pnpm --filter @repo/timo-design-system lint 통과
  • pnpm --filter @repo/timo-design-system build:storybook 실행 → Default/Big/AllSizes 스토리 포함 빌드 성공
  • 브라우저에서 실제 렌더링 확인 — 미실행: 로컬 브라우저 자동화 도구 없음
  • pnpm build — 미실행: CI에서 확인 예정

- Timebox/Timer 두 옵션을 전환하는 TogglePanel 컴포넌트를 추가했습니다
- 너비는 w-full로 두고 부모가 padding/wrapper로 크기를 제어하도록 했습니다
- Default/Big/AllSizes Storybook 스토리를 추가했습니다
@vercel

vercel Bot commented Jul 2, 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 2, 2026 4:57pm

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

timo-design-systemtimebox/timer를 전환하는 TogglePanel 컴포넌트가 추가되었습니다. 공개 export가 등록됐고, 동작 예시와 패널 연동 스토리도 함께 포함됐습니다.

Changes

TogglePanel 컴포넌트 추가

Layer / File(s) Summary
컴포넌트 구현
packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx
TogglePanelValueTogglePanelProps를 정의하고, 탭 역할·키보드 이동·선택 상태·연결 대상 식별자를 반영하는 토글 UI를 구현했습니다.
export 및 스토리북 예시
packages/timo-design-system/src/components/index.ts, packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx
components 배럴에 TogglePanel을 노출하고, Default/Big/WithPanel/AllSizes 스토리와 상태 동기화용 헬퍼를 추가했습니다.

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

Suggested labels: ✨ Feature

Suggested reviewers: yumin-kim2, ehye1, kimminna

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 TogglePanel 컴포넌트 추가라는 핵심 변경을 간결하게 잘 담고 있습니다.
Description check ✅ Passed 설명이 TogglePanel 추가와 Storybook, 크기 예시를 다루어 변경 내용과 관련 있습니다.
Linked Issues check ✅ Passed TogglePanel 구현, Storybook 스토리, index.ts export가 모두 반영되어 #63의 요구사항을 충족합니다.
Out of Scope Changes check ✅ Passed 접근성 개선과 WithPanel 스토리는 컴포넌트/스토리 보강 범위로 보여 별도 이탈로 보이지 않습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ui/63-toggle-panel
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/ui/63-toggle-panel

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.

@jjangminii jjangminii self-assigned this Jul 2, 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: 3

🤖 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 `@packages/timo-design-system/src/components/index.ts`:
- Line 6: The barrel export in the index module is using a relative path, but
this public entrypoint should follow the repo convention and use the absolute
alias path instead. Update the export in the component index barrel for
TogglePanel to reference it through the package alias rather than a relative
import, keeping the public API shape unchanged.

In
`@packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx`:
- Around line 3-6: The story file is using relative imports for TogglePanel and
TogglePanelValue instead of the package alias convention. Update the imports in
TogglePanel.stories.tsx to use the same alias-based path style used across the
design system, and keep the existing Meta and StoryObj imports unchanged.
- Around line 23-33: PlaygroundTogglePanel currently initializes local state
from args.value only once, so Storybook control changes to value do not
propagate into the TogglePanel state. Update PlaygroundTogglePanel to keep its
internal value synchronized with args.value changes, using the existing
TogglePanelValue state and the component’s onChange handling so the preview
reflects control updates reliably.
🪄 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: 3b8073e9-13de-46d0-ae3d-345517d6c858

📥 Commits

Reviewing files that changed from the base of the PR and between 293536e and b303c83.

📒 Files selected for processing (3)
  • packages/timo-design-system/src/components/index.ts
  • packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx
  • packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx

Comment thread packages/timo-design-system/src/components/index.ts Outdated
Comment thread packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx Outdated
…panel

# Conflicts:
#	packages/timo-design-system/src/components/index.ts
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

Storybook Preview

항목 링크
Storybook 열기
Chromatic 빌드 확인

마지막 업데이트: 2026-07-02 06:54 UTC

- PlaygroundTogglePanel이 args.value를 마운트 시점에만 useState 초기값으로 읽어서 Storybook Controls에서 value를 바꿔도 반영되지 않던 문제를 수정했습니다
- useEffect로 args.value 변경 시 내부 state를 동기화하도록 했습니다

@kimminna kimminna left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

고생하셨습니다 😁😁
코멘트 확인 부탁드려요!

Comment thread packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx Outdated
Comment thread packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx Outdated
Comment thread packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx Outdated
- 리뷰 반영: aria-pressed 대신 role="tablist"/"tab" + aria-selected로 tab 시맨틱을 적용했습니다
- 좌우 화살표 키보드 네비게이션과 roving tabindex를 추가했습니다
- 두 버튼의 중복 마크업을 options 배열 기반 렌더링으로 정리했습니다
- id prop을 추가해 외부 tabpanel과 aria-controls/aria-labelledby로 연결할 수 있게 하고, Storybook에 WithPanel 예시를 추가했습니다

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

🤖 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 `@packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx`:
- Around line 56-61: The tablist container in TogglePanel lacks an accessible
name, so add an aria-label prop (or aria-labelledby support) to the div with
role="tablist" and pass it through from the component API. Update TogglePanel so
consumers can provide a contextual label for the tab group while keeping the
existing keyboard handling and tablist behavior intact.
- Around line 45-53: The handleKeyDown logic in TogglePanel currently ignores
arrow direction and always picks the first option whose value differs from the
current one. Update the keyboard handling in handleKeyDown to derive the current
index from options and choose the next or previous option based on ArrowRight
versus ArrowLeft, then call onChange and focus the selected option’s ref. Keep
the existing symbols handleKeyDown, options, value, onChange, and next/ref-based
focus flow, but make the selection index-based so it remains correct if more
options are added later.
- Line 6: Rename the union type alias `TogglePanelValue` to follow the project
naming convention by using the `Types` suffix, and update all references
accordingly. Check the `TogglePanel` component and any Storybook or related
files that import or use this alias, since the rename will affect those symbols
as well.
🪄 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: b6efeafb-a88f-43e2-b347-9ad2652ce28e

📥 Commits

Reviewing files that changed from the base of the PR and between 1b385b9 and 4aa0522.

📒 Files selected for processing (2)
  • packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx
  • packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx


import type { KeyboardEvent, RefObject } from "react";

export type TogglePanelValue = "timebox" | "timer";

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

type alias 네이밍 컨벤션 확인해주세요.

TogglePanelValue는 유니언 타입인데, 컨벤션 상 type alias는 Types 접미사를 붙이도록 되어 있어요. TogglePanelValueTypes처럼 이름을 맞춰주시면 좋을 것 같습니다 (스토리 파일에서도 함께 사용되니 리네이밍 시 영향 범위 확인 필요해요).

As per path instructions, "type alias는 유니언·튜플·리터럴에만 사용, 접미사 Types"를 따라야 합니다.

♻️ 제안
-export type TogglePanelValue = "timebox" | "timer";
+export type TogglePanelValueTypes = "timebox" | "timer";
📝 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
export type TogglePanelValue = "timebox" | "timer";
export type TogglePanelValueTypes = "timebox" | "timer";
🤖 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 `@packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx` at
line 6, Rename the union type alias `TogglePanelValue` to follow the project
naming convention by using the `Types` suffix, and update all references
accordingly. Check the `TogglePanel` component and any Storybook or related
files that import or use this alias, since the rename will affect those symbols
as well.

Source: Path instructions

Comment on lines +45 to +53
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;

event.preventDefault();
const next = options.find((option) => option.value !== value);
if (!next) return;
onChange(next.value);
next.ref.current?.focus();
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

방향 구분 없이 항상 "다른 옵션"으로 이동합니다.

options.find((option) => option.value !== value)는 ArrowLeft/ArrowRight 방향을 구분하지 않고 항상 현재값이 아닌 옵션을 찾아요. 지금은 옵션이 2개뿐이라 결과적으론 정상 동작하지만, 나중에 옵션이 3개 이상으로 늘어나면 방향과 무관하게 동일한 옵션으로만 이동하는 버그가 생길 수 있어요.

인덱스 기반으로 방향에 맞게 다음/이전 옵션을 계산하도록 방어적으로 구현해두면 향후 확장에도 안전할 것 같습니다.

🛡️ 제안
   const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
     if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;

     event.preventDefault();
-    const next = options.find((option) => option.value !== value);
-    if (!next) return;
+    const currentIndex = options.findIndex((option) => option.value === value);
+    if (currentIndex === -1) return;
+    const delta = event.key === "ArrowRight" ? 1 : -1;
+    const nextIndex = (currentIndex + delta + options.length) % options.length;
+    const next = options[nextIndex];
     onChange(next.value);
     next.ref.current?.focus();
   };
📝 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
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
event.preventDefault();
const next = options.find((option) => option.value !== value);
if (!next) return;
onChange(next.value);
next.ref.current?.focus();
};
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
event.preventDefault();
const currentIndex = options.findIndex((option) => option.value === value);
if (currentIndex === -1) return;
const delta = event.key === "ArrowRight" ? 1 : -1;
const nextIndex = (currentIndex + delta + options.length) % options.length;
const next = options[nextIndex];
onChange(next.value);
next.ref.current?.focus();
};
🤖 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 `@packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx`
around lines 45 - 53, The handleKeyDown logic in TogglePanel currently ignores
arrow direction and always picks the first option whose value differs from the
current one. Update the keyboard handling in handleKeyDown to derive the current
index from options and choose the next or previous option based on ArrowRight
versus ArrowLeft, then call onChange and focus the selected option’s ref. Keep
the existing symbols handleKeyDown, options, value, onChange, and next/ref-based
focus flow, but make the selection index-based so it remains correct if more
options are added later.

Comment on lines +56 to +61
<div
role="tablist"
tabIndex={-1}
onKeyDown={handleKeyDown}
className="flex h-7.5 w-full overflow-hidden rounded-[4px]"
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

tablist 컨테이너에 접근성 이름이 없어요.

role="tablist" 컨테이너에 aria-label(또는 aria-labelledby)이 없어서 스크린리더 사용자는 이 탭 그룹이 무엇을 위한 것인지 알기 어려울 수 있어요. WAI-ARIA Tabs 패턴에서는 tablist에 접근 가능한 이름을 제공할 것을 권장합니다.

aria-label prop을 추가해서 소비자가 컨텍스트에 맞는 라벨을 지정할 수 있게 하면 좋을 것 같아요.

🤖 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 `@packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx`
around lines 56 - 61, The tablist container in TogglePanel lacks an accessible
name, so add an aria-label prop (or aria-labelledby support) to the div with
role="tablist" and pass it through from the component API. Update TogglePanel so
consumers can provide a contextual label for the tab group while keeping the
existing keyboard handling and tablist behavior intact.

@yumin-kim2 yumin-kim2 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.

size variant를 컴포넌트 안에 두지 않고 부모가 wrapper 너비로 제어하게 바꾸신 것, 컴포넌트 책임 범위를 딱 필요한 만큼만 남긴 선택이라고 생각해요!👍 덕분에 TogglePanelProps가 value/onChange 두 개뿐이라 인터페이스가 읽기 쉬운 것 같습니다. 수고하셨써요~~!!

아 그리고 사소한거지만 PR 라벨이 누락된 것 같습니다!

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

Labels

⌚ Timo-Design-system Timo 디자인 시스템

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] TogglePanel 컴포넌트 추가

3 participants