From f3f0518588bf2b04e5f62c2b5f1fced12e28afb6 Mon Sep 17 00:00:00 2001 From: corpi Date: Thu, 5 Mar 2026 16:24:53 +0900 Subject: [PATCH] =?UTF-8?q?fix(space):=20Quick=20Controls=20=EC=82=AC?= =?UTF-8?q?=EC=9A=B4=EB=93=9C=20=EB=B3=B5=EC=9B=90=EA=B3=BC=20HUD=20?= =?UTF-8?q?=ED=94=BC=EB=93=9C=EB=B0=B1=20=EC=A0=95=ED=95=A9=EC=84=B1=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eslint.config.mjs | 13 ++-- .../restart-30s/model/useRestart30s.ts | 26 -------- .../ui/ControlCenterSheetWidget.tsx | 45 ++++++++++++- .../ui/SpaceToolsDockWidget.tsx | 66 +++++++++++++++---- .../ui/SpaceWorkspaceWidget.tsx | 2 + 5 files changed, 109 insertions(+), 43 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 162f8fc..952ece5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,10 +1,15 @@ -// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format -import storybook from "eslint-plugin-storybook"; - import { defineConfig, globalIgnores } from "eslint/config"; import nextVitals from "eslint-config-next/core-web-vitals"; import nextTs from "eslint-config-next/typescript"; +let storybookConfig = []; + +try { + // Optional dependency: lint should still run when Storybook plugin is not installed. + const storybook = await import("eslint-plugin-storybook"); + storybookConfig = storybook.default?.configs?.["flat/recommended"] ?? []; +} catch {} + const eslintConfig = defineConfig([ ...nextVitals, ...nextTs, @@ -16,7 +21,7 @@ const eslintConfig = defineConfig([ "build/**", "next-env.d.ts", ]), - ...storybook.configs["flat/recommended"] + ...storybookConfig ]); export default eslintConfig; diff --git a/src/features/restart-30s/model/useRestart30s.ts b/src/features/restart-30s/model/useRestart30s.ts index 0d640cc..8c62a57 100644 --- a/src/features/restart-30s/model/useRestart30s.ts +++ b/src/features/restart-30s/model/useRestart30s.ts @@ -1,32 +1,17 @@ 'use client'; import { useEffect, useRef, useState } from 'react'; -import { useToast } from '@/shared/ui'; -import { - RECOVERY_30S_BUTTON_LABEL, - RECOVERY_30S_TOAST_MESSAGE, -} from './copy'; - const MODE_DURATION_MS = 2000; -const HINT_DURATION_MS = 1800; export const useRestart30s = () => { - const { pushToast } = useToast(); const [isBreatheMode, setBreatheMode] = useState(false); - const [hintMessage, setHintMessage] = useState(null); const resetTimerRef = useRef(null); - const hintTimerRef = useRef(null); const clearTimers = () => { if (resetTimerRef.current !== null) { window.clearTimeout(resetTimerRef.current); resetTimerRef.current = null; } - - if (hintTimerRef.current !== null) { - window.clearTimeout(hintTimerRef.current); - hintTimerRef.current = null; - } }; useEffect(() => { @@ -38,16 +23,6 @@ export const useRestart30s = () => { const triggerRestart = () => { clearTimers(); setBreatheMode(true); - setHintMessage(RECOVERY_30S_TOAST_MESSAGE); - - pushToast({ - title: RECOVERY_30S_BUTTON_LABEL, - description: RECOVERY_30S_TOAST_MESSAGE, - }); - - hintTimerRef.current = window.setTimeout(() => { - setHintMessage(null); - }, HINT_DURATION_MS); resetTimerRef.current = window.setTimeout(() => { setBreatheMode(false); @@ -56,7 +31,6 @@ export const useRestart30s = () => { return { isBreatheMode, - hintMessage, triggerRestart, }; }; diff --git a/src/widgets/control-center-sheet/ui/ControlCenterSheetWidget.tsx b/src/widgets/control-center-sheet/ui/ControlCenterSheetWidget.tsx index 814f13c..6da819f 100644 --- a/src/widgets/control-center-sheet/ui/ControlCenterSheetWidget.tsx +++ b/src/widgets/control-center-sheet/ui/ControlCenterSheetWidget.tsx @@ -4,10 +4,11 @@ import { useMemo } from 'react'; import type { PlanTier } from '@/entities/plan'; import { PRO_LOCKED_ROOM_IDS, + PRO_LOCKED_SOUND_IDS, PRO_LOCKED_TIMER_LABELS, } from '@/entities/plan'; import { getRoomCardBackgroundStyle, type RoomTheme } from '@/entities/room'; -import type { TimerPreset } from '@/entities/session'; +import { SOUND_PRESETS, type TimerPreset } from '@/entities/session'; import { cn } from '@/shared/lib/cn'; import { useReducedMotion } from '@/shared/lib/useReducedMotion'; import { Toggle } from '@/shared/ui'; @@ -17,6 +18,7 @@ interface ControlCenterSheetWidgetProps { rooms: RoomTheme[]; selectedRoomId: string; selectedTimerLabel: string; + selectedSoundPresetId: string; sceneRecommendedSoundLabel: string; sceneRecommendedTimerLabel: string; timerPresets: TimerPreset[]; @@ -24,6 +26,7 @@ interface ControlCenterSheetWidgetProps { onAutoHideControlsChange: (next: boolean) => void; onSelectRoom: (roomId: string) => void; onSelectTimer: (timerLabel: string) => void; + onSelectSound: (presetId: string) => void; onLockedClick: (source: string) => void; onResetToRecommended: () => void; } @@ -50,6 +53,7 @@ export const ControlCenterSheetWidget = ({ rooms, selectedRoomId, selectedTimerLabel, + selectedSoundPresetId, sceneRecommendedSoundLabel, sceneRecommendedTimerLabel, timerPresets, @@ -57,6 +61,7 @@ export const ControlCenterSheetWidget = ({ onAutoHideControlsChange, onSelectRoom, onSelectTimer, + onSelectSound, onLockedClick, onResetToRecommended, }: ControlCenterSheetWidgetProps) => { @@ -155,6 +160,44 @@ export const ControlCenterSheetWidget = ({ +
+ preset.id === selectedSoundPresetId)?.label ?? '기본'} + /> +
+ {SOUND_PRESETS.slice(0, 6).map((preset) => { + const selected = preset.id === selectedSoundPresetId; + const locked = !isPro && PRO_LOCKED_SOUND_IDS.includes(preset.id); + + return ( + + ); + })} +
+
+

추천: {sceneRecommendedSoundLabel} · {sceneRecommendedTimerLabel}