feat(control-policy): 컨트롤 자동 숨김 표시 정책 옵션 추가

맥락:
- Focus-First 구조에서 패널을 열어둔 채 방치되면 몰입 흐름이 깨질 수 있어, 모드 토글이 아닌 표시 정책 기반 정리가 필요했습니다.

변경사항:
- Quick Controls 패널 하단에 컨트롤 자동 숨김 옵션을 추가했습니다.
- 옵션은 Focus 화면 상시 UI가 아닌 패널 내부에서만 노출되도록 제한했습니다.
- 옵션 ON 상태에서 Control Center가 열려 있고 입력이 없으면 8초 후 패널이 자동으로 닫히도록 UI 상태 로직을 추가했습니다.
- 모드 전환 토스트는 추가하지 않고 상태 변화만 반영했습니다.

검증:
- npx tsc --noEmit

세션-상태: 컨트롤 자동 숨김 정책으로 설정 후 자연스럽게 몰입 화면으로 복귀됩니다.
세션-다음: Scene 추천 매핑 품질과 override UX 체감 검증을 진행합니다.
세션-리스크: 자동 닫힘 8초 타이밍은 실사용 피드백에 따라 추가 조정이 필요할 수 있습니다.
This commit is contained in:
2026-03-05 15:19:02 +09:00
parent 3fbeee059a
commit 245746a996
2 changed files with 60 additions and 0 deletions

View File

@@ -10,6 +10,7 @@ import { getRoomCardBackgroundStyle, type RoomTheme } from '@/entities/room';
import type { TimerPreset } from '@/entities/session';
import { cn } from '@/shared/lib/cn';
import { useReducedMotion } from '@/shared/lib/useReducedMotion';
import { Toggle } from '@/shared/ui';
interface ControlCenterSheetWidgetProps {
plan: PlanTier;
@@ -19,6 +20,8 @@ interface ControlCenterSheetWidgetProps {
sceneRecommendedSoundLabel: string;
sceneRecommendedTimerLabel: string;
timerPresets: TimerPreset[];
autoHideControls: boolean;
onAutoHideControlsChange: (next: boolean) => void;
onSelectRoom: (roomId: string) => void;
onSelectTimer: (timerLabel: string) => void;
onLockedClick: (source: string) => void;
@@ -50,6 +53,8 @@ export const ControlCenterSheetWidget = ({
sceneRecommendedSoundLabel,
sceneRecommendedTimerLabel,
timerPresets,
autoHideControls,
onAutoHideControlsChange,
onSelectRoom,
onSelectTimer,
onLockedClick,
@@ -163,6 +168,21 @@ export const ControlCenterSheetWidget = ({
</button>
</div>
<section className="space-y-2 rounded-2xl border border-white/12 bg-black/18 px-3 py-2.5 backdrop-blur-md">
<div className="flex items-center justify-between gap-3">
<div className="min-w-0">
<p className="text-[11px] text-white/72"> </p>
<p className="mt-0.5 text-[10px] text-white/52"> .</p>
</div>
<Toggle
checked={autoHideControls}
onChange={onAutoHideControlsChange}
ariaLabel="컨트롤 자동 숨김"
className="shrink-0"
/>
</div>
</section>
</div>
);
};

View File

@@ -76,6 +76,7 @@ export const SpaceToolsDockWidget = ({
const { pushToast } = useToast();
const [openPopover, setOpenPopover] = useState<SpaceAnchorPopoverId | null>(null);
const [utilityPanel, setUtilityPanel] = useState<SpaceUtilityPanelId | null>(null);
const [autoHideControls, setAutoHideControls] = useState(true);
const [noteDraft, setNoteDraft] = useState('');
const [volumeFeedback, setVolumeFeedback] = useState<string | null>(null);
const [plan, setPlan] = useState<PlanTier>('normal');
@@ -158,6 +159,43 @@ export const SpaceToolsDockWidget = ({
};
}, [isFocusMode, openPopover, utilityPanel]);
useEffect(() => {
if (utilityPanel !== 'control-center' || !autoHideControls) {
return;
}
let timerId: number | null = null;
const closeDelayMs = 8000;
const armCloseTimer = () => {
if (timerId) {
window.clearTimeout(timerId);
}
timerId = window.setTimeout(() => {
setUtilityPanel((current) => (current === 'control-center' ? null : current));
}, closeDelayMs);
};
const resetTimer = () => {
armCloseTimer();
};
armCloseTimer();
window.addEventListener('pointermove', resetTimer);
window.addEventListener('pointerdown', resetTimer);
window.addEventListener('keydown', resetTimer);
return () => {
if (timerId) {
window.clearTimeout(timerId);
}
window.removeEventListener('pointermove', resetTimer);
window.removeEventListener('pointerdown', resetTimer);
window.removeEventListener('keydown', resetTimer);
};
}, [autoHideControls, utilityPanel]);
const openUtilityPanel = (panel: SpaceUtilityPanelId) => {
setOpenPopover(null);
setUtilityPanel(panel);
@@ -414,6 +452,8 @@ export const SpaceToolsDockWidget = ({
sceneRecommendedSoundLabel={sceneRecommendedSoundLabel}
sceneRecommendedTimerLabel={sceneRecommendedTimerLabel}
timerPresets={timerPresets}
autoHideControls={autoHideControls}
onAutoHideControlsChange={setAutoHideControls}
onSelectRoom={(roomId) => {
onRoomSelect(roomId);
}}