fix(space-ui): /space 포커스 앵커 잘림과 스크롤 문제 수정
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import {
|
||||
getRoomBackgroundStyle,
|
||||
@@ -10,8 +10,10 @@ import {
|
||||
import {
|
||||
GOAL_CHIPS,
|
||||
SOUND_PRESETS,
|
||||
TIMER_PRESETS,
|
||||
useThoughtInbox,
|
||||
type GoalChip,
|
||||
type TimerPreset,
|
||||
} from '@/entities/session';
|
||||
import { useSoundPresetSelection } from '@/features/sound-preset';
|
||||
import { useToast } from '@/shared/ui';
|
||||
@@ -37,6 +39,19 @@ const resolveInitialSoundPreset = (presetIdFromQuery: string | null) => {
|
||||
return SOUND_PRESETS[0].id;
|
||||
};
|
||||
|
||||
const TIMER_SELECTION_PRESETS = TIMER_PRESETS.filter(
|
||||
(preset): preset is TimerPreset & { focusMinutes: number; breakMinutes: number } =>
|
||||
typeof preset.focusMinutes === 'number' && typeof preset.breakMinutes === 'number',
|
||||
).slice(0, 3);
|
||||
|
||||
const resolveInitialTimerLabel = (timerLabelFromQuery: string | null) => {
|
||||
if (timerLabelFromQuery && TIMER_SELECTION_PRESETS.some((preset) => preset.label === timerLabelFromQuery)) {
|
||||
return timerLabelFromQuery;
|
||||
}
|
||||
|
||||
return TIMER_SELECTION_PRESETS[0]?.label ?? '25/5';
|
||||
};
|
||||
|
||||
export const SpaceWorkspaceWidget = () => {
|
||||
const searchParams = useSearchParams();
|
||||
const { pushToast } = useToast();
|
||||
@@ -45,33 +60,31 @@ export const SpaceWorkspaceWidget = () => {
|
||||
const initialRoomId = resolveInitialRoomId(searchParams.get('room'));
|
||||
const initialGoal = searchParams.get('goal')?.trim() ?? '';
|
||||
const initialSoundPresetId = resolveInitialSoundPreset(searchParams.get('sound'));
|
||||
const initialTimerLabel = resolveInitialTimerLabel(searchParams.get('timer'));
|
||||
|
||||
const [workspaceMode, setWorkspaceMode] = useState<WorkspaceMode>('setup');
|
||||
const [isSetupDrawerOpen, setSetupDrawerOpen] = useState(true);
|
||||
const [selectedRoomId, setSelectedRoomId] = useState(initialRoomId);
|
||||
const [selectedTimerLabel, setSelectedTimerLabel] = useState(initialTimerLabel);
|
||||
const [goalInput, setGoalInput] = useState(initialGoal);
|
||||
const [selectedGoalId, setSelectedGoalId] = useState<string | null>(null);
|
||||
|
||||
const {
|
||||
selectedPresetId,
|
||||
setSelectedPresetId,
|
||||
isMixerOpen,
|
||||
setMixerOpen,
|
||||
isMuted,
|
||||
setMuted,
|
||||
masterVolume,
|
||||
setMasterVolume,
|
||||
trackLevels,
|
||||
setTrackLevel,
|
||||
trackKeys,
|
||||
} = useSoundPresetSelection(initialSoundPresetId);
|
||||
|
||||
const selectedRoom = useMemo(() => {
|
||||
return getRoomById(selectedRoomId) ?? ROOM_THEMES[0];
|
||||
}, [selectedRoomId]);
|
||||
|
||||
const setupRooms = useMemo(() => {
|
||||
const restRooms = ROOM_THEMES.filter((room) => room.id !== selectedRoom.id);
|
||||
return [selectedRoom, ...restRooms].slice(0, 4);
|
||||
const visibleRooms = ROOM_THEMES.slice(0, 6);
|
||||
|
||||
if (visibleRooms.some((room) => room.id === selectedRoom.id)) {
|
||||
return visibleRooms;
|
||||
}
|
||||
|
||||
return [selectedRoom, ...visibleRooms].slice(0, 6);
|
||||
}, [selectedRoom]);
|
||||
|
||||
const canStart = goalInput.trim().length > 0;
|
||||
@@ -96,19 +109,32 @@ export const SpaceWorkspaceWidget = () => {
|
||||
}
|
||||
|
||||
setWorkspaceMode('focus');
|
||||
setSetupDrawerOpen(false);
|
||||
|
||||
pushToast({
|
||||
title: `목표: ${goalInput.trim()} 시작해요.`,
|
||||
});
|
||||
};
|
||||
|
||||
const handleOpenSetup = () => {
|
||||
setSetupDrawerOpen(true);
|
||||
const handleExitRequested = () => {
|
||||
setWorkspaceMode('setup');
|
||||
pushToast({ title: '준비 모드로 돌아왔어요.' });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const previousBodyOverflow = document.body.style.overflow;
|
||||
const previousHtmlOverflow = document.documentElement.style.overflow;
|
||||
|
||||
document.body.style.overflow = 'hidden';
|
||||
document.documentElement.style.overflow = 'hidden';
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = previousBodyOverflow;
|
||||
document.documentElement.style.overflow = previousHtmlOverflow;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen overflow-hidden text-white">
|
||||
<div className="relative h-dvh overflow-hidden text-white">
|
||||
<div
|
||||
aria-hidden
|
||||
className="absolute inset-0 bg-cover bg-center will-change-transform animate-[space-stage-pan_42s_ease-in-out_infinite_alternate] motion-reduce:animate-none"
|
||||
@@ -135,72 +161,51 @@ export const SpaceWorkspaceWidget = () => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative z-10 flex min-h-screen flex-col pr-[4.25rem]">
|
||||
<header className="flex items-center justify-between px-4 pt-3.5 sm:px-6">
|
||||
{!isFocusMode ? (
|
||||
<div className="rounded-full border border-white/16 bg-slate-950/32 px-3 py-1.5 backdrop-blur-xl">
|
||||
<p className="text-xs font-semibold tracking-tight text-white/88">VibeRoom</p>
|
||||
</div>
|
||||
) : (
|
||||
<div aria-hidden className="h-7" />
|
||||
)}
|
||||
|
||||
{isFocusMode && !isSetupDrawerOpen ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenSetup}
|
||||
className="rounded-full border border-white/16 bg-slate-950/24 px-2.5 py-1 text-[11px] text-white/58 transition-colors hover:bg-slate-950/40 hover:text-white/84"
|
||||
>
|
||||
Setup 열기
|
||||
</button>
|
||||
) : null}
|
||||
</header>
|
||||
|
||||
<div className="relative z-10 flex h-full flex-col">
|
||||
<main className="relative flex-1" />
|
||||
</div>
|
||||
|
||||
<SpaceSetupDrawerWidget
|
||||
open={isSetupDrawerOpen}
|
||||
dismissible={isFocusMode}
|
||||
open={!isFocusMode}
|
||||
rooms={setupRooms}
|
||||
selectedRoomId={selectedRoom.id}
|
||||
selectedTimerLabel={selectedTimerLabel}
|
||||
selectedSoundPresetId={selectedPresetId}
|
||||
goalInput={goalInput}
|
||||
selectedGoalId={selectedGoalId}
|
||||
selectedSoundPresetId={selectedPresetId}
|
||||
goalChips={GOAL_CHIPS}
|
||||
soundPresets={SOUND_PRESETS}
|
||||
timerPresets={TIMER_SELECTION_PRESETS}
|
||||
canStart={canStart}
|
||||
onClose={() => {
|
||||
if (isFocusMode) {
|
||||
setSetupDrawerOpen(false);
|
||||
}
|
||||
}}
|
||||
onRoomSelect={setSelectedRoomId}
|
||||
onTimerSelect={setSelectedTimerLabel}
|
||||
onSoundSelect={setSelectedPresetId}
|
||||
onGoalChange={handleGoalChange}
|
||||
onGoalChipSelect={handleGoalChipSelect}
|
||||
onSoundSelect={setSelectedPresetId}
|
||||
onStart={handleStart}
|
||||
/>
|
||||
|
||||
<SpaceFocusHudWidget goal={goalInput.trim()} visible={isFocusMode} />
|
||||
<SpaceFocusHudWidget
|
||||
goal={goalInput.trim()}
|
||||
timerLabel={selectedTimerLabel}
|
||||
visible={isFocusMode}
|
||||
/>
|
||||
|
||||
<SpaceToolsDockWidget
|
||||
isFocusMode={isFocusMode}
|
||||
rooms={setupRooms}
|
||||
selectedRoomId={selectedRoom.id}
|
||||
selectedTimerLabel={selectedTimerLabel}
|
||||
timerPresets={TIMER_SELECTION_PRESETS}
|
||||
thoughts={thoughts}
|
||||
thoughtCount={thoughtCount}
|
||||
selectedPresetId={selectedPresetId}
|
||||
onRoomSelect={setSelectedRoomId}
|
||||
onTimerSelect={setSelectedTimerLabel}
|
||||
onSelectPreset={setSelectedPresetId}
|
||||
isMixerOpen={isMixerOpen}
|
||||
onToggleMixer={() => setMixerOpen((current) => !current)}
|
||||
isMuted={isMuted}
|
||||
onMuteChange={setMuted}
|
||||
masterVolume={masterVolume}
|
||||
onMasterVolumeChange={setMasterVolume}
|
||||
trackKeys={trackKeys}
|
||||
trackLevels={trackLevels}
|
||||
onTrackLevelChange={setTrackLevel}
|
||||
onCaptureThought={(note) => addThought(note, selectedRoom.name)}
|
||||
onClearInbox={clearThoughts}
|
||||
onExitRequested={handleExitRequested}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user