From 3167ee97311a22ae030a75315cdde6343603fb93 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Sun, 27 Sep 2026 03:18:13 +0900 Subject: [PATCH 01/89] =?UTF-8?q?feat(ui):=20=ED=83=80=EC=9E=84=20?= =?UTF-8?q?=EC=85=80=EB=A0=89=ED=84=B0=20AI=20=EC=B6=94=EC=B2=9C=20?= =?UTF-8?q?=EB=B0=B0=EC=A7=80=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20(#297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AI 추천 배지에 텍스트 라벨을 추가했습니다 - 아이콘·라벨 줄과 시간 표시 줄을 세로로 분리했습니다 - 시간 텍스트 폰트를 Medium 굵기로 변경했습니다 - AI 배지와 시간 목록 사이에 구분선을 추가했습니다 - AI 아이콘 크기를 14px로 고정했습니다 Co-Authored-By: Claude Sonnet 5 --- .../time/time-selector/TimeSelector.tsx | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/timo-design-system/src/components/time/time-selector/TimeSelector.tsx b/packages/timo-design-system/src/components/time/time-selector/TimeSelector.tsx index b204c3e2..4e80a73a 100644 --- a/packages/timo-design-system/src/components/time/time-selector/TimeSelector.tsx +++ b/packages/timo-design-system/src/components/time/time-selector/TimeSelector.tsx @@ -111,10 +111,10 @@ export const TimeSelector = ({ {trigger} - +
@@ -122,13 +122,24 @@ export const TimeSelector = ({ type="button" onClick={() => selectDraft("ai")} aria-pressed={isAiSelected} - aria-label="AI 추천 시간 선택" - className="shrink-0" + className="flex w-full items-center gap-1" > - {isAiSelected ? : } + {isAiSelected ? ( + + ) : ( + + )} + + AI 추천 + -
+
@@ -157,14 +168,14 @@ export const TimeSelector = ({ maxLength={TIME_SEGMENT_DIGIT_LIMIT} aria-label="예상 시간(분) 입력" className={cn( - "typo-headline-r-14 focus-visible:ring-timo-blue-300 w-4.5 shrink-0 rounded-xs bg-transparent text-right outline-none focus-visible:ring-2", + "typo-headline-m-14 focus-visible:ring-timo-blue-300 w-4.5 shrink-0 rounded-xs bg-transparent outline-none focus-visible:ring-2", isAiSelected ? "text-white" : "text-timo-black", )} />
-
+
{times.map(({ minute, value, unit }) => { const isSelected = minute === draftSelected; From 2f6a7c25d2b9fdc202769236ab4a1da6770b32f6 Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:01:32 +0900 Subject: [PATCH 02/89] =?UTF-8?q?fix(web):=20=ED=83=88=ED=87=B4=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=ED=8C=9D=EC=97=85=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EB=AC=B8=EA=B5=AC=20=EB=B0=8F=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#300)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 취소 버튼을 fill, 탈퇴하기 버튼을 border로 변경하고 취소 문구를 수정했습니다. --- .../_containers/withdrawal/SettingsWithdrawModalContainer.tsx | 4 ++-- apps/timo-web/messages/en.json | 2 +- apps/timo-web/messages/ko.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/settings/_containers/withdrawal/SettingsWithdrawModalContainer.tsx b/apps/timo-web/app/[locale]/(main)/settings/_containers/withdrawal/SettingsWithdrawModalContainer.tsx index 9becd317..4927433e 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_containers/withdrawal/SettingsWithdrawModalContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_containers/withdrawal/SettingsWithdrawModalContainer.tsx @@ -44,11 +44,11 @@ export const SettingsWithdrawModalContainer = ({ {t("withdrawConfirmDescription")}

- + {t("withdrawConfirmCancel")} { onWithdraw(); diff --git a/apps/timo-web/messages/en.json b/apps/timo-web/messages/en.json index 180de03d..5e6a0303 100644 --- a/apps/timo-web/messages/en.json +++ b/apps/timo-web/messages/en.json @@ -125,7 +125,7 @@ "withdraw": "Withdraw", "withdrawConfirmTitle": "Are you sure you want to withdraw?", "withdrawConfirmDescription": "After withdrawal, all data will be deleted and cannot be undone.", - "withdrawConfirmCancel": "Go back", + "withdrawConfirmCancel": "Cancel", "withdrawConfirmConfirm": "Withdraw", "withdrawError": "An error occurred during withdrawal. Please try again later." } diff --git a/apps/timo-web/messages/ko.json b/apps/timo-web/messages/ko.json index 7e48164f..0a7fb6ae 100644 --- a/apps/timo-web/messages/ko.json +++ b/apps/timo-web/messages/ko.json @@ -125,7 +125,7 @@ "withdraw": "탈퇴하기", "withdrawConfirmTitle": "정말 탈퇴하시겠어요?", "withdrawConfirmDescription": "탈퇴 후에는 모든 데이터가 삭제되며 되돌릴 수 없어요.", - "withdrawConfirmCancel": "돌아가기", + "withdrawConfirmCancel": "취소", "withdrawConfirmConfirm": "탈퇴하기", "withdrawError": "탈퇴 처리 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요." } From f1c5095be06b0a04d04f49d27ad1f3e1a58149c5 Mon Sep 17 00:00:00 2001 From: yumin-kim4757 Date: Wed, 30 Sep 2026 02:26:27 +0900 Subject: [PATCH 03/89] =?UTF-8?q?fix(ui):=20=EB=B0=98=EB=B3=B5=20=EC=9C=A0?= =?UTF-8?q?=ED=98=95=20=EB=B3=80=EA=B2=BD=20=EC=8B=9C=20=EC=9A=94=EC=9D=BC?= =?UTF-8?q?=EA=B3=BC=20=EB=82=A0=EC=A7=9C=20=EC=B4=88=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repeat/repeat-selector/RepeatSelector.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/timo-design-system/src/components/repeat/repeat-selector/RepeatSelector.tsx b/packages/timo-design-system/src/components/repeat/repeat-selector/RepeatSelector.tsx index 216f4649..f0f7cf09 100644 --- a/packages/timo-design-system/src/components/repeat/repeat-selector/RepeatSelector.tsx +++ b/packages/timo-design-system/src/components/repeat/repeat-selector/RepeatSelector.tsx @@ -187,6 +187,17 @@ export const RepeatSelector = ({ )?.label; const handleSelectFrequency = (value: RepeatFrequency) => { + if (draftFrequencyRef.current !== value) { + if (draftFrequencyRef.current === "WEEKLY" || value === "WEEKLY") { + draftWeekdayIdsRef.current = []; + setDraftWeekdayIds([]); + } + if (draftFrequencyRef.current === "MONTHLY" || value === "MONTHLY") { + draftRepeatDayRef.current = ""; + setDraftRepeatDay(""); + } + } + hasSelectedFrequencyRef.current = true; draftFrequencyRef.current = value; setDraftFrequency(value); @@ -226,6 +237,7 @@ export const RepeatSelector = ({ onFrequencyChange?.(draftFrequencyRef.current); } if ( + draftFrequencyRef.current === "WEEKLY" && !isSameWeekdaySelection( draftWeekdayIdsRef.current, weekly.selectedWeekdayIds, @@ -233,7 +245,10 @@ export const RepeatSelector = ({ ) { weekly.onWeekdaysChange?.(draftWeekdayIdsRef.current); } - if (draftRepeatDayRef.current !== monthly.repeatDay) { + if ( + draftFrequencyRef.current === "MONTHLY" && + draftRepeatDayRef.current !== monthly.repeatDay + ) { monthly.onRepeatDayChange?.(draftRepeatDayRef.current); } }; From 787e832216d1a06a00bfe22804c421c6c10cbd7f Mon Sep 17 00:00:00 2001 From: yumin-kim4757 Date: Wed, 30 Sep 2026 02:26:46 +0900 Subject: [PATCH 04/89] =?UTF-8?q?fix(web):=20=EB=B0=98=EB=B3=B5=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20=EB=B3=80=EA=B2=BD=EC=9D=84=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EB=AA=A8=EB=8B=AC=20=EB=8B=AB=EC=9D=84=20=EB=95=8C?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=20(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../detail/DetailTodoModalContent.tsx | 1 + .../todo-modal/create/use-repeat-field.ts | 4 ++ .../todo-modal/detail/use-detail-todo-form.ts | 23 +++++++++ .../detail/use-detail-todo-patch-handlers.ts | 50 +++++++++++-------- 4 files changed, 56 insertions(+), 22 deletions(-) diff --git a/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx b/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx index 59db3fd7..f5c122e7 100644 --- a/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx +++ b/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx @@ -113,6 +113,7 @@ export const DetailTodoModalContent = ({ const handleClose = () => { submitPendingUpdates(); + patchHandlers.submitRepeatChange(); onClose(); }; diff --git a/apps/timo-web/hooks/todo-modal/create/use-repeat-field.ts b/apps/timo-web/hooks/todo-modal/create/use-repeat-field.ts index 613d07c7..3ec3da1a 100644 --- a/apps/timo-web/hooks/todo-modal/create/use-repeat-field.ts +++ b/apps/timo-web/hooks/todo-modal/create/use-repeat-field.ts @@ -47,6 +47,10 @@ export const useRepeatField = ({ control }: UseRepeatFieldParams) => { repeatType === "NONE" ? "DAILY" : repeatType; const handleFrequencyChange = (frequency: RepeatFrequency) => { + if (repeatTypeField.field.value !== frequency) { + repeatWeekdaysField.field.onChange([]); + repeatDayField.field.onChange(null); + } repeatTypeField.field.onChange(frequency); }; diff --git a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts index d11fcf3e..5211b27d 100644 --- a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts +++ b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts @@ -164,10 +164,32 @@ export const useDetailTodoForm = ({ }; const changeRepeatFrequency = (frequency: RepeatFrequency) => { + if ( + !isRepeatActiveField.value || + repeatFrequencyField.value !== frequency + ) { + selectedWeekdayIdsField.onChange([]); + repeatDayField.onChange(""); + } isRepeatActiveField.onChange(true); repeatFrequencyField.onChange(frequency); }; + const initialWeekdayIds = todo.repeat.weekdays ?? []; + const currentWeekdayIds = selectedWeekdayIdsField.value ?? []; + const sortedInitialWeekdayIds = [...initialWeekdayIds].sort(); + const sortedCurrentWeekdayIds = [...currentWeekdayIds].sort(); + const hasSameWeekdays = + sortedInitialWeekdayIds.length === sortedCurrentWeekdayIds.length && + sortedInitialWeekdayIds.every( + (weekdayId, index) => weekdayId === sortedCurrentWeekdayIds[index], + ); + const hasRepeatChanges = + isRepeatActiveField.value !== (todo.repeat.type !== "NONE") || + repeatFrequencyField.value !== repeatType || + !hasSameWeekdays || + repeatDayField.value !== (todo.repeat.dayOfMonth?.toString() ?? ""); + const getTagIdByLabel = (label: string) => { const option = tagField.tagOptions.find((item) => item.label === label); if (!option) return null; @@ -212,6 +234,7 @@ export const useDetailTodoForm = ({ removeIcon, selectTime, changeRepeatFrequency, + hasRepeatChanges, handleSubmit, dirtyFields: formState.dirtyFields, }; diff --git a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts index 74c520d7..a379d676 100644 --- a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts +++ b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts @@ -94,37 +94,42 @@ export const useDetailTodoPatchHandlers = ({ }; const handleRepeatFrequencyChange = (repeatFrequency: RepeatFrequency) => { - updateTodo( - { repeatType: repeatFrequency }, - { onSuccess: () => form.changeRepeatFrequency(repeatFrequency) }, - ); + form.changeRepeatFrequency(repeatFrequency); }; const handleWeekdaysChange = (weekdayIds: string[]) => { - updateTodo( - { - repeatType: "WEEKLY", - repeatWeekdays: weekdayIds.filter(isTodoUpdateRepeatWeekday), - }, - { - onSuccess: () => form.setSelectedWeekdayIds(weekdayIds), - }, - ); + form.setSelectedWeekdayIds(weekdayIds); }; const handleRepeatDayChange = (repeatDay: string) => { - const repeatDayOfMonth = Number(repeatDay); + form.setRepeatDay(repeatDay); + }; - if ( - Number.isInteger(repeatDayOfMonth) && - repeatDayOfMonth >= 1 && - repeatDayOfMonth <= 31 - ) { - updateTodo( - { repeatType: "MONTHLY", repeatDayOfMonth }, - { onSuccess: () => form.setRepeatDay(repeatDay) }, + const submitRepeatChange = () => { + if (!form.hasRepeatChanges) return; + + const updateData: TodoUpdateRequest = { + repeatType: form.isRepeatActive ? form.repeatFrequency : "NONE", + }; + + if (form.isRepeatActive && form.repeatFrequency === "WEEKLY") { + updateData.repeatWeekdays = form.selectedWeekdayIds.filter( + isTodoUpdateRepeatWeekday, ); } + + if (form.isRepeatActive && form.repeatFrequency === "MONTHLY") { + const repeatDayOfMonth = Number(form.repeatDay); + if ( + Number.isInteger(repeatDayOfMonth) && + repeatDayOfMonth >= 1 && + repeatDayOfMonth <= 31 + ) { + updateData.repeatDayOfMonth = repeatDayOfMonth; + } + } + + updateTodo(updateData); }; const handleSubtaskCompletedChange = (id: number, completed: boolean) => { @@ -147,6 +152,7 @@ export const useDetailTodoPatchHandlers = ({ handleRepeatFrequencyChange, handleWeekdaysChange, handleRepeatDayChange, + submitRepeatChange, handleSubtaskCompletedChange, }; }; From b3b921d22265959d03661be039b0798aadeaf6c9 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Wed, 30 Sep 2026 22:07:27 +0900 Subject: [PATCH 05/89] =?UTF-8?q?fix(ui):=20=ED=83=80=EC=9E=84=20=EC=85=80?= =?UTF-8?q?=EB=A0=89=ED=84=B0=20=ED=83=80=EC=9D=B4=ED=8F=AC=EA=B7=B8?= =?UTF-8?q?=EB=9E=98=ED=94=BC=EC=99=80=20=EC=8B=9C=EA=B0=84=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=EC=9D=84=20=ED=94=BC=EA=B7=B8=EB=A7=88=20=EC=8A=A4?= =?UTF-8?q?=ED=8E=99=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AI 추천 라벨 폰트를 8px 하드코딩에서 caption 토큰(typo-caption-r-10)으로 변경했습니다 - 시간 목록 항목을 value/unit 분리 표기에서 "00 : 15" 형태의 단일 라벨로 변경했습니다 - TimeOption 타입을 {minute, label} 구조로 변경했습니다 Co-Authored-By: Claude Sonnet 5 --- .../time-selector/TimeSelector.stories.tsx | 12 ++++----- .../time/time-selector/TimeSelector.tsx | 27 +++++++------------ 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/packages/timo-design-system/src/components/time/time-selector/TimeSelector.stories.tsx b/packages/timo-design-system/src/components/time/time-selector/TimeSelector.stories.tsx index e29b8414..e3e94b3c 100644 --- a/packages/timo-design-system/src/components/time/time-selector/TimeSelector.stories.tsx +++ b/packages/timo-design-system/src/components/time/time-selector/TimeSelector.stories.tsx @@ -19,7 +19,7 @@ const meta = { argTypes: { selected: { control: "select", - options: ["ai", 15, 30, 45, 60, 120], + options: ["ai", 15, 30, 45, 60, 90], }, }, } satisfies Meta; @@ -34,11 +34,11 @@ const TRIGGER = ( ); const TIMES = [ - { minute: 15, value: "15", unit: "min" }, - { minute: 30, value: "30", unit: "min" }, - { minute: 45, value: "45", unit: "min" }, - { minute: 60, value: "1", unit: "h" }, - { minute: 120, value: "2", unit: "h" }, + { minute: 15, label: "00 : 15" }, + { minute: 30, label: "00 : 30" }, + { minute: 45, label: "00 : 45" }, + { minute: 60, label: "01 : 00" }, + { minute: 90, label: "01 : 30" }, ]; export const Default: Story = { diff --git a/packages/timo-design-system/src/components/time/time-selector/TimeSelector.tsx b/packages/timo-design-system/src/components/time/time-selector/TimeSelector.tsx index 4e80a73a..a2dc996f 100644 --- a/packages/timo-design-system/src/components/time/time-selector/TimeSelector.tsx +++ b/packages/timo-design-system/src/components/time/time-selector/TimeSelector.tsx @@ -10,8 +10,7 @@ export type TimeSelection = number | "ai"; export interface TimeOption { minute: number; - value: string; - unit: string; + label: string; } export interface TimeSelectorProps { @@ -122,16 +121,16 @@ export const TimeSelector = ({ type="button" onClick={() => selectDraft("ai")} aria-pressed={isAiSelected} - className="flex w-full items-center gap-1" + className="flex w-full items-center" > {isAiSelected ? ( - + ) : ( - + )} @@ -175,8 +174,8 @@ export const TimeSelector = ({
-
- {times.map(({ minute, value, unit }) => { +
+ {times.map(({ minute, label }) => { const isSelected = minute === draftSelected; return ( @@ -186,7 +185,7 @@ export const TimeSelector = ({ closeOnSelect={false} aria-pressed={isSelected} className={cn( - "justify-between px-[3.5px] py-[2.5px]", + "px-[3.5px] py-0.5", isSelected && "bg-timo-blue-300", )} > @@ -196,15 +195,7 @@ export const TimeSelector = ({ isSelected && "text-white", )} > - {value} - - - {unit} + {label} ); From 820501964dca05709dcb12e631e0f302d9e7252e Mon Sep 17 00:00:00 2001 From: jjangminii Date: Wed, 30 Sep 2026 22:07:36 +0900 Subject: [PATCH 06/89] =?UTF-8?q?fix(web):=20=ED=83=80=EC=9E=84=20?= =?UTF-8?q?=EC=85=80=EB=A0=89=ED=84=B0=20=EC=98=B5=EC=85=98=20=EB=9D=BC?= =?UTF-8?q?=EB=B2=A8=EC=9D=84=20=EC=8B=9C:=EB=B6=84=20=ED=98=95=EC=8B=9D?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TimeOption 타입 변경에 맞춰 생성/상세 모달의 시간 옵션 라벨을 "00 : 15" 형식으로 생성하도록 수정했습니다 Co-Authored-By: Claude Sonnet 5 --- .../hooks/todo-modal/create/use-time-field.ts | 17 ++++++++------ .../todo-modal/detail/use-detail-todo-form.ts | 22 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/apps/timo-web/hooks/todo-modal/create/use-time-field.ts b/apps/timo-web/hooks/todo-modal/create/use-time-field.ts index 4572634a..fd19f9b8 100644 --- a/apps/timo-web/hooks/todo-modal/create/use-time-field.ts +++ b/apps/timo-web/hooks/todo-modal/create/use-time-field.ts @@ -10,16 +10,19 @@ import { useRecommendDuration } from "@/generated/endpoints/ai/ai"; import { recommendDurationResponseSchema } from "@/schemas/todo/todo-schema"; import { convertApiDurationToSeconds, + convertDurationToTimeText, convertSecondsToApiDuration, } from "@/utils/todo/todo-time"; -const TIME_OPTIONS = [ - { minute: 15, value: "15", unit: "min" }, - { minute: 30, value: "30", unit: "min" }, - { minute: 45, value: "45", unit: "min" }, - { minute: 60, value: "60", unit: "min" }, - { minute: 90, value: "90", unit: "min" }, -]; +const TIME_OPTION_MINUTES = [15, 30, 45, 60, 90]; + +const TIME_OPTIONS = TIME_OPTION_MINUTES.map((minute) => ({ + minute, + label: convertDurationToTimeText(minute * SECONDS_PER_MINUTE).replace( + ":", + " : ", + ), +})); export interface UseTimeFieldParams { control: Control; diff --git a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts index d11fcf3e..00265f7c 100644 --- a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts +++ b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts @@ -19,7 +19,10 @@ import { TITLE_MAX_WEIGHTED_LENGTH, truncateToWeightedLength, } from "@/utils/todo/text-length"; -import { convertSecondsToApiDuration } from "@/utils/todo/todo-time"; +import { + convertDurationToTimeText, + convertSecondsToApiDuration, +} from "@/utils/todo/todo-time"; interface DetailTodoFormValues { date: Date; @@ -51,13 +54,16 @@ const isRepeatFrequency = ( ): repeatType is RepeatFrequency => repeatType === "DAILY" || repeatType === "WEEKLY" || repeatType === "MONTHLY"; -export const DETAIL_TODO_TIME_OPTIONS: TimeOption[] = [ - { minute: 15, value: "15", unit: "min" }, - { minute: 30, value: "30", unit: "min" }, - { minute: 45, value: "45", unit: "min" }, - { minute: 60, value: "60", unit: "min" }, - { minute: 90, value: "90", unit: "min" }, -]; +const DETAIL_TODO_TIME_OPTION_MINUTES = [15, 30, 45, 60, 90]; + +export const DETAIL_TODO_TIME_OPTIONS: TimeOption[] = + DETAIL_TODO_TIME_OPTION_MINUTES.map((minute) => ({ + minute, + label: convertDurationToTimeText(minute * SECONDS_PER_MINUTE).replace( + ":", + " : ", + ), + })); export const DETAIL_TODO_WEEKDAY_IDS = [ "MON", From 0ba4fb95195ff795ffcb13b0a555eb951c291e2a Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Thu, 1 Oct 2026 11:50:38 +0900 Subject: [PATCH 07/89] =?UTF-8?q?fix(web):=20=ED=83=88=ED=87=B4=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=ED=8C=9D=EC=97=85=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EB=B0=8F=20=EC=B7=A8=EC=86=8C=20=EB=AC=B8?= =?UTF-8?q?=EA=B5=AC=20=EC=88=98=EC=A0=95=20(#300)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 탈퇴하기 버튼을 왼쪽, 취소하기 버튼을 오른쪽으로 옮기고 취소 문구를 취소하기로 수정했습니다. --- .../withdrawal/SettingsWithdrawModalContainer.tsx | 6 +++--- apps/timo-web/messages/ko.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/settings/_containers/withdrawal/SettingsWithdrawModalContainer.tsx b/apps/timo-web/app/[locale]/(main)/settings/_containers/withdrawal/SettingsWithdrawModalContainer.tsx index 4927433e..8eeb1e0e 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_containers/withdrawal/SettingsWithdrawModalContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_containers/withdrawal/SettingsWithdrawModalContainer.tsx @@ -44,9 +44,6 @@ export const SettingsWithdrawModalContainer = ({ {t("withdrawConfirmDescription")}

- - {t("withdrawConfirmCancel")} - {t("withdrawConfirmConfirm")} + + {t("withdrawConfirmCancel")} +
); diff --git a/apps/timo-web/messages/ko.json b/apps/timo-web/messages/ko.json index 0a7fb6ae..c1697e77 100644 --- a/apps/timo-web/messages/ko.json +++ b/apps/timo-web/messages/ko.json @@ -125,7 +125,7 @@ "withdraw": "탈퇴하기", "withdrawConfirmTitle": "정말 탈퇴하시겠어요?", "withdrawConfirmDescription": "탈퇴 후에는 모든 데이터가 삭제되며 되돌릴 수 없어요.", - "withdrawConfirmCancel": "취소", + "withdrawConfirmCancel": "취소하기", "withdrawConfirmConfirm": "탈퇴하기", "withdrawError": "탈퇴 처리 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요." } From d1eb0bdbd580c0468d19e14f861c902e54b8488c Mon Sep 17 00:00:00 2001 From: jjangminii Date: Fri, 2 Oct 2026 00:55:00 +0900 Subject: [PATCH 08/89] =?UTF-8?q?feat(ui):=20SidebarButton=EC=97=90=20aria?= =?UTF-8?q?-expanded=20=EC=86=8D=EC=84=B1=20=EC=B6=94=EA=B0=80=20(#303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - isOpen 값을 aria-expanded로도 노출해 스크린리더가 사이드바 열림/닫힘 상태를 인식할 수 있게 했습니다 Co-Authored-By: Claude Sonnet 5 --- .../src/components/button/sidebar-button/SidebarButton.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/timo-design-system/src/components/button/sidebar-button/SidebarButton.tsx b/packages/timo-design-system/src/components/button/sidebar-button/SidebarButton.tsx index ceabcc8f..1e24f6e1 100644 --- a/packages/timo-design-system/src/components/button/sidebar-button/SidebarButton.tsx +++ b/packages/timo-design-system/src/components/button/sidebar-button/SidebarButton.tsx @@ -16,6 +16,7 @@ export const SidebarButton = ({ + )), + )} + {VARIANTS.map((variant) => ( + + ))} +
+ ))} +
+ ))} +
+ ), +}; + +export const WithIcon: Story = { + args: { + icon: , + children: "삭제하기", + }, +}; + +export const FullWidth: Story = { + parameters: { controls: { disable: true } }, + render: () => ( +
+ +
+ + +
+
+ ), +}; diff --git a/packages/timo-design-system/src/components/button/button/Button.tsx b/packages/timo-design-system/src/components/button/button/Button.tsx new file mode 100644 index 00000000..a917faba --- /dev/null +++ b/packages/timo-design-system/src/components/button/button/Button.tsx @@ -0,0 +1,75 @@ +import { cn } from "../../../lib"; + +import type { ButtonHTMLAttributes, ReactNode } from "react"; + +export type ButtonVariantTypes = "primary" | "secondary"; +export type ButtonAppearanceTypes = "fill" | "outline"; +export type ButtonSizeTypes = "m" | "lg"; + +const BUTTON_VARIANTS: Record< + ButtonVariantTypes, + Record +> = { + primary: { + fill: "bg-timo-blue-300 text-white disabled:bg-timo-blue-65", + outline: + "border-timo-blue-300 text-timo-blue-300 border bg-white disabled:border-timo-gray-500 disabled:text-timo-gray-700", + }, + secondary: { + fill: "bg-timo-gray-300 text-timo-gray-900 disabled:text-timo-gray-700", + outline: + "border-timo-gray-500 text-timo-gray-900 border bg-white disabled:text-timo-gray-700", + }, +}; + +const BUTTON_HEIGHT: Record = { + m: "h-8.5", + lg: "h-11.5", +}; + +const BUTTON_PADDING: Record = + { + m: { text: "px-4", icon: "px-2" }, + lg: { text: "px-12", icon: "px-12" }, + }; + +export interface ButtonProps extends ButtonHTMLAttributes { + variant: ButtonVariantTypes; + appearance: ButtonAppearanceTypes; + size: ButtonSizeTypes; + icon?: ReactNode; +} + +export const Button = ({ + variant, + appearance, + size, + icon, + type = "button", + className, + children, + ...rest +}: ButtonProps) => { + return ( + + ); +}; diff --git a/packages/timo-design-system/src/components/index.ts b/packages/timo-design-system/src/components/index.ts index 3de2f8a8..4e7cd294 100644 --- a/packages/timo-design-system/src/components/index.ts +++ b/packages/timo-design-system/src/components/index.ts @@ -9,6 +9,7 @@ export { TagNameInput } from "./tag/tag-name-input/TagNameInput"; export { PriorityIcon } from "./priority/priority-icon/PriorityIcon"; export { PrioritySelector } from "./priority/priority-selector/PrioritySelector"; export type { PriorityLevel } from "./priority/priority-selector/PrioritySelector"; +export { Button } from "./button/button/Button"; export { CreateButton } from "./button/create-button/CreateButton"; export { DeleteButton } from "./button/delete-button/DeleteButton"; export { AddTaskButton } from "./button/add-task-button/AddTaskButton"; From c2e014b866747a6be15fbc8311181a89a01097e2 Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Fri, 2 Oct 2026 23:58:16 +0900 Subject: [PATCH 14/89] =?UTF-8?q?refactor(ui):=20Button=20=EB=86=92?= =?UTF-8?q?=EC=9D=B4=C2=B7=ED=8C=A8=EB=94=A9=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=EC=9D=84=20BUTTON=5FSIZE=EB=A1=9C=20=ED=86=B5=ED=95=A9=20(#305?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - size별 높이와 패딩을 한 객체로 묶어 피그마 size 정의와 1:1로 대응하도록 했습니다 --- .../src/components/button/button/Button.tsx | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/timo-design-system/src/components/button/button/Button.tsx b/packages/timo-design-system/src/components/button/button/Button.tsx index a917faba..2778e703 100644 --- a/packages/timo-design-system/src/components/button/button/Button.tsx +++ b/packages/timo-design-system/src/components/button/button/Button.tsx @@ -22,17 +22,14 @@ const BUTTON_VARIANTS: Record< }, }; -const BUTTON_HEIGHT: Record = { - m: "h-8.5", - lg: "h-11.5", +const BUTTON_SIZE: Record< + ButtonSizeTypes, + { height: string; padding: { text: string; icon: string } } +> = { + m: { height: "h-8.5", padding: { text: "px-4", icon: "px-2" } }, + lg: { height: "h-11.5", padding: { text: "px-12", icon: "px-12" } }, }; -const BUTTON_PADDING: Record = - { - m: { text: "px-4", icon: "px-2" }, - lg: { text: "px-12", icon: "px-12" }, - }; - export interface ButtonProps extends ButtonHTMLAttributes { variant: ButtonVariantTypes; appearance: ButtonAppearanceTypes; @@ -58,8 +55,8 @@ export const Button = ({ "focus-visible:ring-timo-blue-300 focus-visible:ring-2 focus-visible:ring-offset-2", "disabled:cursor-not-allowed", BUTTON_VARIANTS[variant][appearance], - BUTTON_HEIGHT[size], - BUTTON_PADDING[size][icon ? "icon" : "text"], + BUTTON_SIZE[size].height, + BUTTON_SIZE[size].padding[icon ? "icon" : "text"], className, )} {...rest} From 65f848db5b9f6e9a23115bbcfd7e1755f6c5493a Mon Sep 17 00:00:00 2001 From: jjangminii Date: Sat, 3 Oct 2026 15:36:36 +0900 Subject: [PATCH 15/89] =?UTF-8?q?feat(ui):=20SidebarButton=EC=97=90=20labe?= =?UTF-8?q?l=20prop=20=EC=B6=94=EA=B0=80=20(#303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기본값 "사이드바"를 유지하면서, 다른 패널(시간 패널 등)의 토글 버튼으로 재사용할 때 aria-label을 올바르게 바꿀 수 있도록 했습니다 Co-Authored-By: Claude Sonnet 5 --- .../src/components/button/sidebar-button/SidebarButton.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/timo-design-system/src/components/button/sidebar-button/SidebarButton.tsx b/packages/timo-design-system/src/components/button/sidebar-button/SidebarButton.tsx index 1e24f6e1..29d4979d 100644 --- a/packages/timo-design-system/src/components/button/sidebar-button/SidebarButton.tsx +++ b/packages/timo-design-system/src/components/button/sidebar-button/SidebarButton.tsx @@ -5,19 +5,21 @@ export interface SidebarButtonProps { isOpen?: boolean; onClick?: () => void; className?: string; + label?: string; } export const SidebarButton = ({ isOpen = true, onClick, className, + label = "사이드바", }: SidebarButtonProps) => { return ( @@ -77,7 +85,7 @@ export const AllVariants: Story = { export const WithIcon: Story = { args: { - icon: , + icon: , children: "삭제하기", }, }; diff --git a/packages/timo-design-system/src/components/button/button/Button.tsx b/packages/timo-design-system/src/components/button/button/Button.tsx index 2778e703..d5c64ca8 100644 --- a/packages/timo-design-system/src/components/button/button/Button.tsx +++ b/packages/timo-design-system/src/components/button/button/Button.tsx @@ -2,9 +2,9 @@ import { cn } from "../../../lib"; import type { ButtonHTMLAttributes, ReactNode } from "react"; -export type ButtonVariantTypes = "primary" | "secondary"; -export type ButtonAppearanceTypes = "fill" | "outline"; -export type ButtonSizeTypes = "m" | "lg"; +type ButtonVariantTypes = "primary" | "secondary"; +type ButtonAppearanceTypes = "fill" | "outline"; +type ButtonSizeTypes = "m" | "lg"; const BUTTON_VARIANTS: Record< ButtonVariantTypes, @@ -62,7 +62,10 @@ export const Button = ({ {...rest} > {icon && ( - + )} From ae47464315aa69de9f1212671af6ab340ceca39a Mon Sep 17 00:00:00 2001 From: yumin-kim4757 Date: Sun, 4 Oct 2026 11:01:52 +0900 Subject: [PATCH 26/89] =?UTF-8?q?fix(web):=20=EC=9B=94=EA=B0=84=20?= =?UTF-8?q?=EB=82=A0=EC=A7=9C=20=EB=AF=B8=EC=84=A0=ED=83=9D=20=EC=8B=9C=20?= =?UTF-8?q?=EB=B0=98=EB=B3=B5=20=EC=97=86=EC=9D=8C=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts index ab199803..b6bcde8f 100644 --- a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts +++ b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts @@ -131,6 +131,8 @@ export const useDetailTodoPatchHandlers = ({ repeatDayOfMonth <= 31 ) { updateData.repeatDayOfMonth = repeatDayOfMonth; + } else if (form.repeatDay.trim() === "") { + updateData.repeatType = "NONE"; } } From 9bdca35bc2b464b8820178e90ac47cf08a0ce2be Mon Sep 17 00:00:00 2001 From: yumin-kim4757 Date: Sun, 4 Oct 2026 11:34:17 +0900 Subject: [PATCH 27/89] =?UTF-8?q?refactor(web):=20=EB=B0=98=EB=B3=B5=20?= =?UTF-8?q?=EC=9C=A0=ED=98=95=20=EB=B3=80=EA=B2=BD=20=EC=8B=9C=20=EC=84=B8?= =?UTF-8?q?=EB=B6=80=20=EA=B0=92=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EA=B7=9C?= =?UTF-8?q?=EC=B9=99=20=EA=B3=B5=ED=86=B5=ED=99=94=20(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo-modal/create/use-repeat-field.ts | 15 ++++++++++--- .../todo-modal/detail/use-detail-todo-form.ts | 17 +++++++++----- .../utils/todo/repeat-detail-reset.ts | 22 +++++++++++++++++++ 3 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 apps/timo-web/utils/todo/repeat-detail-reset.ts diff --git a/apps/timo-web/hooks/todo-modal/create/use-repeat-field.ts b/apps/timo-web/hooks/todo-modal/create/use-repeat-field.ts index 3ec3da1a..33ad49b1 100644 --- a/apps/timo-web/hooks/todo-modal/create/use-repeat-field.ts +++ b/apps/timo-web/hooks/todo-modal/create/use-repeat-field.ts @@ -8,6 +8,8 @@ import type { import type { RepeatFrequency } from "@repo/timo-design-system/ui"; import type { Control } from "react-hook-form"; +import { getRepeatDetailsReset } from "@/utils/todo/repeat-detail-reset"; + const WEEKDAY_CODES: TodoRepeatWeekday[] = [ "MON", "TUE", @@ -47,9 +49,16 @@ export const useRepeatField = ({ control }: UseRepeatFieldParams) => { repeatType === "NONE" ? "DAILY" : repeatType; const handleFrequencyChange = (frequency: RepeatFrequency) => { - if (repeatTypeField.field.value !== frequency) { - repeatWeekdaysField.field.onChange([]); - repeatDayField.field.onChange(null); + const reset = getRepeatDetailsReset({ + currentFrequency: repeatTypeField.field.value, + nextFrequency: frequency, + isRepeatActive, + emptyDay: null, + }); + + if (reset) { + repeatWeekdaysField.field.onChange(reset.weekdays); + repeatDayField.field.onChange(reset.day); } repeatTypeField.field.onChange(frequency); }; diff --git a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts index 5211b27d..df0e1422 100644 --- a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts +++ b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts @@ -15,6 +15,7 @@ import { SECONDS_PER_MINUTE } from "@/constants/time"; import { useTagField } from "@/hooks/todo-modal/common/use-tag-field"; import { useDetailSubtaskField } from "@/hooks/todo-modal/detail/use-detail-subtask-field"; import { parseDateKey } from "@/utils/date/date"; +import { getRepeatDetailsReset } from "@/utils/todo/repeat-detail-reset"; import { TITLE_MAX_WEIGHTED_LENGTH, truncateToWeightedLength, @@ -164,12 +165,16 @@ export const useDetailTodoForm = ({ }; const changeRepeatFrequency = (frequency: RepeatFrequency) => { - if ( - !isRepeatActiveField.value || - repeatFrequencyField.value !== frequency - ) { - selectedWeekdayIdsField.onChange([]); - repeatDayField.onChange(""); + const reset = getRepeatDetailsReset({ + currentFrequency: repeatFrequencyField.value, + nextFrequency: frequency, + isRepeatActive: isRepeatActiveField.value, + emptyDay: "", + }); + + if (reset) { + selectedWeekdayIdsField.onChange(reset.weekdays); + repeatDayField.onChange(reset.day); } isRepeatActiveField.onChange(true); repeatFrequencyField.onChange(frequency); diff --git a/apps/timo-web/utils/todo/repeat-detail-reset.ts b/apps/timo-web/utils/todo/repeat-detail-reset.ts new file mode 100644 index 00000000..fff5eeab --- /dev/null +++ b/apps/timo-web/utils/todo/repeat-detail-reset.ts @@ -0,0 +1,22 @@ +import type { RepeatFrequency } from "@repo/timo-design-system/ui"; + +export interface GetRepeatDetailsResetParams { + currentFrequency: RepeatFrequency | "NONE"; + nextFrequency: RepeatFrequency; + isRepeatActive: boolean; + emptyDay: TDay; +} + +export const getRepeatDetailsReset = ({ + currentFrequency, + nextFrequency, + isRepeatActive, + emptyDay, +}: GetRepeatDetailsResetParams): { + weekdays: string[]; + day: TDay; +} | null => { + if (isRepeatActive && currentFrequency === nextFrequency) return null; + + return { weekdays: [], day: emptyDay }; +}; From d67426d3f709ed16510235b1e941ce71919641a2 Mon Sep 17 00:00:00 2001 From: yumin-kim4757 Date: Sun, 4 Oct 2026 18:08:39 +0900 Subject: [PATCH 28/89] =?UTF-8?q?fix(web):=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EB=B0=98=EB=B3=B5=20=EC=95=84=EC=9D=B4=EC=BD=98=EA=B3=BC=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EA=B2=B0=EA=B3=BC=20=EC=9D=BC=EC=B9=98=20?= =?UTF-8?q?(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../detail/DetailTodoModalContent.tsx | 2 +- .../todo-modal/detail/use-detail-todo-form.ts | 37 ++++++---- .../detail/use-detail-todo-patch-handlers.ts | 32 +-------- .../timo-web/utils/todo/detail-todo-repeat.ts | 71 +++++++++++++++++++ 4 files changed, 95 insertions(+), 47 deletions(-) create mode 100644 apps/timo-web/utils/todo/detail-todo-repeat.ts diff --git a/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx b/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx index f5c122e7..05df9d21 100644 --- a/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx +++ b/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx @@ -221,7 +221,7 @@ export const DetailTodoModalContent = ({ onSelectTag={patchHandlers.handleSelectTag} onAddTagClick={detailTodoForm.handleAddTagClick} hasMemo={detailTodoForm.memo.trim().length > 0} - isRepeatActive={detailTodoForm.isRepeatActive} + isRepeatActive={detailTodoForm.isRepeatEffective} repeat={{ frequencyHeading: t("repeatFrequencyHeading"), detailHeading: tCreateModal("repeatDetailHeading"), diff --git a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts index df0e1422..3bdf83de 100644 --- a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts +++ b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-form.ts @@ -15,6 +15,10 @@ import { SECONDS_PER_MINUTE } from "@/constants/time"; import { useTagField } from "@/hooks/todo-modal/common/use-tag-field"; import { useDetailSubtaskField } from "@/hooks/todo-modal/detail/use-detail-subtask-field"; import { parseDateKey } from "@/utils/date/date"; +import { + getEffectiveDetailTodoRepeat, + isSameEffectiveDetailTodoRepeat, +} from "@/utils/todo/detail-todo-repeat"; import { getRepeatDetailsReset } from "@/utils/todo/repeat-detail-reset"; import { TITLE_MAX_WEIGHTED_LENGTH, @@ -180,20 +184,22 @@ export const useDetailTodoForm = ({ repeatFrequencyField.onChange(frequency); }; - const initialWeekdayIds = todo.repeat.weekdays ?? []; - const currentWeekdayIds = selectedWeekdayIdsField.value ?? []; - const sortedInitialWeekdayIds = [...initialWeekdayIds].sort(); - const sortedCurrentWeekdayIds = [...currentWeekdayIds].sort(); - const hasSameWeekdays = - sortedInitialWeekdayIds.length === sortedCurrentWeekdayIds.length && - sortedInitialWeekdayIds.every( - (weekdayId, index) => weekdayId === sortedCurrentWeekdayIds[index], - ); - const hasRepeatChanges = - isRepeatActiveField.value !== (todo.repeat.type !== "NONE") || - repeatFrequencyField.value !== repeatType || - !hasSameWeekdays || - repeatDayField.value !== (todo.repeat.dayOfMonth?.toString() ?? ""); + const effectiveRepeat = getEffectiveDetailTodoRepeat({ + isRepeatActive: isRepeatActiveField.value, + repeatFrequency: repeatFrequencyField.value, + selectedWeekdayIds: selectedWeekdayIdsField.value ?? [], + repeatDay: repeatDayField.value, + }); + const initialEffectiveRepeat = getEffectiveDetailTodoRepeat({ + isRepeatActive: todo.repeat.type !== "NONE", + repeatFrequency: repeatType, + selectedWeekdayIds: todo.repeat.weekdays ?? [], + repeatDay: todo.repeat.dayOfMonth?.toString() ?? "", + }); + const hasRepeatChanges = !isSameEffectiveDetailTodoRepeat( + effectiveRepeat, + initialEffectiveRepeat, + ); const getTagIdByLabel = (label: string) => { const option = tagField.tagOptions.find((item) => item.label === label); @@ -218,7 +224,8 @@ export const useDetailTodoForm = ({ closeTagLimitToast: tagField.closeTagLimitToast, isCreateTagErrorToastOpen: tagField.isCreateTagErrorToastOpen, closeCreateTagErrorToast: tagField.closeCreateTagErrorToast, - isRepeatActive: isRepeatActiveField.value, + isRepeatEffective: effectiveRepeat.repeatType !== "NONE", + effectiveRepeat, repeatFrequency: repeatFrequencyField.value, selectedWeekdayIds: selectedWeekdayIdsField.value, setSelectedWeekdayIds, diff --git a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts index b6bcde8f..c8aa5a69 100644 --- a/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts +++ b/apps/timo-web/hooks/todo-modal/detail/use-detail-todo-patch-handlers.ts @@ -10,7 +10,6 @@ import type { } from "@repo/timo-design-system/ui"; import { formatDateKey } from "@/utils/date/date"; -import { isTodoUpdateRepeatWeekday } from "@/utils/todo/detail-todo-update-request"; import { convertApiDurationToSeconds, convertClockTimeTextToApiDuration, @@ -107,36 +106,7 @@ export const useDetailTodoPatchHandlers = ({ const submitRepeatChange = () => { if (!form.hasRepeatChanges) return; - - const updateData: TodoUpdateRequest = { - repeatType: form.isRepeatActive ? form.repeatFrequency : "NONE", - }; - - if (form.isRepeatActive && form.repeatFrequency === "WEEKLY") { - const repeatWeekdays = form.selectedWeekdayIds.filter( - isTodoUpdateRepeatWeekday, - ); - if (repeatWeekdays.length === 0) { - updateData.repeatType = "NONE"; - } else { - updateData.repeatWeekdays = repeatWeekdays; - } - } - - if (form.isRepeatActive && form.repeatFrequency === "MONTHLY") { - const repeatDayOfMonth = Number(form.repeatDay); - if ( - Number.isInteger(repeatDayOfMonth) && - repeatDayOfMonth >= 1 && - repeatDayOfMonth <= 31 - ) { - updateData.repeatDayOfMonth = repeatDayOfMonth; - } else if (form.repeatDay.trim() === "") { - updateData.repeatType = "NONE"; - } - } - - updateTodo(updateData); + updateTodo(form.effectiveRepeat); }; const handleSubtaskCompletedChange = (id: number, completed: boolean) => { diff --git a/apps/timo-web/utils/todo/detail-todo-repeat.ts b/apps/timo-web/utils/todo/detail-todo-repeat.ts new file mode 100644 index 00000000..b50a138c --- /dev/null +++ b/apps/timo-web/utils/todo/detail-todo-repeat.ts @@ -0,0 +1,71 @@ +import type { TodoUpdateRequestRepeatWeekdaysItem } from "@/generated/models"; +import type { RepeatFrequency } from "@repo/timo-design-system/ui"; + +import { isTodoUpdateRepeatWeekday } from "@/utils/todo/detail-todo-update-request"; + +export interface DetailTodoRepeatValues { + isRepeatActive: boolean; + repeatFrequency: RepeatFrequency; + selectedWeekdayIds: string[]; + repeatDay: string; +} + +export type EffectiveDetailTodoRepeat = + | { repeatType: "NONE" | "DAILY" } + | { + repeatType: "WEEKLY"; + repeatWeekdays: TodoUpdateRequestRepeatWeekdaysItem[]; + } + | { repeatType: "MONTHLY"; repeatDayOfMonth?: number }; + +export const getEffectiveDetailTodoRepeat = ({ + isRepeatActive, + repeatFrequency, + selectedWeekdayIds, + repeatDay, +}: DetailTodoRepeatValues): EffectiveDetailTodoRepeat => { + if (!isRepeatActive) return { repeatType: "NONE" }; + if (repeatFrequency === "DAILY") return { repeatType: "DAILY" }; + + if (repeatFrequency === "WEEKLY") { + const repeatWeekdays = selectedWeekdayIds.filter(isTodoUpdateRepeatWeekday); + return repeatWeekdays.length > 0 + ? { repeatType: "WEEKLY", repeatWeekdays } + : { repeatType: "NONE" }; + } + + const repeatDayOfMonth = Number(repeatDay); + if ( + Number.isInteger(repeatDayOfMonth) && + repeatDayOfMonth >= 1 && + repeatDayOfMonth <= 31 + ) { + return { repeatType: "MONTHLY", repeatDayOfMonth }; + } + + return repeatDay.trim() === "" + ? { repeatType: "NONE" } + : { repeatType: "MONTHLY" }; +}; + +export const isSameEffectiveDetailTodoRepeat = ( + first: EffectiveDetailTodoRepeat, + second: EffectiveDetailTodoRepeat, +): boolean => { + if (first.repeatType !== second.repeatType) return false; + + if (first.repeatType === "WEEKLY" && second.repeatType === "WEEKLY") { + const firstWeekdays = [...first.repeatWeekdays].sort(); + const secondWeekdays = [...second.repeatWeekdays].sort(); + return ( + firstWeekdays.length === secondWeekdays.length && + firstWeekdays.every((day, index) => day === secondWeekdays[index]) + ); + } + + if (first.repeatType === "MONTHLY" && second.repeatType === "MONTHLY") { + return first.repeatDayOfMonth === second.repeatDayOfMonth; + } + + return true; +}; From 1967ea21625c0b40b9d72e6d1cd2f58b8a023ce5 Mon Sep 17 00:00:00 2001 From: yumin-kim4757 Date: Sun, 4 Oct 2026 18:08:53 +0900 Subject: [PATCH 29/89] =?UTF-8?q?fix(web):=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=ED=8F=BC=20=EC=A3=BC=EA=B0=84=20=EB=B0=98=EB=B3=B5=20=ED=95=B4?= =?UTF-8?q?=EC=A0=9C=20=EC=8B=9C=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EC=88=98=EC=A0=95=20(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo-modal/create/CreateTodoModalContent.tsx | 2 +- .../hooks/todo-modal/create/use-repeat-field.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/timo-web/components/todo-modal/create/CreateTodoModalContent.tsx b/apps/timo-web/components/todo-modal/create/CreateTodoModalContent.tsx index ec8603a5..cb6d769e 100644 --- a/apps/timo-web/components/todo-modal/create/CreateTodoModalContent.tsx +++ b/apps/timo-web/components/todo-modal/create/CreateTodoModalContent.tsx @@ -179,7 +179,7 @@ export const CreateTodoModalContent = ({ onSelectTag={tagField.handleSelectTag} onAddTagClick={tagField.handleAddTagClick} hasMemo={(memoField.value ?? "").trim().length > 0} - isRepeatActive={repeatField.isRepeatActive} + isRepeatActive={repeatField.isRepeatEffective} repeat={{ detailHeading: t("createModal.repeatDetailHeading"), options: [ diff --git a/apps/timo-web/hooks/todo-modal/create/use-repeat-field.ts b/apps/timo-web/hooks/todo-modal/create/use-repeat-field.ts index 33ad49b1..b7b759cc 100644 --- a/apps/timo-web/hooks/todo-modal/create/use-repeat-field.ts +++ b/apps/timo-web/hooks/todo-modal/create/use-repeat-field.ts @@ -43,8 +43,17 @@ export const useRepeatField = ({ control }: UseRepeatFieldParams) => { const repeatType = repeatTypeField.field.value; const repeatWeekdays = repeatWeekdaysField.field.value ?? []; + const repeatDayOfMonth = repeatDayField.field.value; const isRepeatActive = repeatType !== "NONE"; + const isRepeatEffective = + repeatType === "DAILY" || + (repeatType === "WEEKLY" && repeatWeekdays.length > 0) || + (repeatType === "MONTHLY" && + typeof repeatDayOfMonth === "number" && + Number.isInteger(repeatDayOfMonth) && + repeatDayOfMonth >= 1 && + repeatDayOfMonth <= 31); const uiRepeatFrequency: RepeatFrequency = repeatType === "NONE" ? "DAILY" : repeatType; @@ -77,9 +86,9 @@ export const useRepeatField = ({ control }: UseRepeatFieldParams) => { return { repeatType, repeatWeekdays, - repeatDayOfMonth: repeatDayField.field.value, + repeatDayOfMonth, weekdays, - isRepeatActive, + isRepeatEffective, uiRepeatFrequency, handleFrequencyChange, handleWeekdaysChange, From 56f54b54a4a5c2629e1bc30246ad82dfbb4baaa0 Mon Sep 17 00:00:00 2001 From: yumin-kim4757 Date: Sun, 4 Oct 2026 22:24:04 +0900 Subject: [PATCH 30/89] =?UTF-8?q?fix(web):=20=ED=86=B5=EA=B3=84=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B8=B0=EB=A1=9D=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=AC=B8=EA=B5=AC=20=EC=88=98=EC=A0=95=20(#311)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/timo-web/messages/en.json | 2 +- apps/timo-web/messages/ko.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/timo-web/messages/en.json b/apps/timo-web/messages/en.json index 180de03d..7b1dfb23 100644 --- a/apps/timo-web/messages/en.json +++ b/apps/timo-web/messages/en.json @@ -141,7 +141,7 @@ "activeDayCount": "{count} days", "dailyAverage": "Daily average", "accumulatedTasks": "Accumulated tasks", - "dayTotalRecordTime": "Total record time today", + "dayTotalRecordTime": "Total record time", "plan": "Plan", "overtime": "+Overdue", "shortened": "-Shortened" diff --git a/apps/timo-web/messages/ko.json b/apps/timo-web/messages/ko.json index 7e48164f..8f5485b0 100644 --- a/apps/timo-web/messages/ko.json +++ b/apps/timo-web/messages/ko.json @@ -141,7 +141,7 @@ "activeDayCount": "{count}일", "dailyAverage": "일평균", "accumulatedTasks": "누적 태스크", - "dayTotalRecordTime": "오늘의 총 기록 시간", + "dayTotalRecordTime": "총 기록 시간", "plan": "계획", "overtime": "+초과", "shortened": "-단축" From ccd6bfca0e00716133926f135a7ae0748c7fac49 Mon Sep 17 00:00:00 2001 From: yumin-kim4757 Date: Sun, 4 Oct 2026 23:18:12 +0900 Subject: [PATCH 31/89] =?UTF-8?q?feat(web):=20=ED=86=B5=EA=B3=84=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9B=94=EB=B3=84=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=B5=EA=B7=80=20=EB=8F=99=EC=9E=91=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#311)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/StatisticsCalendar.tsx | 18 ++++++++++++++---- .../StatisticsCalendarContainer.tsx | 3 +++ .../_containers/StatisticsContainer.tsx | 9 +++++++++ apps/timo-web/messages/en.json | 3 ++- apps/timo-web/messages/ko.json | 3 ++- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx b/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx index 98b16dad..9ed5264c 100644 --- a/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx +++ b/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx @@ -5,7 +5,7 @@ import { StatisticsClockOutlineIcon, } from "@repo/timo-design-system/icons"; import { cn } from "@repo/timo-design-system/utils"; -import { useLocale } from "next-intl"; +import { useLocale, useTranslations } from "next-intl"; import type { StatisticsCalendarResponse } from "@/app/[locale]/(main)/statistics/_types/statistics"; @@ -62,6 +62,7 @@ interface StatisticsCalendarProps { displayDate: Date; selectedDate: Date | null; onSelectDate: (date: Date) => void; + onDeselectDate: () => void; calendarData: StatisticsCalendarResponse; } @@ -70,9 +71,11 @@ export const StatisticsCalendar = ({ displayDate, selectedDate, onSelectDate, + onDeselectDate, calendarData, }: StatisticsCalendarProps) => { const locale = useLocale(); + const t = useTranslations("Statistics.calendar"); const today = parseDateKey(calendarData.today) ?? new Date(); const calendarDates = getCalendarDates(currentMonth); const firstDayOffset = getFirstDayOffset(currentMonth); @@ -83,8 +86,15 @@ export const StatisticsCalendar = ({ const todayTime = getDateTime(today); return ( -
-
+
+
diff --git a/apps/timo-web/messages/en.json b/apps/timo-web/messages/en.json index 7b1dfb23..a1d69f82 100644 --- a/apps/timo-web/messages/en.json +++ b/apps/timo-web/messages/en.json @@ -133,7 +133,8 @@ "Statistics": { "calendar": { "loading": "Loading statistics calendar.", - "error": "Failed to load statistics calendar." + "error": "Failed to load statistics calendar.", + "showMonthlyRecords": "Show monthly records" }, "sidePanel": { "monthTotalRecordTime": "Total record time this month", diff --git a/apps/timo-web/messages/ko.json b/apps/timo-web/messages/ko.json index 8f5485b0..ee93657c 100644 --- a/apps/timo-web/messages/ko.json +++ b/apps/timo-web/messages/ko.json @@ -133,7 +133,8 @@ "Statistics": { "calendar": { "loading": "통계 캘린더를 불러오고 있어요.", - "error": "통계 캘린더를 불러오지 못했어요." + "error": "통계 캘린더를 불러오지 못했어요.", + "showMonthlyRecords": "월별 기록 보기" }, "sidePanel": { "monthTotalRecordTime": "이번 달의 총 기록 시간", From d41f78cb1984100f90e75b4e1d244ff883ac7cf8 Mon Sep 17 00:00:00 2001 From: yumin-kim4757 Date: Sun, 4 Oct 2026 23:22:51 +0900 Subject: [PATCH 32/89] =?UTF-8?q?fix(web):=20=ED=86=B5=EA=B3=84=20?= =?UTF-8?q?=EB=8B=AC=EB=A0=A5=EC=9D=98=20=EC=9B=94=EB=B3=84=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=B5=EA=B7=80=20=ED=81=B4=EB=A6=AD=20=EB=B2=94?= =?UTF-8?q?=EC=9C=84=20=EB=B3=B4=EC=99=84=20(#311)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(main)/statistics/_components/StatisticsCalendar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx b/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx index 9ed5264c..be4dce58 100644 --- a/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx +++ b/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx @@ -86,7 +86,7 @@ export const StatisticsCalendar = ({ const todayTime = getDateTime(today); return ( -
+
); }; From 52e76e3fea0dd3727ca1a357c54ec9360b7514b5 Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:50:45 +0900 Subject: [PATCH 34/89] =?UTF-8?q?refactor(ui):=20Button=20=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EA=B5=AC=EC=84=B1=20=EB=B0=8F=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81=20(#305)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 오른쪽 아이콘 버튼을 포함해 피그마 32개 조합을 확인할 수 있도록 했습니다 - 아이콘 스토리를 WithLeftIcon, WithRightIcon으로 나누고 variant·비활성 상태에 맞는 아이콘을 보여주도록 했습니다 - 아이콘이 배경에 묻히지 않도록 아이콘 스토리의 appearance를 fill로 고정했습니다 - 버튼을 size에 맞는 고정 폭 안에서 보여주고, 반반 배치 예시는 grid로 바꿨습니다 - Boolean 변수명을 isDisabled로 바꿨습니다 --- .../button/button/Button.stories.tsx | 136 ++++++++++++------ 1 file changed, 90 insertions(+), 46 deletions(-) diff --git a/packages/timo-design-system/src/components/button/button/Button.stories.tsx b/packages/timo-design-system/src/components/button/button/Button.stories.tsx index 5e2f828e..240eec39 100644 --- a/packages/timo-design-system/src/components/button/button/Button.stories.tsx +++ b/packages/timo-design-system/src/components/button/button/Button.stories.tsx @@ -1,20 +1,30 @@ import { Button } from "./Button"; import { TrashDisableIcon, TrashOnIcon, TrashWhiteIcon } from "../../../icons"; -import type { Meta, StoryObj } from "@storybook/react"; +import type { ButtonProps } from "./Button"; +import type { Decorator, Meta, StoryObj } from "@storybook/react"; const VARIANTS = ["primary", "secondary"] as const; const APPEARANCES = ["fill", "outline"] as const; const SIZES = ["m", "lg"] as const; +const ICON_POSITIONS = ["left", "right"] as const; -const getTrashIcon = ( - variant: (typeof VARIANTS)[number], - disabled: boolean, -) => { +const STORY_WIDTH: Record = { + m: "w-28", + lg: "w-43", +}; + +const getTrashIcon = (variant: ButtonProps["variant"], isDisabled: boolean) => { if (variant === "primary") return ; - return disabled ? : ; + return isDisabled ? : ; }; +const withStoryWidth: Decorator = (Story, { args }) => ( +
+ +
+); + const meta = { title: "Components/Button/Button", component: Button, @@ -27,6 +37,8 @@ const meta = { size: { control: "select", options: SIZES }, disabled: { control: "boolean" }, children: { control: "text" }, + icon: { control: false }, + iconPosition: { control: false }, }, args: { variant: "primary", @@ -40,7 +52,9 @@ const meta = { export default meta; type Story = StoryObj; -export const Default: Story = {}; +export const Default: Story = { + decorators: [withStoryWidth], +}; export const AllVariants: Story = { parameters: { controls: { disable: true } }, @@ -48,33 +62,44 @@ export const AllVariants: Story = {
{SIZES.map((size) => (
- {[false, true].map((disabled) => ( -
+ {[false, true].map((isDisabled) => ( +
{VARIANTS.map((variant) => APPEARANCES.map((appearance) => ( - +
+ )), + )} + {ICON_POSITIONS.map((iconPosition) => + VARIANTS.map((variant) => ( +
- {size === "m" ? "생성하기" : "시간 추가하기"} - + +
)), )} - {VARIANTS.map((variant) => ( - - ))}
))}
@@ -83,35 +108,54 @@ export const AllVariants: Story = { ), }; -export const WithIcon: Story = { +export const WithLeftIcon: Story = { + decorators: [withStoryWidth], args: { - icon: , + appearance: "fill", + iconPosition: "left", children: "삭제하기", }, + argTypes: { + appearance: { control: false }, + }, + render: (args) => ( + -
- -
From 3a8e8c8ca248508e9bc20dffcf0f4cfa70330b61 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Mon, 5 Oct 2026 14:33:08 +0900 Subject: [PATCH 35/89] =?UTF-8?q?refactor(web):=20=ED=8F=AC=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=20=ED=97=A4=EB=8D=94=EB=A5=BC=20=EC=BB=A8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=84=88=EC=97=90=EC=84=9C=20=EC=A1=B0=ED=95=A9?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83?= =?UTF-8?q?=EC=9D=80=20header=20=EC=8A=AC=EB=A1=AF=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=9B=EA=B8=B0=20(#303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FocusSessionLayout이 FocusHeaderContainer를 직접 렌더링해 사이드바 Context에 의존하던 구조를 바꿉니다. 헤더는 FocusSessionContainer에서 한 번 조합해 optional header prop으로 넘기고, 레이아웃은 presentational 컴포넌트로 유지합니다. Co-Authored-By: Claude Sonnet 5 --- .../[locale]/(main)/focus/_components/FocusSessionLayout.tsx | 5 +++-- .../(main)/focus/_containers/FocusSessionContainer.tsx | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/focus/_components/FocusSessionLayout.tsx b/apps/timo-web/app/[locale]/(main)/focus/_components/FocusSessionLayout.tsx index 56c84a10..59a72882 100644 --- a/apps/timo-web/app/[locale]/(main)/focus/_components/FocusSessionLayout.tsx +++ b/apps/timo-web/app/[locale]/(main)/focus/_components/FocusSessionLayout.tsx @@ -1,9 +1,9 @@ import type { ReactNode } from "react"; -import { FocusHeaderContainer } from "@/app/[locale]/(main)/focus/_containers/FocusHeaderContainer"; import { AnimatedToast } from "@/components/toast/AnimatedToast"; export interface FocusSessionLayoutProps { + header?: ReactNode; isErrorToastOpen: boolean; onCloseErrorToast: () => void; errorToastMessage: string; @@ -13,6 +13,7 @@ export interface FocusSessionLayoutProps { } export const FocusSessionLayout = ({ + header, isErrorToastOpen, onCloseErrorToast, errorToastMessage, @@ -22,7 +23,7 @@ export const FocusSessionLayout = ({ }: FocusSessionLayoutProps) => { return (
- + {header} { onFeedback: setFeedbackText, }); + const header = ; + if (!focusSessionState.focusView.hasTodo || !focusSessionState.todo) { return ( setIsErrorToastOpen(false)} errorToastMessage={tToast("focusActionFailed")} @@ -79,6 +83,7 @@ export const FocusSessionContainer = () => { return ( setIsErrorToastOpen(false)} errorToastMessage={tToast("focusActionFailed")} From f5b8ec6365cbc4dea711e040708d8568decc108e Mon Sep 17 00:00:00 2001 From: jjangminii Date: Mon, 5 Oct 2026 14:50:15 +0900 Subject: [PATCH 36/89] =?UTF-8?q?fix(web):=20=EB=AA=A8=EB=B0=94=EC=9D=BC?= =?UTF-8?q?=20=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EC=84=A0=ED=83=9D=20=EC=8B=9C=20=EB=93=9C?= =?UTF-8?q?=EB=A1=9C=EC=96=B4=20=EB=8B=AB=EA=B8=B0=20(#303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 메뉴 항목과 로고 링크를 누르면 모바일 드로어가 닫히도록 closeMobileSidebar를 추가하고, 오버레이 클릭과 Esc 닫기도 같은 핸들러를 쓰도록 통일합니다. 드로어가 닫혀 있을 때만 토글하므로 데스크톱 동작에는 영향이 없습니다. Co-Authored-By: Claude Sonnet 5 --- .../sidebar/navigation/NavigationSidebar.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/apps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsx b/apps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsx index 8c4b7d7b..8fcfeb80 100644 --- a/apps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsx +++ b/apps/timo-web/components/layout/sidebar/navigation/NavigationSidebar.tsx @@ -71,17 +71,23 @@ const isActivePath = (pathname: string, href: string) => interface NavigationSidebarNavItemProps { item: (typeof MAIN_NAV_ITEMS)[number] | typeof SETTINGS_NAV_ITEM; pathname: string; + onNavigate: () => void; } const NavigationSidebarNavItem = ({ item: { href, labelKey, OnIcon, OffIcon, HoverIcon }, pathname, + onNavigate, }: NavigationSidebarNavItemProps) => { const t = useTranslations("Navigation"); const isSelected = isActivePath(pathname, href); return ( - + { const { isOpen, isMobileOpen, toggle } = useNavigationSidebar(); - useEscapeKey(isMobileOpen, toggle); + const closeMobileSidebar = () => { + if (isMobileOpen) toggle(); + }; + + useEscapeKey(isMobileOpen, closeMobileSidebar); return ( <> From ff7b68f79158bade7596c206a12ff0b05635ad35 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Mon, 5 Oct 2026 15:57:08 +0900 Subject: [PATCH 37/89] =?UTF-8?q?fix(web):=20=EB=AA=A8=EB=B0=94=EC=9D=BC?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=8B=9C=EA=B0=84=20=ED=8C=A8=EB=84=90?= =?UTF-8?q?=EC=9D=84=20=EA=B8=B0=EB=B3=B8=20=EB=8B=AB=ED=9E=98=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=8B=9C=EC=9E=91=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B6=84=EB=A6=AC=20(#303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 시간 패널이 데스크톱과 같은 isOpen을 써서 모바일에서도 기본 열림으로 시작하던 문제를 고칩니다. 모바일 드로어 상태(isMobileOpen, 기본 닫힘)를 따로 두고, 드로어 위치·오버레이·Esc·헤더 버튼은 이 값을 따릅니다. 데스크톱 폭과 마진은 기존 isOpen을 유지합니다. Co-Authored-By: Claude Sonnet 5 --- .../_containers/WithTimeSidebarContainer.tsx | 15 ++++++++++++- .../home/_containers/HomeHeaderContainer.tsx | 12 +++++----- .../_containers/TodayHeaderContainer.tsx | 12 +++++----- .../layout/sidebar/time/TimeSidebar.tsx | 22 ++++++++++++------- .../time-sidebar/useTimeSidebarStore.ts | 10 ++++++++- 5 files changed, 51 insertions(+), 20 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsx index 0d8d4e73..8de25438 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_containers/WithTimeSidebarContainer.tsx @@ -20,7 +20,14 @@ export const WithTimeSidebarContainer = ({ children, }: WithTimeSidebarContainerProps) => { const isOpen = useTimeSidebarStore((state) => state.isOpen); + const isMobileOpen = useTimeSidebarStore((state) => state.isMobileOpen); const toggleOpen = useTimeSidebarStore((state) => state.toggleOpen); + const closeMobileOpen = useTimeSidebarStore((state) => state.closeMobileOpen); + + const handleToggleCollapse = () => { + closeMobileOpen(); + toggleOpen(); + }; const pathname = usePathname(); @@ -39,7 +46,13 @@ export const WithTimeSidebarContainer = ({ {children}
- + ); }; diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer.tsx index 1e793e20..d8aa239d 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeHeaderContainer.tsx @@ -16,9 +16,11 @@ export const HomeHeaderContainer = () => { const viewOptions = [basicLabel, weekLabel]; const { isOpen, toggle } = useNavigationSidebar(); - const isTimeSidebarOpen = useTimeSidebarStore((state) => state.isOpen); - const toggleTimeSidebarOpen = useTimeSidebarStore( - (state) => state.toggleOpen, + const isTimeSidebarMobileOpen = useTimeSidebarStore( + (state) => state.isMobileOpen, + ); + const toggleTimeSidebarMobileOpen = useTimeSidebarStore( + (state) => state.toggleMobileOpen, ); const { isWeekView, setViewMode, goToNextWeek, goToPrevWeek, goToToday } = useHomeViewMode(); @@ -56,8 +58,8 @@ export const HomeHeaderContainer = () => { onChange={handleChangeView} /> diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer.tsx index 2ca057a9..e47f0d79 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayHeaderContainer.tsx @@ -6,9 +6,11 @@ import { useTimeSidebarStore } from "@/stores/time-sidebar/useTimeSidebarStore"; export const TodayHeaderContainer = () => { const { isOpen, toggle } = useNavigationSidebar(); - const isTimeSidebarOpen = useTimeSidebarStore((state) => state.isOpen); - const toggleTimeSidebarOpen = useTimeSidebarStore( - (state) => state.toggleOpen, + const isTimeSidebarMobileOpen = useTimeSidebarStore( + (state) => state.isMobileOpen, + ); + const toggleTimeSidebarMobileOpen = useTimeSidebarStore( + (state) => state.toggleMobileOpen, ); return ( @@ -16,8 +18,8 @@ export const TodayHeaderContainer = () => { left={} right={ diff --git a/apps/timo-web/components/layout/sidebar/time/TimeSidebar.tsx b/apps/timo-web/components/layout/sidebar/time/TimeSidebar.tsx index 94f823bc..003f8a65 100644 --- a/apps/timo-web/components/layout/sidebar/time/TimeSidebar.tsx +++ b/apps/timo-web/components/layout/sidebar/time/TimeSidebar.tsx @@ -22,22 +22,28 @@ const noop = () => {}; export interface TimeSidebarProps { size?: TimeSidebarSize; isOpen?: boolean; + isMobileOpen?: boolean; onToggleCollapse?: () => void; + onCloseMobile?: () => void; } export const TimeSidebar = ({ size = "sm", isOpen = true, + isMobileOpen = false, onToggleCollapse, + onCloseMobile, }: TimeSidebarProps) => { const id = useId(); const activeTab = useTimeSidebarStore((state) => state.activeTab); const setActiveTab = useTimeSidebarStore((state) => state.setActiveTab); + const isExpanded = isOpen || isMobileOpen; + const timeboxScrollRef = useRef(null); useEffect(() => { - if (!isOpen || activeTab !== "timebox") return; + if (!isExpanded || activeTab !== "timebox") return; const container = timeboxScrollRef.current; if (!container) return; const now = new Date(); @@ -46,7 +52,7 @@ export const TimeSidebar = ({ top: Math.max(0, offset - container.clientHeight / 2), behavior: "smooth", }); - }, [activeTab, isOpen]); + }, [activeTab, isExpanded]); const timeboxPanelId = `${id}-timebox-panel`; const timerPanelId = `${id}-timer-panel`; @@ -55,16 +61,16 @@ export const TimeSidebar = ({ setActiveTab(value as TimeSidebarTab); }; - useEscapeKey(isOpen, onToggleCollapse ?? noop); + useEscapeKey(isMobileOpen, onCloseMobile ?? noop); return ( <> + {feedback && ( +
+ {feedback} +
+ )} , document.body, ); diff --git a/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx b/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx index 05df9d21..515c8165 100644 --- a/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx +++ b/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx @@ -5,7 +5,7 @@ import { DeleteIcon, TrashOnIcon } from "@repo/timo-design-system/icons"; import { Modal, TodoToolbar } from "@repo/timo-design-system/ui"; import Image from "next/image"; import { useTranslations } from "next-intl"; -import { useRef } from "react"; +import { useRef, useState } from "react"; import type { TodoDetailResponse, @@ -80,6 +80,8 @@ export const DetailTodoModalContent = ({ const tCreateModal = useTranslations("Home.createModal"); const tCommon = useTranslations("Common"); const tToast = useTranslations("Toast"); + const [isRepeatDayErrorToastOpen, setIsRepeatDayErrorToastOpen] = + useState(false); const detailTodoForm = useDetailTodoForm({ todo, onUpdate }); const dateNumber = detailTodoForm.date.getDate(); const dayOfWeek = isDetailTodoWeekdayId(todo.dayOfWeek) @@ -112,8 +114,11 @@ export const DetailTodoModalContent = ({ }); const handleClose = () => { + if (!patchHandlers.submitRepeatChange()) { + setIsRepeatDayErrorToastOpen(true); + return; + } submitPendingUpdates(); - patchHandlers.submitRepeatChange(); onClose(); }; @@ -130,6 +135,13 @@ export const DetailTodoModalContent = ({ onExited={onExited} ariaLabel={t("ariaLabel")} className="w-124 items-start px-7.5 py-5" + feedback={ + setIsRepeatDayErrorToastOpen(false)} + message={tToast("repeatDayInvalid")} + /> + } >
diff --git a/apps/timo-web/messages/en.json b/apps/timo-web/messages/en.json index a1d69f82..7b1dfb23 100644 --- a/apps/timo-web/messages/en.json +++ b/apps/timo-web/messages/en.json @@ -133,8 +133,7 @@ "Statistics": { "calendar": { "loading": "Loading statistics calendar.", - "error": "Failed to load statistics calendar.", - "showMonthlyRecords": "Show monthly records" + "error": "Failed to load statistics calendar." }, "sidePanel": { "monthTotalRecordTime": "Total record time this month", diff --git a/apps/timo-web/messages/ko.json b/apps/timo-web/messages/ko.json index ee93657c..8f5485b0 100644 --- a/apps/timo-web/messages/ko.json +++ b/apps/timo-web/messages/ko.json @@ -133,8 +133,7 @@ "Statistics": { "calendar": { "loading": "통계 캘린더를 불러오고 있어요.", - "error": "통계 캘린더를 불러오지 못했어요.", - "showMonthlyRecords": "월별 기록 보기" + "error": "통계 캘린더를 불러오지 못했어요." }, "sidePanel": { "monthTotalRecordTime": "이번 달의 총 기록 시간", From fdf5b4588648be868d019ab257449066427e74a6 Mon Sep 17 00:00:00 2001 From: yumin-kim4757 Date: Tue, 6 Oct 2026 23:08:26 +0900 Subject: [PATCH 49/89] =?UTF-8?q?refactor(web):=20=ED=86=B5=EA=B3=84=20?= =?UTF-8?q?=EB=8B=AC=EB=A0=A5=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=84=A4=EC=A0=95(relative)=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20(#311)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(main)/statistics/_components/StatisticsCalendar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx b/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx index 8d554eea..a2eab948 100644 --- a/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx +++ b/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx @@ -96,7 +96,7 @@ export const StatisticsCalendar = ({ return ( // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
From d3365b16ad4baf849f90480056ed2fd714a819e2 Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:16:25 +0900 Subject: [PATCH 50/89] =?UTF-8?q?refactor(web):=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EB=B2=84=ED=8A=BC=EC=9D=84=20=EA=B3=B5?= =?UTF-8?q?=EC=9A=A9=20Button=EC=9C=BC=EB=A1=9C=20=EA=B5=90=EC=B2=B4=20(#3?= =?UTF-8?q?16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 할 일 생성·태그 생성 모달의 CreateButton을 공용 Button(primary fill m)으로 교체했습니다. --- .../components/tag/CreateTagModalContainer.tsx | 13 +++++++++---- .../todo-modal/create/CreateTodoModalContent.tsx | 13 +++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/timo-web/components/tag/CreateTagModalContainer.tsx b/apps/timo-web/components/tag/CreateTagModalContainer.tsx index 6f9a77db..d275d044 100644 --- a/apps/timo-web/components/tag/CreateTagModalContainer.tsx +++ b/apps/timo-web/components/tag/CreateTagModalContainer.tsx @@ -1,7 +1,7 @@ "use client"; import { DeleteIcon } from "@repo/timo-design-system/icons"; -import { CreateButton, TagNameInput } from "@repo/timo-design-system/ui"; +import { Button, TagNameInput } from "@repo/timo-design-system/ui"; import { useTranslations } from "next-intl"; import { useState } from "react"; @@ -81,11 +81,16 @@ export const CreateTagModalContainer = ({
- + > + {t("createTagModal.create")} +
); diff --git a/apps/timo-web/components/todo-modal/create/CreateTodoModalContent.tsx b/apps/timo-web/components/todo-modal/create/CreateTodoModalContent.tsx index 009984e6..95738e6f 100644 --- a/apps/timo-web/components/todo-modal/create/CreateTodoModalContent.tsx +++ b/apps/timo-web/components/todo-modal/create/CreateTodoModalContent.tsx @@ -2,7 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { DeleteIcon } from "@repo/timo-design-system/icons"; -import { CreateButton, TodoToolbar } from "@repo/timo-design-system/ui"; +import { Button, TodoToolbar } from "@repo/timo-design-system/ui"; import { useTranslations } from "next-intl"; import { useController, useForm } from "react-hook-form"; @@ -207,11 +207,16 @@ export const CreateTodoModalContent = ({ }} /> - + > + {t("createModal.create")} + From 742303bb4d87d5c35bdd09f944c6140a3562908b Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:17:07 +0900 Subject: [PATCH 51/89] =?UTF-8?q?refactor(ui):=20Modal=20=ED=95=98?= =?UTF-8?q?=EB=8B=A8=20=EB=B2=84=ED=8A=BC=EC=9D=84=20=EA=B3=B5=EC=9A=A9=20?= =?UTF-8?q?Button=EC=9C=BC=EB=A1=9C=20=EA=B5=90=EC=B2=B4=20(#316)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modal.BorderButton은 secondary outline lg, Modal.FillButton은 primary fill lg Button을 사용하도록 교체했습니다. --- .../src/components/layout/modal/Modal.tsx | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/timo-design-system/src/components/layout/modal/Modal.tsx b/packages/timo-design-system/src/components/layout/modal/Modal.tsx index 4c10ea60..c1e8d96c 100644 --- a/packages/timo-design-system/src/components/layout/modal/Modal.tsx +++ b/packages/timo-design-system/src/components/layout/modal/Modal.tsx @@ -15,7 +15,7 @@ import { import { createPortal } from "react-dom"; import { acquireModalStackIndex, cn, hasOpenFloatingLayer } from "../../../lib"; -import { ModalButton } from "../../button/modal-button/ModalButton"; +import { Button } from "../../button/button/Button"; // tailwind-config theme.css의 --z-index-modal-overlay/--z-index-modal-panel과 동일한 기준값이다. const BASE_OVERLAY_Z_INDEX = 40; @@ -232,20 +232,25 @@ export type ModalBorderButtonProps = ButtonHTMLAttributes; const ModalBorderButton = ({ onClick, className, + children, ...rest }: ModalBorderButtonProps) => { const { close } = useModalContext(); return ( - { onClick?.(e); close(); }} - className={cn("flex-1 px-0", className)} - /> + className={cn("flex-1", className)} + > + {children} + ); }; @@ -254,20 +259,25 @@ export type ModalFillButtonProps = ButtonHTMLAttributes; const ModalFillButton = ({ onClick, className, + children, ...rest }: ModalFillButtonProps) => { const { close } = useModalContext(); return ( - { onClick?.(e); close(); }} - className={cn("flex-1 px-0", className)} - /> + className={cn("flex-1", className)} + > + {children} + ); }; From c0b4f7a0f55d7b7b781d8ce3ea86a15ad6f03197 Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:21:22 +0900 Subject: [PATCH 52/89] =?UTF-8?q?refactor(web):=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=EC=9D=84=20=EA=B3=B5=EC=9A=A9=20Button?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EA=B5=90=EC=B2=B4=20(#316)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ModalButton을 직접 사용하던 모달을 공용 Button으로 교체하고, 시간 추가하기 버튼의 비활성 스타일 덮어쓰기와 중복 className을 제거했습니다. --- .../SettingsCalendarDisconnectModal.tsx | 22 +++++++++++------ .../SettingsWithdrawModalContainer.tsx | 22 +++++++++++------ .../timer/TimerCompleteModalPanel.tsx | 2 +- .../components/timer/TimerEndModalPanel.tsx | 22 ++++++++++------- .../timer/TimerExtendModalPanel.tsx | 24 +++++++------------ .../components/timer/TimerStopModalPanel.tsx | 16 ++++++++----- 6 files changed, 63 insertions(+), 45 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsCalendarDisconnectModal.tsx b/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsCalendarDisconnectModal.tsx index ac26e8c0..31f6a9d8 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsCalendarDisconnectModal.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsCalendarDisconnectModal.tsx @@ -1,7 +1,7 @@ "use client"; import timoTimerLogo from "@repo/timo-design-system/assets/images/logo/timo-timer.svg"; -import { ModalButton } from "@repo/timo-design-system/ui"; +import { Button } from "@repo/timo-design-system/ui"; import Image from "next/image"; import type { SettingsProfileLabels } from "@/app/[locale]/(main)/settings/_types/account/profile-type"; @@ -41,19 +41,27 @@ export const SettingsCalendarDisconnectModal = ({ {labels.calendarDisconnectConfirmDescription}

- +
); diff --git a/apps/timo-web/app/[locale]/(main)/settings/_containers/withdrawal/SettingsWithdrawModalContainer.tsx b/apps/timo-web/app/[locale]/(main)/settings/_containers/withdrawal/SettingsWithdrawModalContainer.tsx index 1f43dfba..4ae6e597 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_containers/withdrawal/SettingsWithdrawModalContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_containers/withdrawal/SettingsWithdrawModalContainer.tsx @@ -1,7 +1,7 @@ "use client"; import timoTimerLogo from "@repo/timo-design-system/assets/images/logo/timo-timer.svg"; -import { ModalButton } from "@repo/timo-design-system/ui"; +import { Button } from "@repo/timo-design-system/ui"; import Image from "next/image"; import { useTranslations } from "next-intl"; @@ -44,19 +44,27 @@ export const SettingsWithdrawModalContainer = ({ {t("withdrawConfirmDescription")}

- { onWithdraw(); onClose(); }} > {t("withdrawConfirmConfirm")} - - + +
); diff --git a/apps/timo-web/components/timer/TimerCompleteModalPanel.tsx b/apps/timo-web/components/timer/TimerCompleteModalPanel.tsx index babed0f6..c80ff2ea 100644 --- a/apps/timo-web/components/timer/TimerCompleteModalPanel.tsx +++ b/apps/timo-web/components/timer/TimerCompleteModalPanel.tsx @@ -50,7 +50,7 @@ export const TimerCompleteModalPanel = ({ - + {t("completeButton")} diff --git a/apps/timo-web/components/timer/TimerEndModalPanel.tsx b/apps/timo-web/components/timer/TimerEndModalPanel.tsx index b2e57e0d..370284e5 100644 --- a/apps/timo-web/components/timer/TimerEndModalPanel.tsx +++ b/apps/timo-web/components/timer/TimerEndModalPanel.tsx @@ -1,5 +1,5 @@ import timoTimerLogo from "@repo/timo-design-system/assets/images/logo/timo-timer.svg"; -import { Modal, ModalButton } from "@repo/timo-design-system/ui"; +import { Button, Modal } from "@repo/timo-design-system/ui"; import Image from "next/image"; import { useTranslations } from "next-intl"; @@ -22,20 +22,24 @@ export const TimerEndModalPanel = ({ {t("title")} {t("description")} - {t("continueButton")} - - + ); diff --git a/apps/timo-web/components/timer/TimerExtendModalPanel.tsx b/apps/timo-web/components/timer/TimerExtendModalPanel.tsx index 8de4e4be..289ececf 100644 --- a/apps/timo-web/components/timer/TimerExtendModalPanel.tsx +++ b/apps/timo-web/components/timer/TimerExtendModalPanel.tsx @@ -1,4 +1,4 @@ -import { Modal, ModalButton } from "@repo/timo-design-system/ui"; +import { Button, Modal } from "@repo/timo-design-system/ui"; import { cn } from "@repo/timo-design-system/utils"; import { useTranslations } from "next-intl"; @@ -115,27 +115,21 @@ export const TimerExtendModalPanel = ({
{canGoBack ? ( - {t("closeButton")} - + ) : ( - + {t("closeButton")} )} - + {t("submitButton")}
diff --git a/apps/timo-web/components/timer/TimerStopModalPanel.tsx b/apps/timo-web/components/timer/TimerStopModalPanel.tsx index 25f7c584..fd0d247a 100644 --- a/apps/timo-web/components/timer/TimerStopModalPanel.tsx +++ b/apps/timo-web/components/timer/TimerStopModalPanel.tsx @@ -1,5 +1,5 @@ import timoTimerLogo from "@repo/timo-design-system/assets/images/logo/timo-timer.svg"; -import { Modal, ModalButton } from "@repo/timo-design-system/ui"; +import { Button, Modal } from "@repo/timo-design-system/ui"; import Image from "next/image"; import { useTranslations } from "next-intl"; @@ -37,12 +37,16 @@ export const TimerStopModalPanel = ({ })} - - {t("continueButton")} - - + {t("continueButton")} + ); From ed75430438b5ba6aa03bf79ae4764ac6f3af90b8 Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:22:45 +0900 Subject: [PATCH 53/89] =?UTF-8?q?refactor(ui):=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20ModalButton=C2=B7Crea?= =?UTF-8?q?teButton=20=EC=A0=9C=EA=B1=B0=20(#316)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 공용 Button으로 교체되어 사용처가 없어진 ModalButton과 CreateButton 컴포넌트, 스토리, export를 제거했습니다. --- .../create-button/CreateButton.stories.tsx | 39 ---------------- .../button/create-button/CreateButton.tsx | 29 ------------ .../modal-button/ModalButton.stories.tsx | 46 ------------------- .../button/modal-button/ModalButton.tsx | 36 --------------- .../src/components/index.ts | 2 - 5 files changed, 152 deletions(-) delete mode 100644 packages/timo-design-system/src/components/button/create-button/CreateButton.stories.tsx delete mode 100644 packages/timo-design-system/src/components/button/create-button/CreateButton.tsx delete mode 100644 packages/timo-design-system/src/components/button/modal-button/ModalButton.stories.tsx delete mode 100644 packages/timo-design-system/src/components/button/modal-button/ModalButton.tsx diff --git a/packages/timo-design-system/src/components/button/create-button/CreateButton.stories.tsx b/packages/timo-design-system/src/components/button/create-button/CreateButton.stories.tsx deleted file mode 100644 index 36f12114..00000000 --- a/packages/timo-design-system/src/components/button/create-button/CreateButton.stories.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { CreateButton } from "./CreateButton"; - -import type { Meta, StoryObj } from "@storybook/react"; - -const meta = { - title: "Components/CreateButton", - component: CreateButton, - parameters: { - layout: "centered", - }, - argTypes: { - label: { - control: "text", - description: "버튼에 표시될 텍스트", - table: { - type: { summary: "string" }, - }, - }, - disabled: { - control: "boolean", - description: "버튼 비활성화 여부", - table: { - type: { summary: "boolean" }, - defaultValue: { summary: "false" }, - }, - }, - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { label: "생성하기" }, -}; - -export const Disabled: Story = { - args: { label: "생성하기", disabled: true }, -}; diff --git a/packages/timo-design-system/src/components/button/create-button/CreateButton.tsx b/packages/timo-design-system/src/components/button/create-button/CreateButton.tsx deleted file mode 100644 index 1ae43d93..00000000 --- a/packages/timo-design-system/src/components/button/create-button/CreateButton.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { cn } from "../../../lib"; - -export interface CreateButtonProps { - label: string; - disabled?: boolean; - onClick?: () => void; -} - -export const CreateButton = ({ - label, - disabled = false, - onClick, -}: CreateButtonProps) => { - return ( - - ); -}; diff --git a/packages/timo-design-system/src/components/button/modal-button/ModalButton.stories.tsx b/packages/timo-design-system/src/components/button/modal-button/ModalButton.stories.tsx deleted file mode 100644 index bf575de2..00000000 --- a/packages/timo-design-system/src/components/button/modal-button/ModalButton.stories.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { ModalButton } from "./ModalButton"; - -import type { Meta, StoryObj } from "@storybook/react"; - -const meta = { - title: "Components/Button/ModalButton", - component: ModalButton, - parameters: { - layout: "centered", - }, - argTypes: { - variant: { control: "radio", options: ["fill", "border"] }, - onClick: { action: "clicked" }, - }, - args: { - variant: "fill", - children: "삭제하기", - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const AllStates: Story = { - parameters: { controls: { disable: true } }, - render: () => ( -
- 돌아가기 - 삭제하기 -
- ), -}; - -export const CustomWidth: Story = { - parameters: { controls: { disable: true } }, - render: () => ( -
- - 돌아가기 - - - 삭제하기 - -
- ), -}; diff --git a/packages/timo-design-system/src/components/button/modal-button/ModalButton.tsx b/packages/timo-design-system/src/components/button/modal-button/ModalButton.tsx deleted file mode 100644 index 22cb7e31..00000000 --- a/packages/timo-design-system/src/components/button/modal-button/ModalButton.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { cn } from "../../../lib"; - -import type { ButtonHTMLAttributes } from "react"; - -export type ModalButtonVariantTypes = "fill" | "border"; - -const MODAL_BUTTON_VARIANT: Record = { - fill: "bg-timo-blue-300 text-white focus-visible:ring-timo-blue-300 focus-visible:ring-2 focus-visible:ring-offset-2", - border: - "border border-timo-gray-500 text-timo-black focus-visible:border-timo-blue-300", -}; - -export interface ModalButtonProps extends ButtonHTMLAttributes { - variant: ModalButtonVariantTypes; -} - -export const ModalButton = ({ - variant, - className, - children, - ...rest -}: ModalButtonProps) => { - return ( - - ); -}; diff --git a/packages/timo-design-system/src/components/index.ts b/packages/timo-design-system/src/components/index.ts index 4e7cd294..b1120afc 100644 --- a/packages/timo-design-system/src/components/index.ts +++ b/packages/timo-design-system/src/components/index.ts @@ -10,7 +10,6 @@ export { PriorityIcon } from "./priority/priority-icon/PriorityIcon"; export { PrioritySelector } from "./priority/priority-selector/PrioritySelector"; export type { PriorityLevel } from "./priority/priority-selector/PrioritySelector"; export { Button } from "./button/button/Button"; -export { CreateButton } from "./button/create-button/CreateButton"; export { DeleteButton } from "./button/delete-button/DeleteButton"; export { AddTaskButton } from "./button/add-task-button/AddTaskButton"; export { TodayBadge } from "./badge/today-badge/TodayBadge"; @@ -19,7 +18,6 @@ export type { TogglePanelOption } from "./toggle-panel/TogglePanel"; export { PlayButton } from "./button/play-button/PlayButton"; export { TabButton } from "./button/tab-button/TabButton"; export { Toast } from "./toast/Toast"; -export { ModalButton } from "./button/modal-button/ModalButton"; export { Modal } from "./layout/modal/Modal"; export { SidebarButton } from "./button/sidebar-button/SidebarButton"; export { TodayButton } from "./button/today-button/TodayButton"; From 0fc3298bc9ee2d50dc7ee97c7f6299ac7089f0c0 Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:43:40 +0900 Subject: [PATCH 54/89] =?UTF-8?q?feat(ui):=20Button=20secondary=20fill?= =?UTF-8?q?=EC=97=90=20=EC=84=A0=ED=83=9D(aria-pressed)=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=B6=94=EA=B0=80=20(#316)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit secondary fill Button에 aria-pressed일 때 흰 배경과 파란 테두리로 바뀌는 선택 스타일과 Pressed 스토리를 추가했습니다. --- .../button/button/Button.stories.tsx | 43 +++++++++++++++++++ .../src/components/button/button/Button.tsx | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/timo-design-system/src/components/button/button/Button.stories.tsx b/packages/timo-design-system/src/components/button/button/Button.stories.tsx index 240eec39..d542ac72 100644 --- a/packages/timo-design-system/src/components/button/button/Button.stories.tsx +++ b/packages/timo-design-system/src/components/button/button/Button.stories.tsx @@ -144,6 +144,49 @@ export const WithRightIcon: Story = { ), }; +export const Pressed: Story = { + parameters: { controls: { disable: true } }, + render: () => ( +
+ + + + +
+ ), +}; + export const Layout: Story = { parameters: { controls: { disable: true } }, render: () => ( diff --git a/packages/timo-design-system/src/components/button/button/Button.tsx b/packages/timo-design-system/src/components/button/button/Button.tsx index 04c81eb8..581e2d7b 100644 --- a/packages/timo-design-system/src/components/button/button/Button.tsx +++ b/packages/timo-design-system/src/components/button/button/Button.tsx @@ -17,7 +17,7 @@ const BUTTON_VARIANTS: Record< "border-timo-blue-300 text-timo-blue-300 border bg-white disabled:border-timo-gray-500 disabled:text-timo-gray-700", }, secondary: { - fill: "bg-timo-gray-300 text-timo-gray-900 disabled:text-timo-gray-700", + fill: "bg-timo-gray-300 text-timo-gray-900 disabled:text-timo-gray-700 aria-pressed:border aria-pressed:border-timo-blue-300 aria-pressed:bg-white", outline: "border-timo-gray-500 text-timo-gray-900 border bg-white disabled:text-timo-gray-700", }, From bc1471128ba3060b9686243152df3a1742c1ba55 Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:43:41 +0900 Subject: [PATCH 55/89] =?UTF-8?q?refactor(web):=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=AA=A8=EB=8B=AC=20=ED=94=84=EB=A6=AC?= =?UTF-8?q?=EC=85=8B=20=EC=B9=A9=EC=9D=84=20=EA=B3=B5=EC=9A=A9=20Button?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EA=B5=90=EC=B2=B4=20(#316)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 시간 추가 모달의 프리셋·직접입력 칩을 공용 Button(secondary fill m)으로 교체하고, 직접입력 선택 시 입력창에 바로 포커스되며 선택 상태를 파란 테두리로 유지하도록 했습니다. --- .../timer/TimerExtendModalPanel.tsx | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/apps/timo-web/components/timer/TimerExtendModalPanel.tsx b/apps/timo-web/components/timer/TimerExtendModalPanel.tsx index 289ececf..76b522d1 100644 --- a/apps/timo-web/components/timer/TimerExtendModalPanel.tsx +++ b/apps/timo-web/components/timer/TimerExtendModalPanel.tsx @@ -1,5 +1,4 @@ import { Button, Modal } from "@repo/timo-design-system/ui"; -import { cn } from "@repo/timo-design-system/utils"; import { useTranslations } from "next-intl"; import { extendTimerBodyExtendMinutesMax } from "@/generated/endpoints/timer/timer.zod"; @@ -20,8 +19,10 @@ const PRESET_OPTIONS: { { preset: 60, labelKey: "presetOneHour" }, ]; -const CHIP_FOCUS_BORDER_CLASS = - "border-2 border-transparent focus-visible:border-timo-blue-300 outline-none"; +// JSX 안에 인라인으로 두면 리렌더마다 다시 포커스되므로 컴포넌트 밖에 둔다 +const focusOnMount = (element: HTMLInputElement | null) => { + element?.focus(); +}; export interface TimerExtendModalPanelProps { selectedPreset: ExtendTimePreset | null; @@ -54,26 +55,23 @@ export const TimerExtendModalPanel = ({
{PRESET_OPTIONS.map(({ preset, labelKey }) => ( - + ))} {isCustomSelected ? ( -
+
) : ( - + )}
From 6c2bfd92fa504e66fbda838875cb031e5bb58566 Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:54:53 +0900 Subject: [PATCH 56/89] =?UTF-8?q?fix(web):=20=EC=A7=81=EC=A0=91=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EB=B0=95=EC=8A=A4=EC=97=90=20=ED=8F=AC=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=20=EB=A7=81=20=EC=B6=94=EA=B0=80=20(#316)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 테두리가 항상 파란색이 되면서 사라진 포커스 표시를 Button과 같은 포커스 링으로 보완했습니다. --- apps/timo-web/components/timer/TimerExtendModalPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/timo-web/components/timer/TimerExtendModalPanel.tsx b/apps/timo-web/components/timer/TimerExtendModalPanel.tsx index 76b522d1..bcbce60b 100644 --- a/apps/timo-web/components/timer/TimerExtendModalPanel.tsx +++ b/apps/timo-web/components/timer/TimerExtendModalPanel.tsx @@ -69,7 +69,7 @@ export const TimerExtendModalPanel = ({ ))} {isCustomSelected ? ( -
+
Date: Wed, 7 Oct 2026 15:02:00 +0900 Subject: [PATCH 57/89] =?UTF-8?q?feat(web):=20=EC=98=A4=EB=8A=98=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=97=AC=EB=B0=B1=20?= =?UTF-8?q?=EB=B0=8F=20=EB=84=88=EB=B9=84=20=EC=A1=B0=EC=A0=95=20(#318)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모바일에서 가로 스크롤이 생기지 않도록 오늘 페이지 여백을 px-5로 줄이고 최소 너비와 가로 스크롤은 데스크톱에서만 적용했습니다 - 캘린더 일정 카드가 모바일 화면 폭에 맞도록 최소 너비를 데스크톱에서만 적용하고 모바일 여백을 줄였습니다 --- .../app/[locale]/(main)/(with-time-sidebar)/today/page.tsx | 4 ++-- apps/timo-web/components/calendar/TodayCalendarEventCard.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx index 4fffe122..1b06d043 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx @@ -6,8 +6,8 @@ export default function TodayPage() { return (
-
-
+
+
diff --git a/apps/timo-web/components/calendar/TodayCalendarEventCard.tsx b/apps/timo-web/components/calendar/TodayCalendarEventCard.tsx index 5a5d1573..c0662370 100644 --- a/apps/timo-web/components/calendar/TodayCalendarEventCard.tsx +++ b/apps/timo-web/components/calendar/TodayCalendarEventCard.tsx @@ -9,7 +9,7 @@ interface TodayCalendarEventCardProps { export const TodayCalendarEventCard = ({ title, }: TodayCalendarEventCardProps) => ( -
+
Date: Wed, 7 Oct 2026 15:02:02 +0900 Subject: [PATCH 58/89] =?UTF-8?q?feat(web):=20=EC=98=A4=EB=8A=98=20?= =?UTF-8?q?=ED=88=AC=EB=91=90=20=EC=B9=B4=EB=93=9C=20=ED=88=B4=EB=B0=94?= =?UTF-8?q?=EB=A5=BC=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20=EB=8C=80=EC=9D=91=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#318)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 보여주기용으로만 쓰던 TodoToolbar를 정적인 툴바 한 줄로 바꿔 모바일과 데스크톱을 md 클래스로 분기했습니다 - 모바일에서는 홈 투두 카드처럼 좌측에 날짜·우선순위·태그·메모·반복, 우측에 시간이 보이도록 했습니다 - 데스크톱에서는 피그마에 맞게 우선순위를 22px 영역으로 감싸고 아이콘과 글자 간격을 통일했습니다 - 카드가 모바일 화면 폭에 맞도록 최소 너비를 데스크톱에서만 적용하고 모바일 여백을 줄였습니다 - 더 이상 쓰지 않는 toolbar.dateValue를 제거했습니다 --- .../today/_components/TodayTodoCard.tsx | 109 +++++++++++++----- .../_containers/TodayTodoListContainer.tsx | 1 - 2 files changed, 80 insertions(+), 30 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx index 920d018f..3c09dc7b 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx @@ -1,12 +1,21 @@ import { + CalendarDisableIcon, + CalendarOnIcon, + ClockDisableIcon, + ClockOnIcon, + MemoDisableIcon, + MemoOnIcon, PlayDisabledIcon, PlayIcon, + RepeatDisableIcon, + RepeatOnIcon, StopIcon, } from "@repo/timo-design-system/icons"; import { Checkbox, PlayButton, - TodoToolbar, + PriorityIcon, + TagIcon, type PriorityLevel, } from "@repo/timo-design-system/ui"; import { cn } from "@repo/timo-design-system/utils"; @@ -19,14 +28,18 @@ const CARD_STYLE = { card: "bg-white", title: "text-timo-gray-900", subText: "text-timo-gray-700", + toolbarText: "text-timo-gray-900", }, done: { card: "bg-timo-gray-200", title: "text-timo-gray-700", subText: "text-timo-gray-700", + toolbarText: "text-timo-gray-700", }, } as const; +const TOOLBAR_ICON_CLASS_NAME = "size-[18px] shrink-0 md:size-[22px]"; + export interface SubTodo { id: number; text: string; @@ -35,7 +48,6 @@ export interface SubTodo { export interface TodayTodoCardToolbar { date: string; - dateValue: Date; time: string; priority?: PriorityLevel; tag?: string; @@ -98,7 +110,7 @@ export const TodayTodoCard = ({ onClick={onCardClick} onKeyDown={onCardClick ? handleCardKeyDown : undefined} className={cn( - "border-timo-gray-500 flex min-w-80 flex-col gap-1 rounded-[4px] border px-5 py-4", + "border-timo-gray-500 flex w-full flex-col gap-1 overflow-hidden rounded-[4px] border px-3.5 py-3 md:min-w-80 md:px-5 md:py-4", style.card, onCardClick && "cursor-pointer", )} @@ -173,33 +185,72 @@ export const TodayTodoCard = ({ )} -
-
- +
+
+ + {isDimmed ? ( + + ) : ( + + )} + + {toolbar.date} + + + + {isDimmed ? : } + + {toolbar.time} + + + + + + + + + + {isDimmed || !toolbar.hasMemo ? ( + + ) : ( + + )} + + + {isDimmed || !toolbar.hasRepeat ? ( + + ) : ( + + )} +
+ + {toolbar.time} +
); diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx index 94a4a295..7d544a7e 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx @@ -139,7 +139,6 @@ export const TodayTodoListContainer = () => { date: formatShortDateLabel( parseDateKey(todo.date) ?? new Date(), ), - dateValue: parseDateKey(todo.date) ?? new Date(), time: convertDurationToTimeText(durationSeconds), priority: todo.priority, tag: todo.tag ? getTagLabel(todo.tag) : undefined, From 0858283e29f908204bb8c9d2ffd4a8695423538c Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:17:58 +0900 Subject: [PATCH 59/89] =?UTF-8?q?refactor(web):=20=EC=98=A4=EB=8A=98=20?= =?UTF-8?q?=ED=88=AC=EB=91=90=20=EC=B9=B4=EB=93=9C=20=ED=88=B4=EB=B0=94=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=91=9C=EA=B8=B0=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC=20=EB=B0=8F=20=EC=9A=B0=EC=84=A0=EC=88=9C=EC=9C=84=20?= =?UTF-8?q?=EB=9D=BC=EB=B2=A8=20=EC=B6=94=EA=B0=80=20(#318)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 아이콘 크기 클래스를 다른 코드와 같이 Tailwind 단위(size-4.5, size-5.5)로 통일했습니다 - 스크린리더가 우선순위를 읽을 수 있도록 홈 투두 카드와 같이 우선순위 라벨을 추가했습니다 --- .../today/_components/TodayTodoCard.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx index 3c09dc7b..f222083d 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx @@ -38,7 +38,7 @@ const CARD_STYLE = { }, } as const; -const TOOLBAR_ICON_CLASS_NAME = "size-[18px] shrink-0 md:size-[22px]"; +const TOOLBAR_ICON_CLASS_NAME = "size-4.5 shrink-0 md:size-5.5"; export interface SubTodo { id: number; @@ -92,8 +92,13 @@ export const TodayTodoCard = ({ onMouseLeave, }: TodayTodoCardProps) => { const t = useTranslations("Home.createModal"); + const tCommon = useTranslations("Common"); const style = CARD_STYLE[isDimmed ? "done" : "active"]; + const priorityLabel = toolbar.priority + ? tCommon(`priority.${toolbar.priority}`) + : undefined; + const handleCardKeyDown = (e: KeyboardEvent) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); @@ -215,7 +220,7 @@ export const TodayTodoCard = ({ @@ -223,6 +228,7 @@ export const TodayTodoCard = ({ priority={ isDimmed || !toolbar.priority ? "Disable" : toolbar.priority } + label={priorityLabel} /> From d9210ce1460abadd66e0293c8153b04325a989c1 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Tue, 6 Oct 2026 19:59:22 +0900 Subject: [PATCH 60/89] =?UTF-8?q?refactor(web):=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B2=84=ED=8A=BC=EC=9D=84=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=20Button=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=EB=A1=9C=20=EA=B5=90=EC=B2=B4=20(#315)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 캘린더 연동·해지 버튼을 공통 Button으로 바꾸고, 연결 여부에 따라 primary와 secondary를 나눴습니다 - 로그아웃 버튼을 secondary fill로 바꿨습니다 - 탈퇴하기 버튼의 직접 작성한 button 요소를 secondary fill 공통 Button으로 교체했습니다 - 버튼 너비를 80px로 고정하고, 캘린더 연동 행의 고정 높이를 없애 내용물 높이를 따르도록 했습니다 - 캘린더 아이콘 래퍼를 24px 박스와 4px 패딩으로 맞췄습니다 Co-Authored-By: Claude Sonnet 5 --- .../account/SettingsProfileView.tsx | 27 +++++++++++-------- .../withdrawal/SettingsWithdrawalView.tsx | 11 +++++--- .../account/SettingsLogoutModalContainer.tsx | 11 +++++--- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsProfileView.tsx b/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsProfileView.tsx index a5e8f4a4..09331135 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsProfileView.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsProfileView.tsx @@ -1,4 +1,4 @@ -import { PillButton } from "@repo/timo-design-system/ui"; +import { Button } from "@repo/timo-design-system/ui"; import Image from "next/image"; import type { @@ -88,25 +88,30 @@ export const SettingsProfileView = ({

{labels.calendarSection}

-
+
- +
+ +
Google Calendar
- {isCalendarConnected ? labels.disconnect : labels.connect} - +
diff --git a/apps/timo-web/app/[locale]/(main)/settings/_components/withdrawal/SettingsWithdrawalView.tsx b/apps/timo-web/app/[locale]/(main)/settings/_components/withdrawal/SettingsWithdrawalView.tsx index 5e0b1c8f..7d858a1a 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_components/withdrawal/SettingsWithdrawalView.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_components/withdrawal/SettingsWithdrawalView.tsx @@ -1,4 +1,5 @@ import { WarningGrayIcon } from "@repo/timo-design-system/icons"; +import { Button } from "@repo/timo-design-system/ui"; import type { SettingsWithdrawalLabels } from "@/app/[locale]/(main)/settings/_types/withdrawal/withdrawal-type"; @@ -42,13 +43,15 @@ export const SettingsWithdrawalView = ({
- +
); diff --git a/apps/timo-web/app/[locale]/(main)/settings/_containers/account/SettingsLogoutModalContainer.tsx b/apps/timo-web/app/[locale]/(main)/settings/_containers/account/SettingsLogoutModalContainer.tsx index 8fd210fc..31e5bea4 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_containers/account/SettingsLogoutModalContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_containers/account/SettingsLogoutModalContainer.tsx @@ -1,7 +1,7 @@ "use client"; import timoTimerLogo from "@repo/timo-design-system/assets/images/logo/timo-timer.svg"; -import { Modal, PillButton } from "@repo/timo-design-system/ui"; +import { Button, Modal } from "@repo/timo-design-system/ui"; import Image from "next/image"; import { useTranslations } from "next-intl"; import { useRef } from "react"; @@ -22,12 +22,15 @@ export const SettingsLogoutModalContainer = ({ return ( - modalTriggerRef.current?.click()} - className="w-fit" + className="w-20" > {labels.logout} - + Date: Wed, 7 Oct 2026 17:24:03 +0900 Subject: [PATCH 61/89] =?UTF-8?q?[CHORE]=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/launch.json | 17 +++++++++++++++++ .../components/timer/TimerExtendModalPanel.tsx | 1 - 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 .claude/launch.json diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 00000000..984cfc58 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,17 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "storybook", + "runtimeExecutable": "pnpm", + "runtimeArgs": ["storybook"], + "port": 6006 + }, + { + "name": "web", + "runtimeExecutable": "pnpm", + "runtimeArgs": ["dev:web"], + "port": 3000 + } + ] +} diff --git a/apps/timo-web/components/timer/TimerExtendModalPanel.tsx b/apps/timo-web/components/timer/TimerExtendModalPanel.tsx index bcbce60b..ca344fbd 100644 --- a/apps/timo-web/components/timer/TimerExtendModalPanel.tsx +++ b/apps/timo-web/components/timer/TimerExtendModalPanel.tsx @@ -19,7 +19,6 @@ const PRESET_OPTIONS: { { preset: 60, labelKey: "presetOneHour" }, ]; -// JSX 안에 인라인으로 두면 리렌더마다 다시 포커스되므로 컴포넌트 밖에 둔다 const focusOnMount = (element: HTMLInputElement | null) => { element?.focus(); }; From 48a6c8f04277a0371eb4318a85587ac912feb051 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Wed, 7 Oct 2026 18:01:08 +0900 Subject: [PATCH 62/89] =?UTF-8?q?refactor(ui):=20=ED=83=80=EC=9D=B4?= =?UTF-8?q?=EB=A8=B8=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=EC=9D=84=20=EC=83=81=ED=83=9C=EB=B3=84(active/default?= =?UTF-8?q?/disabled)=20=EC=84=B8=ED=8A=B8=EB=A1=9C=20=EA=B5=90=EC=B2=B4?= =?UTF-8?q?=20(#315)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 재생·일시정지·종료·시간 연장 아이콘을 control-play/pause/end/plus 세트로 추가했습니다 - 각 세트는 active(파란색)·default(다크)·disabled(회색) 3가지 상태를 가집니다 - 기존 end-black·end-blue·play·stop 아이콘 원본을 제거했습니다 - PlayButton 스토리의 아이콘 참조를 새 세트로 교체했습니다 Co-Authored-By: Claude Sonnet 5 --- .../button/play-button/PlayButton.stories.tsx | 16 ++++++++-------- .../src/icons/source/control-end-active.svg | 3 +++ .../src/icons/source/control-end-default.svg | 3 +++ .../src/icons/source/control-end-disabled.svg | 3 +++ .../src/icons/source/control-pause-active.svg | 3 +++ .../src/icons/source/control-pause-default.svg | 3 +++ .../src/icons/source/control-pause-disabled.svg | 3 +++ .../src/icons/source/control-play-active.svg | 3 +++ .../src/icons/source/control-play-default.svg | 3 +++ .../src/icons/source/control-play-disabled.svg | 3 +++ .../src/icons/source/control-plus-active.svg | 3 +++ .../src/icons/source/control-plus-default.svg | 3 +++ .../src/icons/source/control-plus-disabled.svg | 3 +++ .../src/icons/source/end-black.svg | 3 --- .../src/icons/source/end-blue.svg | 3 --- .../timo-design-system/src/icons/source/play.svg | 3 --- .../timo-design-system/src/icons/source/stop.svg | 3 --- 17 files changed, 44 insertions(+), 20 deletions(-) create mode 100644 packages/timo-design-system/src/icons/source/control-end-active.svg create mode 100644 packages/timo-design-system/src/icons/source/control-end-default.svg create mode 100644 packages/timo-design-system/src/icons/source/control-end-disabled.svg create mode 100644 packages/timo-design-system/src/icons/source/control-pause-active.svg create mode 100644 packages/timo-design-system/src/icons/source/control-pause-default.svg create mode 100644 packages/timo-design-system/src/icons/source/control-pause-disabled.svg create mode 100644 packages/timo-design-system/src/icons/source/control-play-active.svg create mode 100644 packages/timo-design-system/src/icons/source/control-play-default.svg create mode 100644 packages/timo-design-system/src/icons/source/control-play-disabled.svg create mode 100644 packages/timo-design-system/src/icons/source/control-plus-active.svg create mode 100644 packages/timo-design-system/src/icons/source/control-plus-default.svg create mode 100644 packages/timo-design-system/src/icons/source/control-plus-disabled.svg delete mode 100644 packages/timo-design-system/src/icons/source/end-black.svg delete mode 100644 packages/timo-design-system/src/icons/source/end-blue.svg delete mode 100644 packages/timo-design-system/src/icons/source/play.svg delete mode 100644 packages/timo-design-system/src/icons/source/stop.svg diff --git a/packages/timo-design-system/src/components/button/play-button/PlayButton.stories.tsx b/packages/timo-design-system/src/components/button/play-button/PlayButton.stories.tsx index 8844b3b6..4219b250 100644 --- a/packages/timo-design-system/src/components/button/play-button/PlayButton.stories.tsx +++ b/packages/timo-design-system/src/components/button/play-button/PlayButton.stories.tsx @@ -1,7 +1,7 @@ import { PlayButton } from "./PlayButton"; -import { PlayIcon } from "../../../icons/generated/Play"; +import { ControlPauseActiveIcon } from "../../../icons/generated/ControlPauseActive"; +import { ControlPlayActiveIcon } from "../../../icons/generated/ControlPlayActive"; import { PlayDisabledIcon } from "../../../icons/generated/PlayDisabled"; -import { StopIcon } from "../../../icons/generated/Stop"; import type { Meta, StoryObj } from "@storybook/react"; @@ -47,7 +47,7 @@ export const Play: Story = { args: { variant: "play", size: "sm" }, render: (args) => ( - @@ -59,7 +59,7 @@ export const Stop: Story = { args: { variant: "stop", size: "sm" }, render: (args) => ( - @@ -86,10 +86,10 @@ export const AllVariants: Story = {
- + - + @@ -97,10 +97,10 @@ export const AllVariants: Story = {
- + - + diff --git a/packages/timo-design-system/src/icons/source/control-end-active.svg b/packages/timo-design-system/src/icons/source/control-end-active.svg new file mode 100644 index 00000000..6fce9b56 --- /dev/null +++ b/packages/timo-design-system/src/icons/source/control-end-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/timo-design-system/src/icons/source/control-end-default.svg b/packages/timo-design-system/src/icons/source/control-end-default.svg new file mode 100644 index 00000000..c168f406 --- /dev/null +++ b/packages/timo-design-system/src/icons/source/control-end-default.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/timo-design-system/src/icons/source/control-end-disabled.svg b/packages/timo-design-system/src/icons/source/control-end-disabled.svg new file mode 100644 index 00000000..eb7af556 --- /dev/null +++ b/packages/timo-design-system/src/icons/source/control-end-disabled.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/timo-design-system/src/icons/source/control-pause-active.svg b/packages/timo-design-system/src/icons/source/control-pause-active.svg new file mode 100644 index 00000000..7b34c27d --- /dev/null +++ b/packages/timo-design-system/src/icons/source/control-pause-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/timo-design-system/src/icons/source/control-pause-default.svg b/packages/timo-design-system/src/icons/source/control-pause-default.svg new file mode 100644 index 00000000..56640cb8 --- /dev/null +++ b/packages/timo-design-system/src/icons/source/control-pause-default.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/timo-design-system/src/icons/source/control-pause-disabled.svg b/packages/timo-design-system/src/icons/source/control-pause-disabled.svg new file mode 100644 index 00000000..b08b025e --- /dev/null +++ b/packages/timo-design-system/src/icons/source/control-pause-disabled.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/timo-design-system/src/icons/source/control-play-active.svg b/packages/timo-design-system/src/icons/source/control-play-active.svg new file mode 100644 index 00000000..a8c19986 --- /dev/null +++ b/packages/timo-design-system/src/icons/source/control-play-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/timo-design-system/src/icons/source/control-play-default.svg b/packages/timo-design-system/src/icons/source/control-play-default.svg new file mode 100644 index 00000000..66dd3235 --- /dev/null +++ b/packages/timo-design-system/src/icons/source/control-play-default.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/timo-design-system/src/icons/source/control-play-disabled.svg b/packages/timo-design-system/src/icons/source/control-play-disabled.svg new file mode 100644 index 00000000..1291fc7a --- /dev/null +++ b/packages/timo-design-system/src/icons/source/control-play-disabled.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/timo-design-system/src/icons/source/control-plus-active.svg b/packages/timo-design-system/src/icons/source/control-plus-active.svg new file mode 100644 index 00000000..1f83e6cb --- /dev/null +++ b/packages/timo-design-system/src/icons/source/control-plus-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/timo-design-system/src/icons/source/control-plus-default.svg b/packages/timo-design-system/src/icons/source/control-plus-default.svg new file mode 100644 index 00000000..bbdec7d0 --- /dev/null +++ b/packages/timo-design-system/src/icons/source/control-plus-default.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/timo-design-system/src/icons/source/control-plus-disabled.svg b/packages/timo-design-system/src/icons/source/control-plus-disabled.svg new file mode 100644 index 00000000..da29df39 --- /dev/null +++ b/packages/timo-design-system/src/icons/source/control-plus-disabled.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/timo-design-system/src/icons/source/end-black.svg b/packages/timo-design-system/src/icons/source/end-black.svg deleted file mode 100644 index 4cc69b64..00000000 --- a/packages/timo-design-system/src/icons/source/end-black.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/timo-design-system/src/icons/source/end-blue.svg b/packages/timo-design-system/src/icons/source/end-blue.svg deleted file mode 100644 index c538db3c..00000000 --- a/packages/timo-design-system/src/icons/source/end-blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/timo-design-system/src/icons/source/play.svg b/packages/timo-design-system/src/icons/source/play.svg deleted file mode 100644 index 3037e424..00000000 --- a/packages/timo-design-system/src/icons/source/play.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/timo-design-system/src/icons/source/stop.svg b/packages/timo-design-system/src/icons/source/stop.svg deleted file mode 100644 index 64ebe72f..00000000 --- a/packages/timo-design-system/src/icons/source/stop.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - From 97df4117bc9a1027799a542f5d673f11ef885ba0 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Wed, 7 Oct 2026 18:01:23 +0900 Subject: [PATCH 63/89] =?UTF-8?q?refactor(web):=20=EC=9E=AC=EC=83=9D=C2=B7?= =?UTF-8?q?=EC=9D=BC=EC=8B=9C=EC=A0=95=EC=A7=80=C2=B7=EC=A2=85=EB=A3=8C=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=95=84=EC=9D=B4=EC=BD=98=EC=9D=84=20?= =?UTF-8?q?=EC=83=88=20control=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=EB=A1=9C=20=EA=B5=90=EC=B2=B4=20(#315)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 투두 카드(Home·Today)와 집중 화면, 상세 모달의 재생/일시정지 아이콘을 ControlPlay/ControlPause 세트로 교체했습니다 - 타이머 컨트롤의 재생·일시정지 버튼을 재생 중 여부에 따라 default/active/disabled 아이콘으로 분기하고, 종료 버튼도 같은 패턴의 ControlEnd 세트로 교체했습니다 - TimerControlButton에 disabledIcon prop을 추가해 비활성 상태에서도 전용 아이콘이 보이도록 했습니다 - 재생 중(variant="active") 상태의 아이콘을 배경색(파란 톤)에 맞춰 active 아이콘으로 수정했습니다 Co-Authored-By: Claude Sonnet 5 --- .../_components/todo-card/HomeTodoCard.tsx | 8 +-- .../today/_components/TodayTodoCard.tsx | 8 +-- .../focus/_components/FocusTaskItem.tsx | 8 +-- .../components/timer/TimerControlButton.tsx | 6 +- .../components/timer/TimerControls.tsx | 71 ++++++++++++++----- .../detail/DetailTodoTaskFields.tsx | 8 +-- 6 files changed, 73 insertions(+), 36 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx index 0ce550c2..498fbbce 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx @@ -1,13 +1,13 @@ import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { + ControlPauseActiveIcon, + ControlPlayActiveIcon, MemoDisableIcon, MemoOnIcon, PlayDisabledIcon, - PlayIcon, RepeatTodoDisableIcon, RepeatTodoOnIcon, - StopIcon, } from "@repo/timo-design-system/icons"; import { Checkbox, @@ -142,9 +142,9 @@ export const HomeTodoCard = ({ {isCompleted ? ( ) : isRunning ? ( - + ) : isPlayHighlighted ? ( - + ) : ( )} diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx index 920d018f..58ae9671 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_components/TodayTodoCard.tsx @@ -1,7 +1,7 @@ import { + ControlPauseActiveIcon, + ControlPlayActiveIcon, PlayDisabledIcon, - PlayIcon, - StopIcon, } from "@repo/timo-design-system/icons"; import { Checkbox, @@ -135,9 +135,9 @@ export const TodayTodoCard = ({ {isDone ? ( ) : isPlaying ? ( - + ) : isPlayHighlighted ? ( - + ) : ( )} diff --git a/apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskItem.tsx b/apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskItem.tsx index ba02e4c0..17db9059 100644 --- a/apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskItem.tsx +++ b/apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskItem.tsx @@ -1,8 +1,8 @@ import { CalendarOnIcon, ClockOnIcon, - PlayIcon, - StopIcon, + ControlPauseActiveIcon, + ControlPlayActiveIcon, } from "@repo/timo-design-system/icons"; import { Checkbox, PlayButton } from "@repo/timo-design-system/ui"; import { cn } from "@repo/timo-design-system/utils"; @@ -77,9 +77,9 @@ export const FocusTaskItem = ({ onClick={onTogglePlay} > {isRunning ? ( - + ) : ( - + )}
diff --git a/apps/timo-web/components/timer/TimerControlButton.tsx b/apps/timo-web/components/timer/TimerControlButton.tsx index 10197e89..d6a8a904 100644 --- a/apps/timo-web/components/timer/TimerControlButton.tsx +++ b/apps/timo-web/components/timer/TimerControlButton.tsx @@ -7,6 +7,7 @@ export type TimerControlButtonVariant = "default" | "active"; export interface TimerControlButtonProps { icon: ReactNode; activeIcon?: ReactNode; + disabledIcon?: ReactNode; label: string; variant?: TimerControlButtonVariant; onClick?: MouseEventHandler; @@ -16,6 +17,7 @@ export interface TimerControlButtonProps { export const TimerControlButton = ({ icon, activeIcon, + disabledIcon, label, variant = "default", onClick, @@ -38,7 +40,9 @@ export const TimerControlButton = ({ : "bg-timo-gray-300", )} > - {isForcedActive || !activeIcon ? ( + {disabled && disabledIcon ? ( + disabledIcon + ) : isForcedActive || !activeIcon ? ( icon ) : ( <> diff --git a/apps/timo-web/components/timer/TimerControls.tsx b/apps/timo-web/components/timer/TimerControls.tsx index 10137a19..c654ae96 100644 --- a/apps/timo-web/components/timer/TimerControls.tsx +++ b/apps/timo-web/components/timer/TimerControls.tsx @@ -1,10 +1,15 @@ import { - EndBlackIcon, - EndBlueIcon, - PlayTimerIcon, - PlusBlueIcon, - PlusIcon, - StopIcon, + ControlEndActiveIcon, + ControlEndDefaultIcon, + ControlEndDisabledIcon, + ControlPauseActiveIcon, + ControlPauseDisabledIcon, + ControlPlayActiveIcon, + ControlPlayDefaultIcon, + ControlPlayDisabledIcon, + ControlPlusActiveIcon, + ControlPlusDefaultIcon, + ControlPlusDisabledIcon, } from "@repo/timo-design-system/icons"; import { Modal } from "@repo/timo-design-system/ui"; import { useTranslations } from "next-intl"; @@ -39,17 +44,39 @@ export const TimerControls = ({ disabled={disabled} className={MODAL_TRIGGER_CLASS} > - - - - - - + {disabled ? ( + + ) : ( + <> + + + + + + + + )} : + isRunning ? ( + + ) : ( + + ) + } + activeIcon={ + isRunning ? undefined : ( + + ) + } + disabledIcon={ + isRunning ? ( + + ) : ( + + ) } label={isRunning ? t("pause") : t("play")} variant={isRunning ? "active" : "default"} @@ -63,12 +90,18 @@ export const TimerControls = ({ disabled={disabled} className={MODAL_TRIGGER_CLASS} > - - - - - - + {disabled ? ( + + ) : ( + <> + + + + + + + + )}
); diff --git a/apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx b/apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx index 3ef108b6..b916fa37 100644 --- a/apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx +++ b/apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx @@ -1,7 +1,7 @@ import { + ControlPauseActiveIcon, + ControlPlayActiveIcon, PlayDisabledIcon, - PlayIcon, - StopIcon, } from "@repo/timo-design-system/icons"; import { Checkbox, PlayButton } from "@repo/timo-design-system/ui"; @@ -105,9 +105,9 @@ export const DetailTodoTaskFields = ({ {isCompleted ? ( ) : isRunning ? ( - + ) : isPlayHighlighted ? ( - + ) : ( )} From 1613e9f4fe546e1ab63688d52e5c1a99b450a6f1 Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Wed, 7 Oct 2026 18:15:02 +0900 Subject: [PATCH 64/89] =?UTF-8?q?feat(ui):=20=EB=B9=84=ED=99=9C=EC=84=B1?= =?UTF-8?q?=20=EC=9E=91=EC=9D=80=20=EC=98=A4=EB=A5=B8=EC=AA=BD=20=ED=99=94?= =?UTF-8?q?=EC=82=B4=ED=91=9C=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#316)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 온보딩 다음 버튼 비활성 상태에 쓸 #9F9F9F 작은 오른쪽 화살표 아이콘을 추가했습니다. --- .../src/icons/source/chevron-small-right-disable.svg | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 packages/timo-design-system/src/icons/source/chevron-small-right-disable.svg diff --git a/packages/timo-design-system/src/icons/source/chevron-small-right-disable.svg b/packages/timo-design-system/src/icons/source/chevron-small-right-disable.svg new file mode 100644 index 00000000..3f724b29 --- /dev/null +++ b/packages/timo-design-system/src/icons/source/chevron-small-right-disable.svg @@ -0,0 +1,3 @@ + + + From 38d26612723a26db3a01114ac04d680593395993 Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Wed, 7 Oct 2026 18:15:03 +0900 Subject: [PATCH 65/89] =?UTF-8?q?refactor(web):=20=EC=98=A8=EB=B3=B4?= =?UTF-8?q?=EB=94=A9=20=EC=9D=B4=EC=A0=84=C2=B7=EB=8B=A4=EC=9D=8C=C2=B7?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=ED=95=98=EA=B8=B0=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=EC=9D=84=20=EA=B3=B5=EC=9A=A9=20Button=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EA=B5=90=EC=B2=B4=20(#316)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 온보딩 이전(secondary 왼쪽 아이콘), 다음(활성 primary·비활성 secondary 오른쪽 아이콘), 시작하기(primary fill) 버튼을 공용 Button(m)으로 교체했습니다. 활성 조건은 기존과 동일하게 유지했습니다. --- .../_components/OnboardingButton.tsx | 91 ++++++++----------- 1 file changed, 37 insertions(+), 54 deletions(-) diff --git a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsx b/apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsx index 9c6a6a14..ef4cd131 100644 --- a/apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsx +++ b/apps/timo-web/app/[locale]/onboarding/_components/OnboardingButton.tsx @@ -1,9 +1,9 @@ import { - ChevronLeftIcon, - ChevronSmallRightIcon, + ChevronSmallLeftIcon, + ChevronSmallRightDisableIcon, ChevronSmallRightWhiteIcon, } from "@repo/timo-design-system/icons"; -import { cn } from "@repo/timo-design-system/utils"; +import { Button } from "@repo/timo-design-system/ui"; interface OnboardingButtonProps { variant: "next" | "prev" | "start"; @@ -22,71 +22,54 @@ export const OnboardingButton = ({ }: OnboardingButtonProps) => { if (variant === "prev") { return ( - + {label} + ); } + const isDisabled = disabled || !isActive; + if (variant === "start") { - // TODO: 원래 start 버튼 (항상 활성 스타일) — CalendarConnect 단계 재활성화 시 아래 주석 해제 후 isActive 분기 제거 - // return ( - // - // ); return ( - + {label} + ); } - const isDisabled = disabled || !isActive; - return ( - + {label} + ); }; From 0138cfedbe00457f9a4651571fa61d1da83167c7 Mon Sep 17 00:00:00 2001 From: hyewon <130418741+ehye1@users.noreply.github.com> Date: Wed, 7 Oct 2026 18:17:11 +0900 Subject: [PATCH 66/89] =?UTF-8?q?chore(root):=20=EB=A1=9C=EC=BB=AC=20?= =?UTF-8?q?=EC=8B=A4=ED=96=89=20=EC=84=A4=EC=A0=95=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20(#316)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 개인 개발 환경에서만 쓰는 .claude/launch.json이 커밋에 포함되어 제거했습니다. --- .claude/launch.json | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .claude/launch.json diff --git a/.claude/launch.json b/.claude/launch.json deleted file mode 100644 index 984cfc58..00000000 --- a/.claude/launch.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "version": "0.0.1", - "configurations": [ - { - "name": "storybook", - "runtimeExecutable": "pnpm", - "runtimeArgs": ["storybook"], - "port": 6006 - }, - { - "name": "web", - "runtimeExecutable": "pnpm", - "runtimeArgs": ["dev:web"], - "port": 3000 - } - ] -} From 6d853d494ed6e4b8b829c14738a0c6a26a545d57 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Wed, 7 Oct 2026 18:27:39 +0900 Subject: [PATCH 67/89] =?UTF-8?q?fix(ui):=20=EB=AA=A8=EB=8B=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=83=9D=20z-index=EA=B0=80=20=EB=8B=AB=EC=9D=84=20=EB=95=8C?= =?UTF-8?q?=20=EB=B0=98=EB=82=A9=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95=20(#320)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모달 스택 인덱스를 세는 카운터가 증가만 하고 반납되지 않아, 중첩되지 않은 단일 모달도 세션이 길어질수록 더 큰 z-index를 받던 문제를 고쳤습니다 - modal-stack-registry에 releaseModalStackIndex를 추가하고, Modal이 닫히거나 언마운트될 때 반납하도록 했습니다 - Modal의 open()이 이미 열린 상태에서 중복 호출돼도 다시 acquire하지 않도록 가드를 추가했습니다 Co-Authored-By: Claude Sonnet 5 --- .../src/components/layout/modal/Modal.tsx | 15 ++++++++++++++- packages/timo-design-system/src/lib/index.ts | 5 ++++- .../src/lib/modal-stack-registry.ts | 12 +++++++++--- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/timo-design-system/src/components/layout/modal/Modal.tsx b/packages/timo-design-system/src/components/layout/modal/Modal.tsx index 4c10ea60..870abcc9 100644 --- a/packages/timo-design-system/src/components/layout/modal/Modal.tsx +++ b/packages/timo-design-system/src/components/layout/modal/Modal.tsx @@ -14,7 +14,12 @@ import { } from "react"; import { createPortal } from "react-dom"; -import { acquireModalStackIndex, cn, hasOpenFloatingLayer } from "../../../lib"; +import { + acquireModalStackIndex, + cn, + hasOpenFloatingLayer, + releaseModalStackIndex, +} from "../../../lib"; import { ModalButton } from "../../button/modal-button/ModalButton"; // tailwind-config theme.css의 --z-index-modal-overlay/--z-index-modal-panel과 동일한 기준값이다. @@ -85,7 +90,15 @@ const ModalRoot = ({ children, className }: ModalProps) => { }; }, [isOpen]); + // 모달이 닫히거나(어떤 경로로 닫히든) 언마운트될 때 스택 인덱스를 반납한다. + useEffect(() => { + if (!isOpen) return; + + return () => releaseModalStackIndex(); + }, [isOpen]); + const open = () => { + if (isOpen) return; setStackIndex(acquireModalStackIndex()); setIsOpen(true); }; diff --git a/packages/timo-design-system/src/lib/index.ts b/packages/timo-design-system/src/lib/index.ts index 25ffb3bc..f86288b5 100644 --- a/packages/timo-design-system/src/lib/index.ts +++ b/packages/timo-design-system/src/lib/index.ts @@ -4,5 +4,8 @@ export { registerOpenFloatingLayer, unregisterOpenFloatingLayer, } from "./floating-layer-registry"; -export { acquireModalStackIndex } from "./modal-stack-registry"; +export { + acquireModalStackIndex, + releaseModalStackIndex, +} from "./modal-stack-registry"; export { useEscapeKey } from "./use-escape-key"; diff --git a/packages/timo-design-system/src/lib/modal-stack-registry.ts b/packages/timo-design-system/src/lib/modal-stack-registry.ts index 2a6a664c..6b265cf3 100644 --- a/packages/timo-design-system/src/lib/modal-stack-registry.ts +++ b/packages/timo-design-system/src/lib/modal-stack-registry.ts @@ -1,7 +1,13 @@ -let nextModalStackIndex = 0; +// 지금 동시에 열려 있는 모달 개수를 센다. 모달이 열릴 때 그 시점의 개수를 받아가고 +// (acquire), 닫힐 때 반납해서(release) 중첩 깊이만 반영하도록 한다. +let openModalCount = 0; export const acquireModalStackIndex = (): number => { - const index = nextModalStackIndex; - nextModalStackIndex += 1; + const index = openModalCount; + openModalCount += 1; return index; }; + +export const releaseModalStackIndex = (): void => { + openModalCount = Math.max(0, openModalCount - 1); +}; From af28f0b26c612fb799560809ca3c32d956141dd0 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Wed, 7 Oct 2026 18:27:46 +0900 Subject: [PATCH 68/89] =?UTF-8?q?fix(web):=20OverlayModal=EB=8F=84=20?= =?UTF-8?q?=EB=8B=AB=ED=9E=90=20=EB=95=8C=20=EB=AA=A8=EB=8B=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=83=9D=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=EB=A5=BC=20=EB=B0=98?= =?UTF-8?q?=EB=82=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#3?= =?UTF-8?q?20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OverlayModal이 acquireModalStackIndex만 쓰고 반납하지 않던 걸 고쳤습니다 - isOpen이 아니라 shouldRender(퇴장 애니메이션이 끝나 실제로 사라지는 시점)에 맞춰 반납해서, 닫히는 중인 모달과 그사이 새로 열리는 모달이 같은 인덱스를 공유하지 않도록 했습니다 Co-Authored-By: Claude Sonnet 5 --- apps/timo-web/components/modal/OverlayModal.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/timo-web/components/modal/OverlayModal.tsx b/apps/timo-web/components/modal/OverlayModal.tsx index d72623ad..62b324a4 100644 --- a/apps/timo-web/components/modal/OverlayModal.tsx +++ b/apps/timo-web/components/modal/OverlayModal.tsx @@ -4,6 +4,7 @@ import { acquireModalStackIndex, cn, hasOpenFloatingLayer, + releaseModalStackIndex, useEscapeKey, } from "@repo/timo-design-system/utils"; import FocusTrap from "focus-trap-react"; @@ -72,6 +73,15 @@ export const OverlayModal = ({ }; }, [shouldRender]); + // 퇴장 애니메이션이 끝나 실제로 DOM에서 사라질 때 스택 인덱스를 반납한다. + // isOpen이 아니라 shouldRender에 맞춰야, 닫히는 중(fade-out)인 모달과 + // 그사이 새로 열리는 모달이 같은 인덱스를 잠깐 공유하는 걸 막을 수 있다. + useEffect(() => { + if (!shouldRender) return; + + return () => releaseModalStackIndex(); + }, [shouldRender]); + useEscapeKey(shouldRender, onClose); if (!shouldRender) return null; From 184b1b0d090e7d21da139a2f07dfe9762efaab38 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Thu, 8 Oct 2026 00:58:52 +0900 Subject: [PATCH 69/89] =?UTF-8?q?feat(web):=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=AA=A8=EB=B0=94=EC=9D=BC?= =?UTF-8?q?=EB=B7=B0=20=EA=B5=AC=ED=98=84=20(#323)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SettingsNavContainer의 고정 폭 세로 사이드바를 모바일에서 가로 스크롤 탭바로, 데스크톱에서 기존 세로 구조로 분기했습니다 - settings layout의 좌우 2단 구조를 모바일에서 세로(네비 위, 콘텐츠 아래)로 바꿨습니다 - 프로필·탈퇴·약관 섹션의 데스크톱 전용 고정 패딩·고정 폭을 모바일 기준값으로 분리하고 md:에서 기존 값을 유지했습니다 - 프로필 이미지, 구글 캘린더 연동 박스, 언어 토글을 모바일 폭에 맞게 조정했습니다 Co-Authored-By: Claude Sonnet 5 --- .../_components/account/SettingsProfileView.tsx | 8 ++++---- .../_components/withdrawal/SettingsWithdrawalView.tsx | 6 +++--- .../settings/_containers/SettingsNavContainer.tsx | 10 +++++----- .../account/SettingsLanguageSectionContainer.tsx | 2 +- .../_containers/account/SettingsProfileContainer.tsx | 2 +- .../_containers/terms/SettingsTermsContainer.tsx | 2 +- apps/timo-web/app/[locale]/(main)/settings/layout.tsx | 4 ++-- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsProfileView.tsx b/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsProfileView.tsx index a5e8f4a4..349b1068 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsProfileView.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsProfileView.tsx @@ -52,7 +52,7 @@ export const SettingsProfileView = ({

{labels.profileSection}

-
+
{profileImageUrl ? ( ) : ( -
+
)}

{name}

@@ -88,7 +88,7 @@ export const SettingsProfileView = ({

{labels.calendarSection}

-
+
{ return ( -
+

{labels.title}


-
+

{labels.guideTitle} @@ -26,7 +26,7 @@ export const SettingsWithdrawalView = ({

-
+

{labels.warningTitle}

diff --git a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx index 2f736c05..1a70fde6 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx @@ -25,24 +25,24 @@ export const SettingsNavContainer = () => { ]; return ( -