From b303c83fecd07cd7ba75faa97824fb68ddd744ac Mon Sep 17 00:00:00 2001 From: jjangminii Date: Thu, 2 Jul 2026 15:29:47 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat(ui):=20TogglePanel=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80=20(#63)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Timebox/Timer 두 옵션을 전환하는 TogglePanel 컴포넌트를 추가했습니다 - 너비는 w-full로 두고 부모가 padding/wrapper로 크기를 제어하도록 했습니다 - Default/Big/AllSizes Storybook 스토리를 추가했습니다 --- .../src/components/index.ts | 1 + .../toggle-panel/TogglePanel.stories.tsx | 63 +++++++++++++++++++ .../components/toggle-panel/TogglePanel.tsx | 41 ++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx create mode 100644 packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx diff --git a/packages/timo-design-system/src/components/index.ts b/packages/timo-design-system/src/components/index.ts index 579ad40..dedd1be 100644 --- a/packages/timo-design-system/src/components/index.ts +++ b/packages/timo-design-system/src/components/index.ts @@ -3,3 +3,4 @@ export { Color } from "./color/Color"; export { Typography } from "./typography/Typography"; export { Tag } from "./tag/Tag"; export { PriorityIcon } from "./priority-icon/PriorityIcon"; +export { TogglePanel } from "./toggle-panel/TogglePanel"; diff --git a/packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx b/packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx new file mode 100644 index 0000000..f62aa4c --- /dev/null +++ b/packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx @@ -0,0 +1,63 @@ +import { useState } from "react"; + +import { TogglePanel } from "./TogglePanel"; + +import type { TogglePanelValue } from "./TogglePanel"; +import type { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Components/TogglePanel", + component: TogglePanel, + parameters: { + layout: "centered", + }, + args: { + value: "timebox", + onChange: () => {}, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const PlaygroundTogglePanel = ({ + width, + ...args +}: React.ComponentProps & { width: string }) => { + const [value, setValue] = useState(args.value); + return ( +
+ +
+ ); +}; + +export const Default: Story = { + render: (args) => , +}; + +export const Big: Story = { + args: { + value: "timer", + }, + render: (args) => , +}; + +export const AllSizes: Story = { + render: () => ( +
+
+

Default (268px)

+
+ {}} /> +
+
+
+

Big (404px)

+
+ {}} /> +
+
+
+ ), +}; diff --git a/packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx b/packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx new file mode 100644 index 0000000..e48de2a --- /dev/null +++ b/packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx @@ -0,0 +1,41 @@ +import { cn } from "@lib"; + +export type TogglePanelValue = "timebox" | "timer"; + +export interface TogglePanelProps { + value: TogglePanelValue; + onChange: (value: TogglePanelValue) => void; +} + +export const TogglePanel = ({ value, onChange }: TogglePanelProps) => { + return ( +
+ + +
+ ); +}; From 1b385b9ca0213bb2ead41ce74c95da2491bf711b Mon Sep 17 00:00:00 2001 From: jjangminii Date: Thu, 2 Jul 2026 15:53:38 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix(ui):=20TogglePanel=20=EC=8A=A4=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20Controls=20value=20=EB=8F=99=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95=20(#63)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PlaygroundTogglePanel이 args.value를 마운트 시점에만 useState 초기값으로 읽어서 Storybook Controls에서 value를 바꿔도 반영되지 않던 문제를 수정했습니다 - useEffect로 args.value 변경 시 내부 state를 동기화하도록 했습니다 --- .../toggle-panel/TogglePanel.stories.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx b/packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx index f62aa4c..5087951 100644 --- a/packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx +++ b/packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx @@ -1,8 +1,9 @@ -import { useState } from "react"; +import { + TogglePanel, + TogglePanelValue, +} from "@components/toggle-panel/TogglePanel"; +import { useEffect, useState } from "react"; -import { TogglePanel } from "./TogglePanel"; - -import type { TogglePanelValue } from "./TogglePanel"; import type { Meta, StoryObj } from "@storybook/react"; const meta = { @@ -25,6 +26,11 @@ const PlaygroundTogglePanel = ({ ...args }: React.ComponentProps & { width: string }) => { const [value, setValue] = useState(args.value); + + useEffect(() => { + setValue(args.value); + }, [args.value]); + return (
From 4aa0522611817e99faacd5f4edfc5cf959b4f144 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Fri, 3 Jul 2026 01:54:41 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix(ui):=20TogglePanel=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=EC=84=B1=EC=9D=84=20tab=20=ED=8C=A8=ED=84=B4=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0=20(#63)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 리뷰 반영: aria-pressed 대신 role="tablist"/"tab" + aria-selected로 tab 시맨틱을 적용했습니다 - 좌우 화살표 키보드 네비게이션과 roving tabindex를 추가했습니다 - 두 버튼의 중복 마크업을 options 배열 기반 렌더링으로 정리했습니다 - id prop을 추가해 외부 tabpanel과 aria-controls/aria-labelledby로 연결할 수 있게 하고, Storybook에 WithPanel 예시를 추가했습니다 --- .../toggle-panel/TogglePanel.stories.tsx | 45 ++++++++ .../components/toggle-panel/TogglePanel.tsx | 100 +++++++++++++----- 2 files changed, 117 insertions(+), 28 deletions(-) diff --git a/packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx b/packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx index 5087951..0f39c36 100644 --- a/packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx +++ b/packages/timo-design-system/src/components/toggle-panel/TogglePanel.stories.tsx @@ -49,6 +49,51 @@ export const Big: Story = { render: (args) => , }; +const TogglePanelWithPanel = ( + args: React.ComponentProps, +) => { + const [value, setValue] = useState(args.value); + + useEffect(() => { + setValue(args.value); + }, [args.value]); + + return ( +
+ + + +
+ ); +}; + +export const WithPanel: Story = { + render: (args) => , +}; + export const AllSizes: Story = { render: () => (
diff --git a/packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx b/packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx index e48de2a..3ac34ec 100644 --- a/packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx +++ b/packages/timo-design-system/src/components/toggle-panel/TogglePanel.tsx @@ -1,41 +1,85 @@ import { cn } from "@lib"; +import { useId, useRef } from "react"; + +import type { KeyboardEvent, RefObject } from "react"; export type TogglePanelValue = "timebox" | "timer"; export interface TogglePanelProps { + id?: string; value: TogglePanelValue; onChange: (value: TogglePanelValue) => void; + timeboxControls?: string; + timerControls?: string; +} + +interface TogglePanelOption { + value: TogglePanelValue; + label: string; + ref: RefObject; + controls?: string; } -export const TogglePanel = ({ value, onChange }: TogglePanelProps) => { +export const TogglePanel = ({ + id: idProp, + value, + onChange, + timeboxControls, + timerControls, +}: TogglePanelProps) => { + const generatedId = useId(); + const id = idProp ?? generatedId; + const timeboxRef = useRef(null); + const timerRef = useRef(null); + + const options: TogglePanelOption[] = [ + { + value: "timebox", + label: "Timebox", + ref: timeboxRef, + controls: timeboxControls, + }, + { value: "timer", label: "Timer", ref: timerRef, controls: timerControls }, + ]; + + const handleKeyDown = (event: KeyboardEvent) => { + 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(); + }; + return ( -
- - +
+ {options.map((option) => ( + + ))}
); };