style(control-center): 모드 선택 pill로 교체하고 패널 바디로 재배치

맥락:
- Quick Controls 헤더의 모드 토글이 대시보드형 느낌을 만들어 감성 톤을 해치고 있었습니다.

변경사항:
- Control Center 헤더에서는 모드 조작 UI를 제거하고 Plan Pill + 닫기만 유지했습니다.
- 패널 바디 첫 섹션에 기본/몰입 segmented pill을 배치하고, 선택 상태에 따라 저자극 스타일을 적용했습니다.
- 모드 설명 1줄(기본: 모든 컨트롤 표시, 몰입: 필수만 남기고 숨김)을 추가했습니다.
- 모드 상태를 workspace -> tools-dock -> focus-hud 경로로 연결해 HUD 톤 반영을 유지했습니다.

검증:
- npx tsc --noEmit

세션-상태: Quick Controls 헤더가 깔끔해지고 모드 선택이 패널 바디에서 동작합니다.
세션-다음: Scene 추천 매핑 품질 점검과 override UX 검증을 진행합니다.
세션-리스크: 모드 설명 문구의 톤/길이는 실제 사용성 테스트에서 추가 미세조정이 필요할 수 있습니다.
This commit is contained in:
2026-03-05 14:08:09 +09:00
parent a4f6a119aa
commit e9e6006513
4 changed files with 52 additions and 1 deletions

View File

@@ -16,9 +16,11 @@ interface ControlCenterSheetWidgetProps {
rooms: RoomTheme[]; rooms: RoomTheme[];
selectedRoomId: string; selectedRoomId: string;
selectedTimerLabel: string; selectedTimerLabel: string;
isImmersionMode: boolean;
sceneRecommendedSoundLabel: string; sceneRecommendedSoundLabel: string;
sceneRecommendedTimerLabel: string; sceneRecommendedTimerLabel: string;
timerPresets: TimerPreset[]; timerPresets: TimerPreset[];
onImmersionModeChange: (next: boolean) => void;
onSelectRoom: (roomId: string) => void; onSelectRoom: (roomId: string) => void;
onSelectTimer: (timerLabel: string) => void; onSelectTimer: (timerLabel: string) => void;
onLockedClick: (source: string) => void; onLockedClick: (source: string) => void;
@@ -47,9 +49,11 @@ export const ControlCenterSheetWidget = ({
rooms, rooms,
selectedRoomId, selectedRoomId,
selectedTimerLabel, selectedTimerLabel,
isImmersionMode,
sceneRecommendedSoundLabel, sceneRecommendedSoundLabel,
sceneRecommendedTimerLabel, sceneRecommendedTimerLabel,
timerPresets, timerPresets,
onImmersionModeChange,
onSelectRoom, onSelectRoom,
onSelectTimer, onSelectTimer,
onLockedClick, onLockedClick,
@@ -70,6 +74,41 @@ export const ControlCenterSheetWidget = ({
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<section className="space-y-2 rounded-2xl border border-white/12 bg-black/22 p-3 backdrop-blur-md">
<div className="flex items-center justify-between gap-2">
<p className="text-[11px] text-white/64"></p>
<div className="inline-flex items-center rounded-full border border-white/14 bg-white/[0.03] p-0.5">
<button
type="button"
onClick={() => onImmersionModeChange(false)}
className={cn(
'h-7 rounded-full border px-3 text-[11px] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-200/70',
!isImmersionMode
? 'border-white/15 bg-white/10 text-white/90'
: 'border-white/10 bg-transparent text-white/60 hover:text-white/78',
)}
>
</button>
<button
type="button"
onClick={() => onImmersionModeChange(true)}
className={cn(
'h-7 rounded-full border px-3 text-[11px] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-200/70',
isImmersionMode
? 'border-white/15 bg-white/10 text-white/90'
: 'border-white/10 bg-transparent text-white/60 hover:text-white/78',
)}
>
</button>
</div>
</div>
<p className="text-[11px] text-white/54">
{isImmersionMode ? '필수만 남기고 숨김' : '모든 컨트롤 표시'}
</p>
</section>
<section className="space-y-2.5 rounded-2xl border border-white/12 bg-black/22 p-3.5 backdrop-blur-md"> <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 ?? '공간'} /> <SectionTitle title="Scene" description={selectedRoom?.name ?? '공간'} />
<div <div

View File

@@ -9,6 +9,7 @@ interface SpaceFocusHudWidgetProps {
goal: string; goal: string;
timerLabel: string; timerLabel: string;
visible: boolean; visible: boolean;
isImmersionMode: boolean;
onGoalUpdate: (nextGoal: string) => void; onGoalUpdate: (nextGoal: string) => void;
statusLine: HudStatusLineItem | null; statusLine: HudStatusLineItem | null;
onStatusAction: () => void; onStatusAction: () => void;
@@ -19,6 +20,7 @@ export const SpaceFocusHudWidget = ({
goal, goal,
timerLabel, timerLabel,
visible, visible,
isImmersionMode,
onGoalUpdate, onGoalUpdate,
statusLine, statusLine,
onStatusAction, onStatusAction,
@@ -108,7 +110,7 @@ export const SpaceFocusHudWidget = ({
} }
: null : null
} }
isImmersionMode isImmersionMode={isImmersionMode}
className="pr-[4.2rem]" className="pr-[4.2rem]"
onGoalCompleteRequest={handleOpenCompleteSheet} onGoalCompleteRequest={handleOpenCompleteSheet}
onStatusAction={onStatusAction} onStatusAction={onStatusAction}

View File

@@ -20,6 +20,7 @@ import { QuickSoundPopover } from './popovers/QuickSoundPopover';
import { InboxToolPanel } from './panels/InboxToolPanel'; import { InboxToolPanel } from './panels/InboxToolPanel';
interface SpaceToolsDockWidgetProps { interface SpaceToolsDockWidgetProps {
isFocusMode: boolean; isFocusMode: boolean;
isImmersionMode: boolean;
rooms: RoomTheme[]; rooms: RoomTheme[];
selectedRoomId: string; selectedRoomId: string;
selectedTimerLabel: string; selectedTimerLabel: string;
@@ -42,12 +43,14 @@ interface SpaceToolsDockWidgetProps {
onRestoreThought: (thought: RecentThought) => void; onRestoreThought: (thought: RecentThought) => void;
onClearInbox: () => RecentThought[]; onClearInbox: () => RecentThought[];
onResetToSceneRecommended: () => void; onResetToSceneRecommended: () => void;
onImmersionModeChange: (next: boolean) => void;
onStatusMessage: (payload: HudStatusLinePayload) => void; onStatusMessage: (payload: HudStatusLinePayload) => void;
onExitRequested: () => void; onExitRequested: () => void;
} }
export const SpaceToolsDockWidget = ({ export const SpaceToolsDockWidget = ({
isFocusMode, isFocusMode,
isImmersionMode,
rooms, rooms,
selectedRoomId, selectedRoomId,
selectedTimerLabel, selectedTimerLabel,
@@ -70,6 +73,7 @@ export const SpaceToolsDockWidget = ({
onRestoreThought, onRestoreThought,
onClearInbox, onClearInbox,
onResetToSceneRecommended, onResetToSceneRecommended,
onImmersionModeChange,
onStatusMessage, onStatusMessage,
onExitRequested, onExitRequested,
}: SpaceToolsDockWidgetProps) => { }: SpaceToolsDockWidgetProps) => {
@@ -411,9 +415,11 @@ export const SpaceToolsDockWidget = ({
rooms={rooms} rooms={rooms}
selectedRoomId={selectedRoomId} selectedRoomId={selectedRoomId}
selectedTimerLabel={selectedTimerLabel} selectedTimerLabel={selectedTimerLabel}
isImmersionMode={isImmersionMode}
sceneRecommendedSoundLabel={sceneRecommendedSoundLabel} sceneRecommendedSoundLabel={sceneRecommendedSoundLabel}
sceneRecommendedTimerLabel={sceneRecommendedTimerLabel} sceneRecommendedTimerLabel={sceneRecommendedTimerLabel}
timerPresets={timerPresets} timerPresets={timerPresets}
onImmersionModeChange={onImmersionModeChange}
onSelectRoom={(roomId) => { onSelectRoom={(roomId) => {
onRoomSelect(roomId); onRoomSelect(roomId);
}} }}

View File

@@ -167,6 +167,7 @@ export const SpaceWorkspaceWidget = () => {
); );
const [workspaceMode, setWorkspaceMode] = useState<WorkspaceMode>('setup'); const [workspaceMode, setWorkspaceMode] = useState<WorkspaceMode>('setup');
const [isImmersionMode, setImmersionMode] = useState(false);
const [selectedRoomId, setSelectedRoomId] = useState(initialRoomId); const [selectedRoomId, setSelectedRoomId] = useState(initialRoomId);
const [selectedTimerLabel, setSelectedTimerLabel] = useState(initialTimerLabel); const [selectedTimerLabel, setSelectedTimerLabel] = useState(initialTimerLabel);
const [goalInput, setGoalInput] = useState(initialGoal); const [goalInput, setGoalInput] = useState(initialGoal);
@@ -377,6 +378,7 @@ export const SpaceWorkspaceWidget = () => {
goal={goalInput.trim()} goal={goalInput.trim()}
timerLabel={selectedTimerLabel} timerLabel={selectedTimerLabel}
visible={isFocusMode} visible={isFocusMode}
isImmersionMode={isImmersionMode}
statusLine={activeStatus} statusLine={activeStatus}
onStatusAction={runActiveAction} onStatusAction={runActiveAction}
onStatusMessage={pushStatusLine} onStatusMessage={pushStatusLine}
@@ -405,6 +407,8 @@ export const SpaceWorkspaceWidget = () => {
onSetSoundVolume={setMasterVolume} onSetSoundVolume={setMasterVolume}
isSoundMuted={isMuted} isSoundMuted={isMuted}
onSetSoundMuted={setMuted} onSetSoundMuted={setMuted}
isImmersionMode={isImmersionMode}
onImmersionModeChange={setImmersionMode}
onCaptureThought={(note) => addThought(note, selectedRoom.name)} onCaptureThought={(note) => addThought(note, selectedRoom.name)}
onDeleteThought={removeThought} onDeleteThought={removeThought}
onSetThoughtCompleted={setThoughtCompleted} onSetThoughtCompleted={setThoughtCompleted}