From 6a7a9f9e5f52d2ad3a5c5343082178e087610cdc Mon Sep 17 00:00:00 2001 From: jjangminii Date: Wed, 15 Jul 2026 03:52:47 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix(web):=20=EB=8B=A4=EB=A5=B8=20=ED=88=AC?= =?UTF-8?q?=EB=91=90=EC=97=90=20=ED=99=9C=EC=84=B1=20=ED=83=80=EC=9D=B4?= =?UTF-8?q?=EB=A8=B8=EA=B0=80=20=EC=9E=88=EC=9C=BC=EB=A9=B4=20=EC=9E=AC?= =?UTF-8?q?=EC=83=9D=20=EB=B2=84=ED=8A=BC=20=EC=83=89=EC=9D=84=20=EB=82=AE?= =?UTF-8?q?=EC=B6=B0=20=EA=B5=AC=EB=B6=84=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 홈/오늘 탭 목록과 투두 수정 모달의 재생 버튼이 재생 중이든 일시정지든 상관없이 전부 같은 파란색이라, 어떤 투두가 지금 활성 타이머를 갖고 있는지 목록에서 구분이 안 됐습니다. 활성 타이머가 아예 없으면 전부 파란색, 활성 타이머가 있으면 그 투두만 파란색·나머지는 회색으로 낮추도록 PlayButton에 active prop을 추가하고 홈/오늘/투두 수정 모달 세 곳에 동일하게 적용했습니다. --- .../_components/todo-card/HomeTodoCard.tsx | 8 ++++++- .../home/_containers/HomeTodoContainer.tsx | 3 +++ .../_containers/TodayTodoCardContainer.tsx | 3 +++ .../_containers/TodayTodoListContainer.tsx | 2 ++ .../today/_components/TodayTodoCard.tsx | 8 ++++++- .../detail/DetailTodoModalContent.tsx | 3 +++ .../detail/DetailTodoTaskFields.tsx | 8 ++++++- .../detail/DetailTodoModalContainer.tsx | 4 ++++ .../button/play-button/PlayButton.tsx | 23 +++++++++---------- 9 files changed, 47 insertions(+), 15 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 b6f5dbcd..e1a8e187 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 @@ -49,6 +49,8 @@ export interface HomeTodoCardProps { hasMemo: boolean; isRepeated: boolean; timerStatus: TodoTimerStatusTypes; + /** 다른 투두에 활성 타이머(재생/일시정지)가 있을 때 이 투두의 재생 버튼을 낮춰 보여준다 */ + isPlayHighlighted: boolean; subtaskTitle?: string; isSubtaskCompleted?: boolean; onClickTodo?: () => void; @@ -67,6 +69,7 @@ export const HomeTodoCard = ({ hasMemo, isRepeated, timerStatus, + isPlayHighlighted, subtaskTitle, isSubtaskCompleted = false, onClickTodo, @@ -131,6 +134,7 @@ export const HomeTodoCard = ({ variant={isRunning ? "stop" : "play"} size="sm" disabled={isCompleted} + active={isPlayHighlighted} onClick={handlePlayClick} onPointerDown={stopInteractiveEvent} > @@ -138,8 +142,10 @@ export const HomeTodoCard = ({ ) : isRunning ? ( - ) : ( + ) : isPlayHighlighted ? ( + ) : ( + )} diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx index 8c7f75f0..83ba1415 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx @@ -138,6 +138,8 @@ export const HomeTodoContainer = () => { const timerStatus = isActiveTodo ? activeTimer.status : todo.timerStatus; + const isPlayHighlighted = + !activeTimer || Boolean(isActiveTodo); const todoTagLabelKey = todo.tag ? getDefaultTagLabelKey(todo.tag.tagId) @@ -172,6 +174,7 @@ export const HomeTodoContainer = () => { hasMemo={todo.hasMemo} isRepeated={todo.isRepeated} timerStatus={timerStatus} + isPlayHighlighted={isPlayHighlighted} subtaskTitle={firstSubtask?.content} isSubtaskCompleted={firstSubtask?.completed} onClickTodo={openDetailTodoModal} diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoCardContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoCardContainer.tsx index e222ec28..ab6afa60 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoCardContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoCardContainer.tsx @@ -17,6 +17,7 @@ export interface TodayTodoCardContainerProps { subTodos: SubTodo[]; toolbar: TodayTodoCardToolbar; timerStatus: TodoTimerStatusTypes; + isPlayHighlighted: boolean; icon?: ReactNode; onCardClick?: () => void; onToggleCompleted?: (completed: boolean) => void; @@ -31,6 +32,7 @@ export const TodayTodoCardContainer = ({ subTodos, toolbar, timerStatus, + isPlayHighlighted, onCardClick, onToggleCompleted, onTogglePlay, @@ -47,6 +49,7 @@ export const TodayTodoCardContainer = ({ isDone={isCompleted} isDimmed={isDimmed} isPlaying={isPlaying} + isPlayHighlighted={isPlayHighlighted} icon={icon} subTodos={subTodos} toolbar={toolbar} 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 6f973645..360de333 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 @@ -92,6 +92,7 @@ export const TodayTodoListContainer = () => { const timerStatus = isActiveTodo ? activeTimer.status : todo.timerStatus; + const isPlayHighlighted = !activeTimer || Boolean(isActiveTodo); return ( { isCompleted={todo.completed} icon={renderTodoIcon(todo.icon)} timerStatus={timerStatus} + isPlayHighlighted={isPlayHighlighted} toolbar={{ date: formatDate(todo.date), dateValue: new Date(todo.date), diff --git a/apps/timo-web/app/[locale]/(main)/today/_components/TodayTodoCard.tsx b/apps/timo-web/app/[locale]/(main)/today/_components/TodayTodoCard.tsx index 6003adc2..379e3cce 100644 --- a/apps/timo-web/app/[locale]/(main)/today/_components/TodayTodoCard.tsx +++ b/apps/timo-web/app/[locale]/(main)/today/_components/TodayTodoCard.tsx @@ -49,6 +49,8 @@ export interface TodayTodoCardProps { isDone: boolean; isDimmed: boolean; isPlaying: boolean; + /** 다른 투두에 활성 타이머(재생/일시정지)가 있을 때 이 투두의 재생 버튼을 낮춰 보여준다 */ + isPlayHighlighted: boolean; icon?: ReactNode; subTodos: SubTodo[]; toolbar: TodayTodoCardToolbar; @@ -68,6 +70,7 @@ export const TodayTodoCard = ({ isDone, isDimmed, isPlaying, + isPlayHighlighted, icon, subTodos, toolbar, @@ -123,14 +126,17 @@ export const TodayTodoCard = ({ variant={isPlaying ? "stop" : "play"} size="lg" disabled={isDone} + active={isPlayHighlighted} onClick={onPlay} > {isDone ? ( ) : isPlaying ? ( - ) : ( + ) : isPlayHighlighted ? ( + ) : ( + )} diff --git a/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx b/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx index dad126c1..9d4f162c 100644 --- a/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx +++ b/apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx @@ -40,6 +40,7 @@ export interface DetailTodoModalContentProps { onClose: () => void; onExited: () => void; todo: TodoDetailResponse; + isPlayHighlighted: boolean; onTogglePlay: () => void; onToggleCompleted: (completed: boolean) => void; onDelete: () => void; @@ -51,6 +52,7 @@ export const DetailTodoModalContent = ({ onClose, onExited, todo, + isPlayHighlighted, onTogglePlay, onToggleCompleted, onDelete, @@ -227,6 +229,7 @@ export const DetailTodoModalContent = ({ isCompleted={todo.completed} disabled={!canUpdateTodo} timerStatus={todo.timerStatus} + isPlayHighlighted={isPlayHighlighted} subtaskInputs={detailTodoForm.subtaskInputs} onTitleChange={detailTodoForm.changeTitle} onToggleCompleted={onToggleCompleted} diff --git a/apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx b/apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx index e4a5f676..66fc69b2 100644 --- a/apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx +++ b/apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx @@ -20,6 +20,8 @@ export interface DetailTodoTaskFieldsProps { isCompleted: boolean; disabled?: boolean; timerStatus: TodoDetailResponseTimerStatus; + /** 다른 투두에 활성 타이머(재생/일시정지)가 있을 때 이 재생 버튼을 낮춰 보여준다 */ + isPlayHighlighted: boolean; subtaskInputs: DetailTodoSubtaskInput[]; onTitleChange: (value: string) => void; onToggleCompleted: (completed: boolean) => void; @@ -40,6 +42,7 @@ export const DetailTodoTaskFields = ({ isCompleted, disabled = false, timerStatus, + isPlayHighlighted, subtaskInputs, onTitleChange, onToggleCompleted, @@ -72,14 +75,17 @@ export const DetailTodoTaskFields = ({ variant={timerStatus === "RUNNING" ? "stop" : "play"} size="lg" disabled={isCompleted} + active={isPlayHighlighted} onClick={onTogglePlay} > {isCompleted ? ( ) : timerStatus === "RUNNING" ? ( - ) : ( + ) : isPlayHighlighted ? ( + ) : ( + )} diff --git a/apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx b/apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx index c506c341..ee442775 100644 --- a/apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx +++ b/apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx @@ -11,6 +11,7 @@ import { AnimatedToast } from "@/components/toast/AnimatedToast"; import { DetailTodoModalContent } from "@/components/todo-modal/detail/DetailTodoModalContent"; import { useDeleteTodoSubmit } from "@/hooks/todo-modal/detail/use-delete-todo-submit"; import { useUpdateTodoSubmit } from "@/hooks/todo-modal/detail/use-update-todo-submit"; +import { useActiveTimer } from "@/hooks/use-active-timer"; export interface DetailTodoModalContainerProps { todoId: number; @@ -42,7 +43,9 @@ export const DetailTodoModalContainer = ({ ); const { handleDelete } = useDeleteTodoSubmit(); const { handleUpdate } = useUpdateTodoSubmit(); + const { data: activeTimer } = useActiveTimer(); const todo = data?.data; + const isPlayHighlighted = !activeTimer || activeTimer.todoId === todoId; const openDetailTodoModal = () => { setIsMounted(true); @@ -89,6 +92,7 @@ export const DetailTodoModalContainer = ({ onClose={closeDetailTodoModal} onExited={() => setIsMounted(false)} todo={todo} + isPlayHighlighted={isPlayHighlighted} onTogglePlay={onTogglePlay} onToggleCompleted={onToggleCompleted} onDelete={deleteTodo} diff --git a/packages/timo-design-system/src/components/button/play-button/PlayButton.tsx b/packages/timo-design-system/src/components/button/play-button/PlayButton.tsx index 67097b3c..ce3f2af9 100644 --- a/packages/timo-design-system/src/components/button/play-button/PlayButton.tsx +++ b/packages/timo-design-system/src/components/button/play-button/PlayButton.tsx @@ -2,7 +2,6 @@ import { cn } from "../../../lib"; import type { MouseEventHandler, PointerEventHandler, ReactNode } from "react"; - export type PlayButtonVariant = "play" | "stop"; export type PlayButtonSize = "sm" | "lg"; @@ -10,18 +9,17 @@ export interface PlayButtonProps { variant: PlayButtonVariant; size?: PlayButtonSize; disabled?: boolean; + /** 다른 항목에 활성 타이머가 있어 이 버튼을 시각적으로 낮춰야 할 때 false로 준다 (클릭은 계속 가능) */ + active?: boolean; onClick?: MouseEventHandler; onPointerDown?: PointerEventHandler; children?: ReactNode; ariaLabel?: string; } -const VARIANT_MAP: Record< - PlayButtonVariant, - { className: string; label: string } -> = { - play: { className: "cursor-pointer bg-timo-blue-50", label: "재생" }, - stop: { className: "cursor-pointer bg-timo-blue-50", label: "정지" }, +const VARIANT_LABEL: Record = { + play: "재생", + stop: "정지", }; const SIZE_CLASS: Record = { @@ -33,6 +31,7 @@ export const PlayButton = ({ variant, size = "sm", disabled = false, + active = true, onClick, onPointerDown, children, @@ -46,16 +45,16 @@ export const PlayButton = ({ disabled={disabled} aria-label={ ariaLabel ?? - (disabled - ? `${VARIANT_MAP[variant].label} 불가` - : VARIANT_MAP[variant].label) + (disabled ? `${VARIANT_LABEL[variant]} 불가` : VARIANT_LABEL[variant]) } className={cn( - "inline-flex items-center justify-center rounded-full", + "inline-flex cursor-pointer items-center justify-center rounded-full", SIZE_CLASS[size], disabled ? "bg-timo-gray-500 cursor-not-allowed" - : VARIANT_MAP[variant].className, + : active + ? "bg-timo-blue-50" + : "bg-timo-gray-500", )} > {children} From 72428d79ba7566b64c9e4465a069a3009899e784 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Wed, 15 Jul 2026 03:58:40 +0900 Subject: [PATCH 2/2] =?UTF-8?q?chore(web):=20isPlayHighlighted=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=EC=9D=84=20=EC=A0=9C=EA=B1=B0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit prop 이름만으로 드러나는 설명이라 정리했습니다. --- .../home/_components/todo-card/HomeTodoCard.tsx | 1 - .../app/[locale]/(main)/today/_components/TodayTodoCard.tsx | 1 - .../components/todo-modal/detail/DetailTodoTaskFields.tsx | 1 - 3 files changed, 3 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 e1a8e187..e19edb94 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 @@ -49,7 +49,6 @@ export interface HomeTodoCardProps { hasMemo: boolean; isRepeated: boolean; timerStatus: TodoTimerStatusTypes; - /** 다른 투두에 활성 타이머(재생/일시정지)가 있을 때 이 투두의 재생 버튼을 낮춰 보여준다 */ isPlayHighlighted: boolean; subtaskTitle?: string; isSubtaskCompleted?: boolean; diff --git a/apps/timo-web/app/[locale]/(main)/today/_components/TodayTodoCard.tsx b/apps/timo-web/app/[locale]/(main)/today/_components/TodayTodoCard.tsx index 379e3cce..f8d279a5 100644 --- a/apps/timo-web/app/[locale]/(main)/today/_components/TodayTodoCard.tsx +++ b/apps/timo-web/app/[locale]/(main)/today/_components/TodayTodoCard.tsx @@ -49,7 +49,6 @@ export interface TodayTodoCardProps { isDone: boolean; isDimmed: boolean; isPlaying: boolean; - /** 다른 투두에 활성 타이머(재생/일시정지)가 있을 때 이 투두의 재생 버튼을 낮춰 보여준다 */ isPlayHighlighted: boolean; icon?: ReactNode; subTodos: SubTodo[]; diff --git a/apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx b/apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx index 66fc69b2..a3e1db31 100644 --- a/apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx +++ b/apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx @@ -20,7 +20,6 @@ export interface DetailTodoTaskFieldsProps { isCompleted: boolean; disabled?: boolean; timerStatus: TodoDetailResponseTimerStatus; - /** 다른 투두에 활성 타이머(재생/일시정지)가 있을 때 이 재생 버튼을 낮춰 보여준다 */ isPlayHighlighted: boolean; subtaskInputs: DetailTodoSubtaskInput[]; onTitleChange: (value: string) => void;