refactor(control-center): Scene/Time 중심으로 단순화하고 추천 복원 흐름 추가
맥락: - Control Center가 설정 패널처럼 무거워 보여 Focus 몰입 흐름에서 탐색 부담이 컸습니다. 변경사항: - Control Center에서 Sound 섹션과 Preset Packs 섹션을 제거하고 Scene/Time 2개 핵심 섹션만 유지했습니다. - 추천 정보를 비인터랙션 1줄(추천 사운드 · 추천 타이머)로 노출하도록 구성했습니다. - 하단에 tertiary 액션 '추천으로 되돌리기'를 추가해 override 초기화 + 추천값 복원 진입점을 만들었습니다. - 더 이상 사용하지 않는 quick pack 모델 파일을 제거했습니다. 검증: - npx tsc --noEmit 세션-상태: Control Center가 Scene/Time 중심의 경량 구조로 정리되었습니다. 세션-다음: 우하단 Sound Quick 변경 시 override.sound가 명시적으로 적용되는 경로를 분리합니다. 세션-리스크: 추천 정보는 현재 텍스트 기반이라 향후 다국어/라벨 변경 시 매핑 점검이 필요합니다.
This commit is contained in:
@@ -4,11 +4,10 @@ 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 { SoundPreset, TimerPreset } from '@/entities/session';
|
||||
import type { TimerPreset } from '@/entities/session';
|
||||
import { cn } from '@/shared/lib/cn';
|
||||
import { useReducedMotion } from '@/shared/lib/useReducedMotion';
|
||||
|
||||
@@ -17,49 +16,15 @@ interface ControlCenterSheetWidgetProps {
|
||||
rooms: RoomTheme[];
|
||||
selectedRoomId: string;
|
||||
selectedTimerLabel: string;
|
||||
selectedSoundPresetId: string;
|
||||
sceneRecommendedSoundLabel: string;
|
||||
sceneRecommendedTimerLabel: string;
|
||||
timerPresets: TimerPreset[];
|
||||
soundPresets: SoundPreset[];
|
||||
onSelectRoom: (roomId: string) => void;
|
||||
onSelectTimer: (timerLabel: string) => void;
|
||||
onSelectSound: (soundPresetId: string) => void;
|
||||
onApplyPack: (packId: QuickPackId) => void;
|
||||
onLockedClick: (source: string) => void;
|
||||
onResetToRecommended: () => void;
|
||||
}
|
||||
|
||||
type QuickPackId = 'balanced' | 'deep-work' | 'gentle';
|
||||
|
||||
interface QuickPack {
|
||||
id: QuickPackId;
|
||||
name: string;
|
||||
combo: string;
|
||||
locked: boolean;
|
||||
}
|
||||
|
||||
const QUICK_PACKS: QuickPack[] = [
|
||||
{
|
||||
id: 'balanced',
|
||||
name: 'Balanced',
|
||||
combo: '25/5 + Rain Focus',
|
||||
locked: false,
|
||||
},
|
||||
{
|
||||
id: 'deep-work',
|
||||
name: 'Deep Work',
|
||||
combo: '50/10 + Deep White',
|
||||
locked: true,
|
||||
},
|
||||
{
|
||||
id: 'gentle',
|
||||
name: 'Gentle',
|
||||
combo: '25/5 + Silent',
|
||||
locked: true,
|
||||
},
|
||||
];
|
||||
|
||||
const LockBadge = () => {
|
||||
return (
|
||||
<span className="absolute right-2 top-2 rounded-full border border-white/20 bg-black/46 px-1.5 py-0.5 text-[9px] font-semibold tracking-[0.08em] text-white/86">
|
||||
@@ -82,17 +47,13 @@ export const ControlCenterSheetWidget = ({
|
||||
rooms,
|
||||
selectedRoomId,
|
||||
selectedTimerLabel,
|
||||
selectedSoundPresetId,
|
||||
sceneRecommendedSoundLabel: _sceneRecommendedSoundLabel,
|
||||
sceneRecommendedTimerLabel: _sceneRecommendedTimerLabel,
|
||||
sceneRecommendedSoundLabel,
|
||||
sceneRecommendedTimerLabel,
|
||||
timerPresets,
|
||||
soundPresets,
|
||||
onSelectRoom,
|
||||
onSelectTimer,
|
||||
onSelectSound,
|
||||
onApplyPack,
|
||||
onLockedClick,
|
||||
onResetToRecommended: _onResetToRecommended,
|
||||
onResetToRecommended,
|
||||
}: ControlCenterSheetWidgetProps) => {
|
||||
const reducedMotion = useReducedMotion();
|
||||
const isPro = plan === 'pro';
|
||||
@@ -107,12 +68,8 @@ export const ControlCenterSheetWidget = ({
|
||||
return rooms.find((room) => room.id === selectedRoomId) ?? rooms[0];
|
||||
}, [rooms, selectedRoomId]);
|
||||
|
||||
const selectedSound = useMemo(() => {
|
||||
return soundPresets.find((preset) => preset.id === selectedSoundPresetId) ?? soundPresets[0];
|
||||
}, [selectedSoundPresetId, soundPresets]);
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-4">
|
||||
<section className="space-y-2.5 rounded-2xl border border-white/12 bg-black/22 p-3.5 backdrop-blur-md">
|
||||
<SectionTitle title="Scene" description={selectedRoom?.name ?? '공간'} />
|
||||
<div
|
||||
@@ -193,72 +150,19 @@ export const ControlCenterSheetWidget = ({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2.5 rounded-2xl border border-white/12 bg-black/22 p-3.5 backdrop-blur-md">
|
||||
<SectionTitle title="Sound" description={selectedSound?.label ?? '기본'} />
|
||||
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
||||
{soundPresets.slice(0, 6).map((preset) => {
|
||||
const selected = preset.id === selectedSoundPresetId;
|
||||
const locked = !isPro && PRO_LOCKED_SOUND_IDS.includes(preset.id);
|
||||
|
||||
return (
|
||||
<div className="space-y-1.5 rounded-xl border border-white/12 bg-white/[0.03] px-3 py-2.5">
|
||||
<p className="text-[11px] text-white/58">추천: {sceneRecommendedSoundLabel} · {sceneRecommendedTimerLabel}</p>
|
||||
<button
|
||||
key={preset.id}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (locked) {
|
||||
onLockedClick(`사운드: ${preset.label}`);
|
||||
return;
|
||||
}
|
||||
|
||||
onSelectSound(preset.id);
|
||||
}}
|
||||
onClick={onResetToRecommended}
|
||||
className={cn(
|
||||
'rounded-xl border px-3 py-2 text-xs',
|
||||
colorMotionClass,
|
||||
selected
|
||||
? 'border-sky-200/42 bg-sky-200/16 text-white'
|
||||
: 'border-white/18 bg-white/[0.04] text-white/74 hover:bg-white/[0.1]',
|
||||
)}
|
||||
>
|
||||
{preset.label}
|
||||
{locked ? <span className="ml-1.5 text-[10px] text-white/66">LOCK PRO</span> : null}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2.5 rounded-2xl border border-white/12 bg-black/22 p-3.5 backdrop-blur-md">
|
||||
<SectionTitle title="Preset Packs" description="원탭 조합" />
|
||||
<div className="grid gap-2.5">
|
||||
{QUICK_PACKS.map((pack) => {
|
||||
const locked = !isPro && pack.locked;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={pack.id}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (locked) {
|
||||
onLockedClick(`프리셋 팩: ${pack.name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
onApplyPack(pack.id);
|
||||
}}
|
||||
className={cn(
|
||||
'relative rounded-xl border border-white/16 bg-white/[0.04] px-3.5 py-2.5 text-left hover:bg-white/[0.1]',
|
||||
'text-left text-[11px] text-white/72 transition-colors hover:text-white/90',
|
||||
colorMotionClass,
|
||||
)}
|
||||
>
|
||||
{locked ? <LockBadge /> : null}
|
||||
<p className="text-sm font-medium text-white/90">{pack.name}</p>
|
||||
<p className="mt-0.5 text-[11px] text-white/62">{pack.combo}</p>
|
||||
추천으로 되돌리기
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
interface ApplyQuickPackParams {
|
||||
packId: 'balanced' | 'deep-work' | 'gentle';
|
||||
onTimerSelect: (timerLabel: string) => void;
|
||||
onSelectPreset: (presetId: string) => void;
|
||||
onApplied?: (message: string) => void;
|
||||
}
|
||||
|
||||
export const applyQuickPack = ({
|
||||
packId,
|
||||
onTimerSelect,
|
||||
onSelectPreset,
|
||||
onApplied,
|
||||
}: ApplyQuickPackParams) => {
|
||||
if (packId === 'balanced') {
|
||||
onTimerSelect('25/5');
|
||||
onSelectPreset('rain-focus');
|
||||
onApplied?.('Balanced 팩을 적용했어요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (packId === 'deep-work') {
|
||||
onTimerSelect('50/10');
|
||||
onSelectPreset('deep-white');
|
||||
onApplied?.('Deep Work 팩을 적용했어요.');
|
||||
return;
|
||||
}
|
||||
|
||||
onTimerSelect('25/5');
|
||||
onSelectPreset('silent');
|
||||
onApplied?.('Gentle 팩을 적용했어요.');
|
||||
};
|
||||
@@ -12,7 +12,6 @@ import { cn } from '@/shared/lib/cn';
|
||||
import { ControlCenterSheetWidget } from '@/widgets/control-center-sheet';
|
||||
import { SpaceSideSheet } from '@/widgets/space-sheet-shell';
|
||||
import type { SpaceAnchorPopoverId, SpaceUtilityPanelId } from '../model/types';
|
||||
import { applyQuickPack } from '../model/applyQuickPack';
|
||||
import { getQuickSoundPresets } from '../model/getQuickSoundPresets';
|
||||
import { ANCHOR_ICON, formatThoughtCount, UTILITY_PANEL_TITLE } from './constants';
|
||||
import { FocusRightRail } from './FocusRightRail';
|
||||
@@ -246,13 +245,6 @@ export const SpaceToolsDockWidget = ({
|
||||
openUtilityPanel('control-center');
|
||||
};
|
||||
|
||||
const handleApplyPack = (packId: 'balanced' | 'deep-work' | 'gentle') =>
|
||||
applyQuickPack({
|
||||
packId,
|
||||
onTimerSelect,
|
||||
onSelectPreset,
|
||||
});
|
||||
|
||||
const showVolumeFeedback = (nextVolume: number) => {
|
||||
setVolumeFeedback(`${nextVolume}%`);
|
||||
|
||||
@@ -419,21 +411,15 @@ export const SpaceToolsDockWidget = ({
|
||||
rooms={rooms}
|
||||
selectedRoomId={selectedRoomId}
|
||||
selectedTimerLabel={selectedTimerLabel}
|
||||
selectedSoundPresetId={selectedPresetId}
|
||||
sceneRecommendedSoundLabel={sceneRecommendedSoundLabel}
|
||||
sceneRecommendedTimerLabel={sceneRecommendedTimerLabel}
|
||||
timerPresets={timerPresets}
|
||||
soundPresets={SOUND_PRESETS}
|
||||
onSelectRoom={(roomId) => {
|
||||
onRoomSelect(roomId);
|
||||
}}
|
||||
onSelectTimer={(label) => {
|
||||
onTimerSelect(label);
|
||||
}}
|
||||
onSelectSound={(presetId) => {
|
||||
onSelectPreset(presetId);
|
||||
}}
|
||||
onApplyPack={handleApplyPack}
|
||||
onLockedClick={handleLockedClick}
|
||||
onResetToRecommended={onResetToSceneRecommended}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user