feat(immersion): 몰입 모드 상단 액션을 나가기로 전환

맥락:

- 몰입 모드 ON에서 상단 우측 액션이 허브 이동으로 보이는 혼선을 줄이고 즉시 이탈 동선을 제공하기 위해

변경사항:

- 몰입 모드 훅에 exitImmersionMode 액션 추가

- 몰입 모드 ON 시 상단 우측 액션을 나가기 버튼으로 전환

- 나가기 클릭 시 토스트(나가기(더미)) 노출 후 몰입 모드 OFF 처리

- docs/90_current_state.md, docs/session_brief.md 최신 상태 반영

검증:

- npx tsc --noEmit

세션-상태: 몰입 모드 ON에서 상단 우측 액션은 나가기로 동작함

세션-다음: RoomSheet/도크의 인원수 중심 정보를 분위기형 정보로 교체

세션-리스크: 터치 환경에서 미니 레일 발견성이 낮을 수 있어 UX 보완이 필요
This commit is contained in:
2026-02-27 14:22:56 +09:00
parent e361755a91
commit e2fb720a55
5 changed files with 43 additions and 18 deletions

View File

@@ -22,6 +22,12 @@ Last Updated: 2026-02-27
- `/space` 하단 사운드 프리셋 바 제거, 오른쪽 `🎧 Sound` 시트로 이동 - `/space` 하단 사운드 프리셋 바 제거, 오른쪽 `🎧 Sound` 시트로 이동
- `features/sound-preset` + `widgets/sound-sheet` 추가 - `features/sound-preset` + `widgets/sound-sheet` 추가
- `features/immersion-mode` 추가, Quick 시트에서 몰입 모드 토글 연결 - `features/immersion-mode` 추가, Quick 시트에서 몰입 모드 토글 연결
- `/space` 상단 헤더 크롬 최소화:
- 헤더 프레임(border/강한 배경) 제거
- 패딩 축소로 배경 노출 증가
- 타이머 HUD 하단 위치를 safe-area 기반 최소 여백으로 조정
- 몰입 모드 ON 시 상단 액션을 `나가기` 버튼으로 전환
- 클릭 시 토스트 `나가기(더미)` 노출 + 몰입 모드 OFF
- 몰입 모드 ON 시 `/space` 크롬 정리: - 몰입 모드 ON 시 `/space` 크롬 정리:
- 상단 `Current Room` 블록 숨김 - 상단 `Current Room` 블록 숨김
- 우상단 허브 버튼 소형 아이콘화 - 우상단 허브 버튼 소형 아이콘화
@@ -39,12 +45,14 @@ Last Updated: 2026-02-27
1. `RoomSheetWidget`/도크 패널의 인원수 기반 UI를 큐레이션형 정보로 재정의 1. `RoomSheetWidget`/도크 패널의 인원수 기반 UI를 큐레이션형 정보로 재정의
2. 몰입 모드에서 터치 환경(hover 없음) 레일 노출 UX를 보완할지 정책 확정 2. 몰입 모드에서 터치 환경(hover 없음) 레일 노출 UX를 보완할지 정책 확정
3. `/space` 헤더 최소화 스타일을 테마별(밝은 배경) 대비 점검
## RISKS ## RISKS
- `npm run build`는 네트워크 제한 시 Google Font fetch 실패 가능 - `npm run build`는 네트워크 제한 시 Google Font fetch 실패 가능
- 터치 기기에서 레일 미니 상태가 발견성 낮을 수 있어 추가 힌트가 필요할 수 있음 - 터치 기기에서 레일 미니 상태가 발견성 낮을 수 있어 추가 힌트가 필요할 수 있음
- 일부 시트(예: Room)는 아직 인원수 중심 문구가 남아 있어 톤 불일치 가능성 존재 - 일부 시트(예: Room)는 아직 인원수 중심 문구가 남아 있어 톤 불일치 가능성 존재
- safe-area 값이 작은 기기에서는 HUD가 너무 낮게 느껴질 수 있어 세부 조정 여지 존재
## CHANGED FILES ## CHANGED FILES
@@ -81,6 +89,8 @@ Last Updated: 2026-02-27
- `src/widgets/space-tools-dock/model/useSpaceToolsDock.ts` - `src/widgets/space-tools-dock/model/useSpaceToolsDock.ts`
- `src/widgets/space-tools-dock/ui/SpaceToolsDockWidget.tsx` - `src/widgets/space-tools-dock/ui/SpaceToolsDockWidget.tsx`
- `src/widgets/space-timer-hud/ui/SpaceTimerHudWidget.tsx` - `src/widgets/space-timer-hud/ui/SpaceTimerHudWidget.tsx`
- `src/widgets/space-chrome/ui/SpaceChromeWidget.tsx`
- `src/features/immersion-mode/model/useImmersionMode.ts`
## QUICK VERIFY ## QUICK VERIFY

View File

@@ -16,12 +16,15 @@ Last Updated: 2026-02-27
1. `RoomSheetWidget`/도크 패널의 인원 수 UI를 큐레이션형으로 전환 1. `RoomSheetWidget`/도크 패널의 인원 수 UI를 큐레이션형으로 전환
2. 몰입 모드에서 터치 환경 레일 발견성(미니 핸들 UX) 보완 여부 결정 2. 몰입 모드에서 터치 환경 레일 발견성(미니 핸들 UX) 보완 여부 결정
3. `/space` 헤더 최소화 스타일의 밝은 배경 대비 점검
## 최근 세션 상태 ## 최근 세션 상태
- 세션 복구용 문서/템플릿/스크립트가 준비되어 있다. - 세션 복구용 문서/템플릿/스크립트가 준비되어 있다.
- `workFlow.md`는 토큰 절약 모드를 사용한다. - `workFlow.md`는 토큰 절약 모드를 사용한다.
- `/space` 하단 사운드 바를 제거하고 오른쪽 `🎧 Sound` 시트로 이동했다. - `/space` 하단 사운드 바를 제거하고 오른쪽 `🎧 Sound` 시트로 이동했다.
- `/space` 헤더 프레임을 축소하고 HUD를 하단 safe-area 기준으로 더 밀착시켰다.
- 몰입 모드 ON에서 상단 우측 액션을 `나가기`로 전환했고, 클릭 시 토스트(더미)와 함께 몰입 모드 OFF 처리한다.
- 몰입 모드 ON 시 상단 룸 블록 숨김, 레일 미니화, HUD 저대비, 비네팅 강화가 적용된다. - 몰입 모드 ON 시 상단 룸 블록 숨김, 레일 미니화, HUD 저대비, 비네팅 강화가 적용된다.
- 이후 작업은 `docs/work.md`를 기준으로 실행한다. - 이후 작업은 `docs/work.md`를 기준으로 실행한다.
@@ -29,6 +32,7 @@ Last Updated: 2026-02-27
- 네트워크 제한 환경에서는 `npm run build` 시 Google Fonts fetch 실패 가능 - 네트워크 제한 환경에서는 `npm run build` 시 Google Fonts fetch 실패 가능
- 터치 환경에서 레일 미니 상태가 발견성 낮을 수 있어 UX 보완이 필요할 수 있음 - 터치 환경에서 레일 미니 상태가 발견성 낮을 수 있어 UX 보완이 필요할 수 있음
- safe-area가 작은 기기에서는 HUD 하단 간격 체감이 과도할 수 있어 미세 조정이 필요할 수 있음
## 상세 원문 위치 ## 상세 원문 위치

View File

@@ -1,16 +1,24 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState } from 'react';
import { useToast } from '@/shared/ui';
export const useImmersionMode = () => { export const useImmersionMode = () => {
const { pushToast } = useToast();
const [isImmersionMode, setImmersionMode] = useState(false); const [isImmersionMode, setImmersionMode] = useState(false);
const toggleImmersionMode = () => { const toggleImmersionMode = () => {
setImmersionMode((current) => !current); setImmersionMode((current) => !current);
}; };
const exitImmersionMode = () => {
setImmersionMode(false);
pushToast({ title: '나가기(더미)' });
};
return { return {
isImmersionMode, isImmersionMode,
toggleImmersionMode, toggleImmersionMode,
exitImmersionMode,
}; };
}; };

View File

@@ -4,12 +4,14 @@ interface SpaceChromeWidgetProps {
roomName: string; roomName: string;
vibeLabel: string; vibeLabel: string;
isImmersionMode: boolean; isImmersionMode: boolean;
onExitImmersionMode: () => void;
} }
export const SpaceChromeWidget = ({ export const SpaceChromeWidget = ({
roomName, roomName,
vibeLabel, vibeLabel,
isImmersionMode, isImmersionMode,
onExitImmersionMode,
}: SpaceChromeWidgetProps) => { }: SpaceChromeWidgetProps) => {
return ( return (
<div className="px-4 pt-2 sm:px-6"> <div className="px-4 pt-2 sm:px-6">
@@ -21,23 +23,23 @@ export const SpaceChromeWidget = ({
<p className="text-sm font-medium tracking-tight text-white/90">VibeRoom</p> <p className="text-sm font-medium tracking-tight text-white/90">VibeRoom</p>
</div> </div>
<Link {isImmersionMode ? (
href="/app" <button
className={ type="button"
isImmersionMode onClick={onExitImmersionMode}
? 'inline-flex h-7 w-7 items-center justify-center rounded-md bg-white/8 text-[11px] text-white/74 transition hover:bg-white/14 hover:text-white' className="inline-flex items-center gap-1 rounded-md bg-white/8 px-2 py-1 text-[11px] text-white/74 transition hover:bg-white/14 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-200/80"
: 'rounded-lg bg-white/8 px-2.5 py-1.5 text-xs text-white/82 transition hover:bg-white/14 hover:text-white' >
} <span aria-hidden></span>
> <span></span>
{isImmersionMode ? ( </button>
<> ) : (
<span aria-hidden></span> <Link
<span className="sr-only"> </span> href="/app"
</> className="rounded-lg bg-white/8 px-2.5 py-1.5 text-xs text-white/82 transition hover:bg-white/14 hover:text-white"
) : ( >
'허브로 돌아가기'
)} </Link>
</Link> )}
</header> </header>
{!isImmersionMode ? ( {!isImmersionMode ? (

View File

@@ -19,7 +19,7 @@ export const SpaceSkeletonWidget = () => {
const soundFromQuery = searchParams.get('sound'); const soundFromQuery = searchParams.get('sound');
const room = useMemo(() => getRoomById(roomId) ?? ROOM_THEMES[0], [roomId]); const room = useMemo(() => getRoomById(roomId) ?? ROOM_THEMES[0], [roomId]);
const { isImmersionMode, toggleImmersionMode } = useImmersionMode(); const { isImmersionMode, toggleImmersionMode, exitImmersionMode } = useImmersionMode();
const initialSoundPresetId = const initialSoundPresetId =
SOUND_PRESETS.find((preset) => preset.id === soundFromQuery)?.id ?? SOUND_PRESETS.find((preset) => preset.id === soundFromQuery)?.id ??
SOUND_PRESETS[0].id; SOUND_PRESETS[0].id;
@@ -66,6 +66,7 @@ export const SpaceSkeletonWidget = () => {
roomName={room.name} roomName={room.name}
vibeLabel={room.vibeLabel} vibeLabel={room.vibeLabel}
isImmersionMode={isImmersionMode} isImmersionMode={isImmersionMode}
onExitImmersionMode={exitImmersionMode}
/> />
<main className="flex-1" /> <main className="flex-1" />