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:
@@ -10,6 +10,7 @@ import { getRoomCardBackgroundStyle, type RoomTheme } from '@/entities/room';
|
|||||||
import type { TimerPreset } from '@/entities/session';
|
import type { TimerPreset } from '@/entities/session';
|
||||||
import { cn } from '@/shared/lib/cn';
|
import { cn } from '@/shared/lib/cn';
|
||||||
import { useReducedMotion } from '@/shared/lib/useReducedMotion';
|
import { useReducedMotion } from '@/shared/lib/useReducedMotion';
|
||||||
|
import { Toggle } from '@/shared/ui';
|
||||||
|
|
||||||
interface ControlCenterSheetWidgetProps {
|
interface ControlCenterSheetWidgetProps {
|
||||||
plan: PlanTier;
|
plan: PlanTier;
|
||||||
@@ -19,6 +20,8 @@ interface ControlCenterSheetWidgetProps {
|
|||||||
sceneRecommendedSoundLabel: string;
|
sceneRecommendedSoundLabel: string;
|
||||||
sceneRecommendedTimerLabel: string;
|
sceneRecommendedTimerLabel: string;
|
||||||
timerPresets: TimerPreset[];
|
timerPresets: TimerPreset[];
|
||||||
|
autoHideControls: boolean;
|
||||||
|
onAutoHideControlsChange: (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;
|
||||||
@@ -50,6 +53,8 @@ export const ControlCenterSheetWidget = ({
|
|||||||
sceneRecommendedSoundLabel,
|
sceneRecommendedSoundLabel,
|
||||||
sceneRecommendedTimerLabel,
|
sceneRecommendedTimerLabel,
|
||||||
timerPresets,
|
timerPresets,
|
||||||
|
autoHideControls,
|
||||||
|
onAutoHideControlsChange,
|
||||||
onSelectRoom,
|
onSelectRoom,
|
||||||
onSelectTimer,
|
onSelectTimer,
|
||||||
onLockedClick,
|
onLockedClick,
|
||||||
@@ -163,6 +168,21 @@ export const ControlCenterSheetWidget = ({
|
|||||||
추천으로 되돌리기
|
추천으로 되돌리기
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export const SpaceToolsDockWidget = ({
|
|||||||
const { pushToast } = useToast();
|
const { pushToast } = useToast();
|
||||||
const [openPopover, setOpenPopover] = useState<SpaceAnchorPopoverId | null>(null);
|
const [openPopover, setOpenPopover] = useState<SpaceAnchorPopoverId | null>(null);
|
||||||
const [utilityPanel, setUtilityPanel] = useState<SpaceUtilityPanelId | null>(null);
|
const [utilityPanel, setUtilityPanel] = useState<SpaceUtilityPanelId | null>(null);
|
||||||
|
const [autoHideControls, setAutoHideControls] = useState(true);
|
||||||
const [noteDraft, setNoteDraft] = useState('');
|
const [noteDraft, setNoteDraft] = useState('');
|
||||||
const [volumeFeedback, setVolumeFeedback] = useState<string | null>(null);
|
const [volumeFeedback, setVolumeFeedback] = useState<string | null>(null);
|
||||||
const [plan, setPlan] = useState<PlanTier>('normal');
|
const [plan, setPlan] = useState<PlanTier>('normal');
|
||||||
@@ -158,6 +159,43 @@ export const SpaceToolsDockWidget = ({
|
|||||||
};
|
};
|
||||||
}, [isFocusMode, openPopover, utilityPanel]);
|
}, [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) => {
|
const openUtilityPanel = (panel: SpaceUtilityPanelId) => {
|
||||||
setOpenPopover(null);
|
setOpenPopover(null);
|
||||||
setUtilityPanel(panel);
|
setUtilityPanel(panel);
|
||||||
@@ -414,6 +452,8 @@ export const SpaceToolsDockWidget = ({
|
|||||||
sceneRecommendedSoundLabel={sceneRecommendedSoundLabel}
|
sceneRecommendedSoundLabel={sceneRecommendedSoundLabel}
|
||||||
sceneRecommendedTimerLabel={sceneRecommendedTimerLabel}
|
sceneRecommendedTimerLabel={sceneRecommendedTimerLabel}
|
||||||
timerPresets={timerPresets}
|
timerPresets={timerPresets}
|
||||||
|
autoHideControls={autoHideControls}
|
||||||
|
onAutoHideControlsChange={setAutoHideControls}
|
||||||
onSelectRoom={(roomId) => {
|
onSelectRoom={(roomId) => {
|
||||||
onRoomSelect(roomId);
|
onRoomSelect(roomId);
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user