From 4310446b9e38f09b115938302bdbedb17748cb05 Mon Sep 17 00:00:00 2001 From: kimminna Date: Thu, 2 Jul 2026 15:09:39 +0900 Subject: [PATCH 1/7] =?UTF-8?q?file(ui):=20Color=C2=B7Typography=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A5=BC=20tokens=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Color, Typography 컴포넌트를 components/tokens 폴더 하위로 이동했습니다 - index.ts의 Color, Typography import 경로를 갱신했습니다 --- packages/timo-design-system/src/components/index.ts | 4 ++-- .../src/components/{ => tokens}/color/Color.stories.tsx | 0 .../src/components/{ => tokens}/color/Color.tsx | 0 .../components/{ => tokens}/typography/Typography.stories.tsx | 0 .../src/components/{ => tokens}/typography/Typography.tsx | 0 5 files changed, 2 insertions(+), 2 deletions(-) rename packages/timo-design-system/src/components/{ => tokens}/color/Color.stories.tsx (100%) rename packages/timo-design-system/src/components/{ => tokens}/color/Color.tsx (100%) rename packages/timo-design-system/src/components/{ => tokens}/typography/Typography.stories.tsx (100%) rename packages/timo-design-system/src/components/{ => tokens}/typography/Typography.tsx (100%) diff --git a/packages/timo-design-system/src/components/index.ts b/packages/timo-design-system/src/components/index.ts index 7917669..68f73df 100644 --- a/packages/timo-design-system/src/components/index.ts +++ b/packages/timo-design-system/src/components/index.ts @@ -1,6 +1,6 @@ export { Checkbox } from "@components/checkbox/Checkbox"; -export { Color } from "@components/color/Color"; -export { Typography } from "@components/typography/Typography"; +export { Color } from "@components/tokens/color/Color"; +export { Typography } from "@components/tokens/typography/Typography"; export { Tag } from "@components/tag/Tag"; export { PriorityIcon } from "@components/priority-icon/PriorityIcon"; export { CreateButton } from "@components/button/create-button/CreateButton"; diff --git a/packages/timo-design-system/src/components/color/Color.stories.tsx b/packages/timo-design-system/src/components/tokens/color/Color.stories.tsx similarity index 100% rename from packages/timo-design-system/src/components/color/Color.stories.tsx rename to packages/timo-design-system/src/components/tokens/color/Color.stories.tsx diff --git a/packages/timo-design-system/src/components/color/Color.tsx b/packages/timo-design-system/src/components/tokens/color/Color.tsx similarity index 100% rename from packages/timo-design-system/src/components/color/Color.tsx rename to packages/timo-design-system/src/components/tokens/color/Color.tsx diff --git a/packages/timo-design-system/src/components/typography/Typography.stories.tsx b/packages/timo-design-system/src/components/tokens/typography/Typography.stories.tsx similarity index 100% rename from packages/timo-design-system/src/components/typography/Typography.stories.tsx rename to packages/timo-design-system/src/components/tokens/typography/Typography.stories.tsx diff --git a/packages/timo-design-system/src/components/typography/Typography.tsx b/packages/timo-design-system/src/components/tokens/typography/Typography.tsx similarity index 100% rename from packages/timo-design-system/src/components/typography/Typography.tsx rename to packages/timo-design-system/src/components/tokens/typography/Typography.tsx From ca05434fd46f382d083866e3975266036723d97e Mon Sep 17 00:00:00 2001 From: kimminna Date: Thu, 2 Jul 2026 15:11:12 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat(ui):=20PrioritySelector=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Figma 디자인(node-id 894-32397)을 기반으로 우선순위 선택 컴포넌트를 추가했습니다 - PriorityIcon을 priority 폴더 하위로 이동해 관련 컴포넌트를 함께 묶었습니다 - 선택 상태 전환 시 배경·텍스트·아이콘 색상에 트랜지션을 적용했습니다 - Storybook 스토리와 배경 파라미터를 추가했습니다 --- .../src/components/index.ts | 3 +- .../priority-icon/PriorityIcon.stories.tsx | 2 +- .../priority-icon/PriorityIcon.tsx | 5 +- .../PrioritySelector.stories.tsx | 48 ++++++++++++++++ .../priority-selector/PrioritySelector.tsx | 55 +++++++++++++++++++ 5 files changed, 110 insertions(+), 3 deletions(-) rename packages/timo-design-system/src/components/{ => priority}/priority-icon/PriorityIcon.stories.tsx (97%) rename packages/timo-design-system/src/components/{ => priority}/priority-icon/PriorityIcon.tsx (79%) create mode 100644 packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.stories.tsx create mode 100644 packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx diff --git a/packages/timo-design-system/src/components/index.ts b/packages/timo-design-system/src/components/index.ts index 68f73df..af8d2ea 100644 --- a/packages/timo-design-system/src/components/index.ts +++ b/packages/timo-design-system/src/components/index.ts @@ -2,6 +2,7 @@ export { Checkbox } from "@components/checkbox/Checkbox"; export { Color } from "@components/tokens/color/Color"; export { Typography } from "@components/tokens/typography/Typography"; export { Tag } from "@components/tag/Tag"; -export { PriorityIcon } from "@components/priority-icon/PriorityIcon"; +export { PriorityIcon } from "@components/priority/priority-icon/PriorityIcon"; +export { PrioritySelector } from "@components/priority/priority-selector/PrioritySelector"; export { CreateButton } from "@components/button/create-button/CreateButton"; export { TodayBadge } from "@components/badge/today-badge/TodayBadge"; diff --git a/packages/timo-design-system/src/components/priority-icon/PriorityIcon.stories.tsx b/packages/timo-design-system/src/components/priority/priority-icon/PriorityIcon.stories.tsx similarity index 97% rename from packages/timo-design-system/src/components/priority-icon/PriorityIcon.stories.tsx rename to packages/timo-design-system/src/components/priority/priority-icon/PriorityIcon.stories.tsx index 8b5b7e5..eff6883 100644 --- a/packages/timo-design-system/src/components/priority-icon/PriorityIcon.stories.tsx +++ b/packages/timo-design-system/src/components/priority/priority-icon/PriorityIcon.stories.tsx @@ -3,7 +3,7 @@ import { PriorityIcon, type Priority } from "./PriorityIcon"; import type { Meta, StoryObj } from "@storybook/react"; const meta = { - title: "Components/PriorityIcon", + title: "Components/Priority/PriorityIcon", component: PriorityIcon, parameters: { layout: "centered", diff --git a/packages/timo-design-system/src/components/priority-icon/PriorityIcon.tsx b/packages/timo-design-system/src/components/priority/priority-icon/PriorityIcon.tsx similarity index 79% rename from packages/timo-design-system/src/components/priority-icon/PriorityIcon.tsx rename to packages/timo-design-system/src/components/priority/priority-icon/PriorityIcon.tsx index dbed858..abc9c91 100644 --- a/packages/timo-design-system/src/components/priority-icon/PriorityIcon.tsx +++ b/packages/timo-design-system/src/components/priority/priority-icon/PriorityIcon.tsx @@ -26,7 +26,10 @@ export interface PriorityIconProps { export const PriorityIcon = ({ priority }: PriorityIconProps) => { return (
); }; diff --git a/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.stories.tsx b/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.stories.tsx new file mode 100644 index 0000000..810f117 --- /dev/null +++ b/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.stories.tsx @@ -0,0 +1,48 @@ +import { PrioritySelector } from "./PrioritySelector"; + +import type { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Components/Priority/PrioritySelector", + component: PrioritySelector, + parameters: { + layout: "centered", + backgrounds: { + default: "light-gray", + values: [ + { name: "light-gray", value: "#F5F5F5" }, + { name: "dark", value: "#333333" }, + { name: "white", value: "#FFFFFF" }, + ], + }, + }, + argTypes: { + selected: { + control: "select", + options: ["매우중요", "중요", "보통", "낮음"], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: {}, +}; + +export const 매우중요: Story = { + args: { selected: "매우중요" }, +}; + +export const 중요: Story = { + args: { selected: "중요" }, +}; + +export const 보통: Story = { + args: { selected: "보통" }, +}; + +export const 낮음: Story = { + args: { selected: "낮음" }, +}; diff --git a/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx b/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx new file mode 100644 index 0000000..e2a74bf --- /dev/null +++ b/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx @@ -0,0 +1,55 @@ +import { cn } from "@lib"; + +import { PriorityIcon } from "../priority-icon/PriorityIcon"; + +export type PriorityLevel = "매우중요" | "중요" | "보통" | "낮음"; + +const PRIORITY_LEVELS: PriorityLevel[] = ["매우중요", "중요", "보통", "낮음"]; + +const PRIORITY_BG_COLOR: Record = { + 매우중요: "bg-timo-red", + 중요: "bg-timo-orange", + 보통: "bg-timo-gray", + 낮음: "bg-timo-gray-900", +}; + +export interface PrioritySelectorProps { + selected?: PriorityLevel; + onSelect?: (priority: PriorityLevel) => void; +} + +export const PrioritySelector = ({ + selected, + onSelect, +}: PrioritySelectorProps) => { + return ( +
+ {PRIORITY_LEVELS.map((priority) => { + const isSelected = priority === selected; + + return ( + + ); + })} +
+ ); +}; From 36cc97785a9d84fbaab55f072bf512ebf875f4b9 Mon Sep 17 00:00:00 2001 From: kimminna Date: Thu, 2 Jul 2026 15:55:28 +0900 Subject: [PATCH 3/7] =?UTF-8?q?refactor(ui):=20Dropdown=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EC=B6=9C=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 드롭다운 형태 셀렉터들이 공유하는 패널·행 스타일을 Dropdown/Dropdown.Item으로 분리했습니다 - PrioritySelector가 Dropdown을 사용하도록 리팩토링했습니다 --- .../layout/dropdown/Dropdown.stories.tsx | 38 ++++++++++++++++ .../components/layout/dropdown/Dropdown.tsx | 44 +++++++++++++++++++ .../priority-selector/PrioritySelector.tsx | 15 +++---- 3 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 packages/timo-design-system/src/components/layout/dropdown/Dropdown.stories.tsx create mode 100644 packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx diff --git a/packages/timo-design-system/src/components/layout/dropdown/Dropdown.stories.tsx b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.stories.tsx new file mode 100644 index 0000000..ad38d1f --- /dev/null +++ b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.stories.tsx @@ -0,0 +1,38 @@ +import { Dropdown } from "./Dropdown"; + +import type { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Components/layout/Dropdown", + parameters: { + layout: "centered", + backgrounds: { + default: "light-gray", + values: [ + { name: "light-gray", value: "#F5F5F5" }, + { name: "dark", value: "#333333" }, + { name: "white", value: "#FFFFFF" }, + ], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const SAMPLE_ITEMS = ["옵션 1", "옵션 2", "옵션 3"]; + +export const Default: Story = { + name: "Basic Usage", + render: () => ( + + {SAMPLE_ITEMS.map((item) => ( + + + {item} + + + ))} + + ), +}; diff --git a/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx new file mode 100644 index 0000000..bf0be62 --- /dev/null +++ b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx @@ -0,0 +1,44 @@ +import { cn } from "@lib"; + +import type { ReactNode } from "react"; + +export interface DropdownProps { + children: ReactNode; + className?: string; +} + +const DropdownRoot = ({ children, className }: DropdownProps) => { + return ( +
+ {children} +
+ ); +}; + +export interface DropdownItemProps { + children: ReactNode; + className?: string; + onClick?: () => void; +} + +const DropdownItem = ({ children, className, onClick }: DropdownItemProps) => { + return ( + + ); +}; + +export const Dropdown = Object.assign(DropdownRoot, { Item: DropdownItem }); diff --git a/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx b/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx index e2a74bf..da07b62 100644 --- a/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx +++ b/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx @@ -1,7 +1,7 @@ +import { Dropdown } from "@components/layout/dropdown/Dropdown"; +import { PriorityIcon } from "@components/priority/priority-icon/PriorityIcon"; import { cn } from "@lib"; -import { PriorityIcon } from "../priority-icon/PriorityIcon"; - export type PriorityLevel = "매우중요" | "중요" | "보통" | "낮음"; const PRIORITY_LEVELS: PriorityLevel[] = ["매우중요", "중요", "보통", "낮음"]; @@ -23,17 +23,16 @@ export const PrioritySelector = ({ onSelect, }: PrioritySelectorProps) => { return ( -
+ {PRIORITY_LEVELS.map((priority) => { const isSelected = priority === selected; return ( - + ); })} -
+ ); }; From f433393c4b49dc61d216974747a94dfb1ab2142d Mon Sep 17 00:00:00 2001 From: kimminna Date: Thu, 2 Jul 2026 15:56:02 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat(ui):=20TagSelector=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?Tag=EB=A5=BC=20TagIcon=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tag 컴포넌트를 TagIcon으로 이름을 변경해 tag-icon 폴더로 이동했습니다 - Dropdown을 사용하는 TagSelector 컴포넌트를 새로 추가했습니다 - index.ts의 export를 TagIcon, TagSelector로 갱신했습니다 --- .../src/components/index.ts | 3 +- .../TagIcon.stories.tsx} | 8 +-- .../tag/{Tag.tsx => tag-icon/TagIcon.tsx} | 4 +- .../tag/tag-selector/TagSelector.stories.tsx | 48 +++++++++++++++++ .../tag/tag-selector/TagSelector.tsx | 51 +++++++++++++++++++ 5 files changed, 107 insertions(+), 7 deletions(-) rename packages/timo-design-system/src/components/tag/{Tag.stories.tsx => tag-icon/TagIcon.stories.tsx} (79%) rename packages/timo-design-system/src/components/tag/{Tag.tsx => tag-icon/TagIcon.tsx} (82%) create mode 100644 packages/timo-design-system/src/components/tag/tag-selector/TagSelector.stories.tsx create mode 100644 packages/timo-design-system/src/components/tag/tag-selector/TagSelector.tsx diff --git a/packages/timo-design-system/src/components/index.ts b/packages/timo-design-system/src/components/index.ts index af8d2ea..6a98357 100644 --- a/packages/timo-design-system/src/components/index.ts +++ b/packages/timo-design-system/src/components/index.ts @@ -1,7 +1,8 @@ export { Checkbox } from "@components/checkbox/Checkbox"; export { Color } from "@components/tokens/color/Color"; export { Typography } from "@components/tokens/typography/Typography"; -export { Tag } from "@components/tag/Tag"; +export { TagIcon } from "@components/tag/tag-icon/TagIcon"; +export { TagSelector } from "@components/tag/tag-selector/TagSelector"; export { PriorityIcon } from "@components/priority/priority-icon/PriorityIcon"; export { PrioritySelector } from "@components/priority/priority-selector/PrioritySelector"; export { CreateButton } from "@components/button/create-button/CreateButton"; diff --git a/packages/timo-design-system/src/components/tag/Tag.stories.tsx b/packages/timo-design-system/src/components/tag/tag-icon/TagIcon.stories.tsx similarity index 79% rename from packages/timo-design-system/src/components/tag/Tag.stories.tsx rename to packages/timo-design-system/src/components/tag/tag-icon/TagIcon.stories.tsx index 6e61d91..1fc91c0 100644 --- a/packages/timo-design-system/src/components/tag/Tag.stories.tsx +++ b/packages/timo-design-system/src/components/tag/tag-icon/TagIcon.stories.tsx @@ -1,10 +1,10 @@ -import { Tag } from "./Tag"; +import { TagIcon } from "@components/tag/tag-icon/TagIcon"; import type { Meta, StoryObj } from "@storybook/react"; const meta = { - title: "Components/Tag", - component: Tag, + title: "Components/Tag/TagIcon", + component: TagIcon, parameters: { layout: "centered", }, @@ -19,7 +19,7 @@ const meta = { description: "태그 스타일 베리언트", }, }, -} satisfies Meta; +} satisfies Meta; export default meta; type Story = StoryObj; diff --git a/packages/timo-design-system/src/components/tag/Tag.tsx b/packages/timo-design-system/src/components/tag/tag-icon/TagIcon.tsx similarity index 82% rename from packages/timo-design-system/src/components/tag/Tag.tsx rename to packages/timo-design-system/src/components/tag/tag-icon/TagIcon.tsx index 96e6efe..0190b01 100644 --- a/packages/timo-design-system/src/components/tag/Tag.tsx +++ b/packages/timo-design-system/src/components/tag/tag-icon/TagIcon.tsx @@ -1,11 +1,11 @@ export type TagVariant = "default" | "blue"; -export interface TagProps { +export interface TagIconProps { text: string; variant?: TagVariant; } -export const Tag = ({ text, variant = "default" }: TagProps) => { +export const TagIcon = ({ text, variant = "default" }: TagIconProps) => { const isBlue = variant === "blue"; return (
; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: {}, +}; + +export const 일상: Story = { + args: { selected: "일상" }, +}; + +export const 운동: Story = { + args: { selected: "운동" }, +}; + +export const 업무: Story = { + args: { selected: "업무" }, +}; + +export const 기타: Story = { + args: { selected: "기타" }, +}; diff --git a/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.tsx b/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.tsx new file mode 100644 index 0000000..dd16051 --- /dev/null +++ b/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.tsx @@ -0,0 +1,51 @@ +import { Dropdown } from "@components/layout/dropdown/Dropdown"; +import { PlusIcon } from "@icons"; +import { cn } from "@lib"; + +export type Tag = "일상" | "운동" | "업무" | "기타"; + +const TAGS: Tag[] = ["일상", "운동", "업무", "기타"]; + +export interface TagSelectorProps { + selected?: Tag; + onSelect?: (tag: Tag) => void; + onAddClick?: () => void; +} + +export const TagSelector = ({ + selected, + onSelect, + onAddClick, +}: TagSelectorProps) => { + return ( + + {TAGS.map((tag) => { + const isSelected = tag === selected; + + return ( + onSelect?.(tag)} + className={cn("px-1.5 py-1", isSelected && "bg-timo-gray-500")} + > + + {tag} + + + ); + })} + + + + 추가 + + + + + ); +}; From e93c916b05968a35d413ecd83e238348876a2404 Mon Sep 17 00:00:00 2001 From: kimminna Date: Thu, 2 Jul 2026 16:12:30 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat(ui):=20Dropdown=EC=97=90=20Trigger?= =?UTF-8?q?=C2=B7Panel=20=EB=B0=8F=20=EC=97=B4=EB=A6=BC/=EB=8B=AB=ED=9E=98?= =?UTF-8?q?=20=EB=8F=99=EC=9E=91=20=EC=B6=94=EA=B0=80=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dropdown이 내부 open 상태와 외부 클릭 감지를 갖도록 확장했습니다 - Trigger 클릭 시 토글, 바깥 클릭 시 자동으로 닫히도록 구현했습니다 - PrioritySelector·TagSelector가 trigger prop을 받아 스스로 트리거를 조합하도록 변경했습니다 --- .../layout/dropdown/Dropdown.stories.tsx | 22 +++-- .../components/layout/dropdown/Dropdown.tsx | 92 ++++++++++++++++++- .../PrioritySelector.stories.tsx | 16 +++- .../priority-selector/PrioritySelector.tsx | 54 ++++++----- .../tag/tag-selector/TagSelector.stories.tsx | 16 +++- .../tag/tag-selector/TagSelector.tsx | 60 ++++++------ 6 files changed, 190 insertions(+), 70 deletions(-) diff --git a/packages/timo-design-system/src/components/layout/dropdown/Dropdown.stories.tsx b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.stories.tsx index ad38d1f..4618c68 100644 --- a/packages/timo-design-system/src/components/layout/dropdown/Dropdown.stories.tsx +++ b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.stories.tsx @@ -25,14 +25,20 @@ const SAMPLE_ITEMS = ["옵션 1", "옵션 2", "옵션 3"]; export const Default: Story = { name: "Basic Usage", render: () => ( - - {SAMPLE_ITEMS.map((item) => ( - - - {item} - - - ))} + + + 트리거 + + + + {SAMPLE_ITEMS.map((item) => ( + + + {item} + + + ))} + ), }; diff --git a/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx index bf0be62..5527863 100644 --- a/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx +++ b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx @@ -1,6 +1,34 @@ +"use client"; + import { cn } from "@lib"; +import { + createContext, + useContext, + useEffect, + useRef, + useState, + type ReactNode, +} from "react"; + +interface DropdownContextValue { + isOpen: boolean; + toggle: () => void; + close: () => void; +} + +const DropdownContext = createContext(null); + +const useDropdownContext = (): DropdownContextValue => { + const context = useContext(DropdownContext); -import type { ReactNode } from "react"; + if (!context) { + throw new Error( + "Dropdown.Trigger, Dropdown.Panel은 Dropdown 내부에서만 사용할 수 있습니다.", + ); + } + + return context; +}; export interface DropdownProps { children: ReactNode; @@ -8,10 +36,64 @@ export interface DropdownProps { } const DropdownRoot = ({ children, className }: DropdownProps) => { + const [isOpen, setIsOpen] = useState(false); + const rootRef = useRef(null); + + useEffect(() => { + if (!isOpen) return; + + const handleOutsideClick = (event: MouseEvent) => { + if (!rootRef.current?.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + document.addEventListener("mousedown", handleOutsideClick); + + return () => document.removeEventListener("mousedown", handleOutsideClick); + }, [isOpen]); + + const toggle = () => setIsOpen((prev) => !prev); + const close = () => setIsOpen(false); + + return ( + +
+ {children} +
+
+ ); +}; + +export interface DropdownTriggerProps { + children: ReactNode; + className?: string; +} + +const DropdownTrigger = ({ children, className }: DropdownTriggerProps) => { + const { toggle } = useDropdownContext(); + + return ( + + ); +}; + +export interface DropdownPanelProps { + children: ReactNode; + className?: string; +} + +const DropdownPanel = ({ children, className }: DropdownPanelProps) => { + const { isOpen } = useDropdownContext(); + + if (!isOpen) return null; + return (
@@ -41,4 +123,8 @@ const DropdownItem = ({ children, className, onClick }: DropdownItemProps) => { ); }; -export const Dropdown = Object.assign(DropdownRoot, { Item: DropdownItem }); +export const Dropdown = Object.assign(DropdownRoot, { + Trigger: DropdownTrigger, + Panel: DropdownPanel, + Item: DropdownItem, +}); diff --git a/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.stories.tsx b/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.stories.tsx index 810f117..0c6564b 100644 --- a/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.stories.tsx +++ b/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.stories.tsx @@ -27,22 +27,28 @@ const meta = { export default meta; type Story = StoryObj; +const TRIGGER = ( + + 우선순위 + +); + export const Default: Story = { - args: {}, + args: { trigger: TRIGGER }, }; export const 매우중요: Story = { - args: { selected: "매우중요" }, + args: { trigger: TRIGGER, selected: "매우중요" }, }; export const 중요: Story = { - args: { selected: "중요" }, + args: { trigger: TRIGGER, selected: "중요" }, }; export const 보통: Story = { - args: { selected: "보통" }, + args: { trigger: TRIGGER, selected: "보통" }, }; export const 낮음: Story = { - args: { selected: "낮음" }, + args: { trigger: TRIGGER, selected: "낮음" }, }; diff --git a/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx b/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx index da07b62..c1a88c0 100644 --- a/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx +++ b/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx @@ -2,6 +2,8 @@ import { Dropdown } from "@components/layout/dropdown/Dropdown"; import { PriorityIcon } from "@components/priority/priority-icon/PriorityIcon"; import { cn } from "@lib"; +import type { ReactNode } from "react"; + export type PriorityLevel = "매우중요" | "중요" | "보통" | "낮음"; const PRIORITY_LEVELS: PriorityLevel[] = ["매우중요", "중요", "보통", "낮음"]; @@ -14,41 +16,47 @@ const PRIORITY_BG_COLOR: Record = { }; export interface PrioritySelectorProps { + trigger: ReactNode; selected?: PriorityLevel; onSelect?: (priority: PriorityLevel) => void; } export const PrioritySelector = ({ + trigger, selected, onSelect, }: PrioritySelectorProps) => { return ( - - {PRIORITY_LEVELS.map((priority) => { - const isSelected = priority === selected; - - return ( - onSelect?.(priority)} - className={cn( - "gap-2.25 py-0.5 pr-1 pl-2.75", - isSelected && PRIORITY_BG_COLOR[priority], - )} - > - - - + {trigger} + + + {PRIORITY_LEVELS.map((priority) => { + const isSelected = priority === selected; + + return ( + onSelect?.(priority)} className={cn( - "typo-headline-r-14 whitespace-nowrap transition-colors duration-200 ease-in-out", - isSelected ? "text-white" : "text-timo-black", + "gap-2.25 py-0.5 pr-1 pl-2.75", + isSelected && PRIORITY_BG_COLOR[priority], )} > - {priority} - - - ); - })} + + + + {priority} + + + ); + })} + ); }; diff --git a/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.stories.tsx b/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.stories.tsx index be95222..2852a30 100644 --- a/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.stories.tsx +++ b/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.stories.tsx @@ -27,22 +27,28 @@ const meta = { export default meta; type Story = StoryObj; +const TRIGGER = ( + + 태그 + +); + export const Default: Story = { - args: {}, + args: { trigger: TRIGGER }, }; export const 일상: Story = { - args: { selected: "일상" }, + args: { trigger: TRIGGER, selected: "일상" }, }; export const 운동: Story = { - args: { selected: "운동" }, + args: { trigger: TRIGGER, selected: "운동" }, }; export const 업무: Story = { - args: { selected: "업무" }, + args: { trigger: TRIGGER, selected: "업무" }, }; export const 기타: Story = { - args: { selected: "기타" }, + args: { trigger: TRIGGER, selected: "기타" }, }; diff --git a/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.tsx b/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.tsx index dd16051..9e155c9 100644 --- a/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.tsx +++ b/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.tsx @@ -2,50 +2,58 @@ import { Dropdown } from "@components/layout/dropdown/Dropdown"; import { PlusIcon } from "@icons"; import { cn } from "@lib"; +import type { ReactNode } from "react"; + export type Tag = "일상" | "운동" | "업무" | "기타"; const TAGS: Tag[] = ["일상", "운동", "업무", "기타"]; export interface TagSelectorProps { + trigger: ReactNode; selected?: Tag; onSelect?: (tag: Tag) => void; onAddClick?: () => void; } export const TagSelector = ({ + trigger, selected, onSelect, onAddClick, }: TagSelectorProps) => { return ( - {TAGS.map((tag) => { - const isSelected = tag === selected; - - return ( - onSelect?.(tag)} - className={cn("px-1.5 py-1", isSelected && "bg-timo-gray-500")} - > - {trigger} + + + {TAGS.map((tag) => { + const isSelected = tag === selected; + + return ( + onSelect?.(tag)} + className={cn("px-1.5 py-1", isSelected && "bg-timo-gray-500")} > - {tag} - - - ); - })} - - - - 추가 - - - + + {tag} + + + ); + })} + + + + 추가 + + + + ); }; From b9d9d6c93156a618460ce280c8524a722d7f06bf Mon Sep 17 00:00:00 2001 From: kimminna Date: Thu, 2 Jul 2026 16:24:00 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix(ui):=20TagSelector=20=EB=B2=94=EC=9A=A9?= =?UTF-8?q?=ED=99=94=20=EB=B0=8F=20Dropdown=20=EC=A0=91=EA=B7=BC=EC=84=B1?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TagSelector에 하드코딩된 태그 목록을 제거하고 tags prop으로 외부에서 주입받도록 변경했습니다 - DropdownItemProps를 ButtonHTMLAttributes로 확장해 aria-pressed, disabled 등 네이티브 버튼 속성을 전달할 수 있도록 했습니다 - PrioritySelector, TagSelector의 선택된 항목에 aria-pressed를 추가해 스크린리더가 선택 상태를 인지할 수 있도록 했습니다 --- .../src/components/layout/dropdown/Dropdown.tsx | 17 +++++------------ .../priority-selector/PrioritySelector.tsx | 1 + .../tag/tag-selector/TagSelector.stories.tsx | 12 +++++++----- .../components/tag/tag-selector/TagSelector.tsx | 13 ++++++------- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx index 5527863..1d79d4c 100644 --- a/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx +++ b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx @@ -1,5 +1,3 @@ -"use client"; - import { cn } from "@lib"; import { createContext, @@ -7,6 +5,7 @@ import { useEffect, useRef, useState, + type ButtonHTMLAttributes, type ReactNode, } from "react"; @@ -102,24 +101,18 @@ const DropdownPanel = ({ children, className }: DropdownPanelProps) => { ); }; -export interface DropdownItemProps { - children: ReactNode; - className?: string; - onClick?: () => void; -} +export type DropdownItemProps = ButtonHTMLAttributes; -const DropdownItem = ({ children, className, onClick }: DropdownItemProps) => { +const DropdownItem = ({ className, ...rest }: DropdownItemProps) => { return ( + /> ); }; diff --git a/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx b/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx index c1a88c0..f4354e9 100644 --- a/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx +++ b/packages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsx @@ -38,6 +38,7 @@ export const PrioritySelector = ({ onSelect?.(priority)} + aria-pressed={isSelected} className={cn( "gap-2.25 py-0.5 pr-1 pl-2.75", isSelected && PRIORITY_BG_COLOR[priority], diff --git a/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.stories.tsx b/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.stories.tsx index 2852a30..676ee25 100644 --- a/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.stories.tsx +++ b/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.stories.tsx @@ -33,22 +33,24 @@ const TRIGGER = ( ); +const TAGS = ["일상", "운동", "업무", "기타"]; + export const Default: Story = { - args: { trigger: TRIGGER }, + args: { trigger: TRIGGER, tags: TAGS }, }; export const 일상: Story = { - args: { trigger: TRIGGER, selected: "일상" }, + args: { trigger: TRIGGER, tags: TAGS, selected: "일상" }, }; export const 운동: Story = { - args: { trigger: TRIGGER, selected: "운동" }, + args: { trigger: TRIGGER, tags: TAGS, selected: "운동" }, }; export const 업무: Story = { - args: { trigger: TRIGGER, selected: "업무" }, + args: { trigger: TRIGGER, tags: TAGS, selected: "업무" }, }; export const 기타: Story = { - args: { trigger: TRIGGER, selected: "기타" }, + args: { trigger: TRIGGER, tags: TAGS, selected: "기타" }, }; diff --git a/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.tsx b/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.tsx index 9e155c9..3f4a606 100644 --- a/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.tsx +++ b/packages/timo-design-system/src/components/tag/tag-selector/TagSelector.tsx @@ -4,19 +4,17 @@ import { cn } from "@lib"; import type { ReactNode } from "react"; -export type Tag = "일상" | "운동" | "업무" | "기타"; - -const TAGS: Tag[] = ["일상", "운동", "업무", "기타"]; - export interface TagSelectorProps { trigger: ReactNode; - selected?: Tag; - onSelect?: (tag: Tag) => void; + tags: string[]; + selected?: string; + onSelect?: (tag: string) => void; onAddClick?: () => void; } export const TagSelector = ({ trigger, + tags, selected, onSelect, onAddClick, @@ -26,13 +24,14 @@ export const TagSelector = ({ {trigger} - {TAGS.map((tag) => { + {tags.map((tag) => { const isSelected = tag === selected; return ( onSelect?.(tag)} + aria-pressed={isSelected} className={cn("px-1.5 py-1", isSelected && "bg-timo-gray-500")} > Date: Thu, 2 Jul 2026 16:27:47 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix(ui):=20Dropdown=EC=97=90=20Escape=20?= =?UTF-8?q?=ED=82=A4=20=EB=8B=AB=ED=9E=98=20=EB=8F=99=EC=9E=91=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 바깥 클릭뿐 아니라 Escape 키 입력 시에도 Dropdown이 닫히도록 keydown 리스너를 추가했습니다 - 키보드 사용자도 패널을 닫을 수 있도록 접근성을 개선했습니다 --- .../src/components/layout/dropdown/Dropdown.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx index 1d79d4c..b8e2db1 100644 --- a/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx +++ b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx @@ -47,9 +47,19 @@ const DropdownRoot = ({ children, className }: DropdownProps) => { } }; + const handleEscapeKeydown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + setIsOpen(false); + } + }; + document.addEventListener("mousedown", handleOutsideClick); + document.addEventListener("keydown", handleEscapeKeydown); - return () => document.removeEventListener("mousedown", handleOutsideClick); + return () => { + document.removeEventListener("mousedown", handleOutsideClick); + document.removeEventListener("keydown", handleEscapeKeydown); + }; }, [isOpen]); const toggle = () => setIsOpen((prev) => !prev);