refactor(space): 단일 워크스페이스 Setup→Focus 전환 구조 도입

맥락:
- 허브를 경유하는 흐름 대신 /space 한 화면에서 설정과 몰입을 이어서 처리할 필요가 있었습니다.
- View 로직 분리와 파일 분할 기준을 지키면서 도크/시트 패턴을 통합해야 했습니다.

변경사항:
- /space를 Setup(기본)과 Focus(시작 후) 2상태로 운영하는 space-workspace 위젯을 추가했습니다.
- Setup Drawer를 추가해 Space 선택, Goal(필수), Sound(선택) 섹션과 하단 고정 CTA를 구성했습니다.
- Goal 입력이 비어있으면 시작하기 버튼이 비활성화되도록 UI 검증을 반영했습니다.
- Focus 상태에서 하단 HUD만 유지하고 우측 Tools Dock(🎧/📝/📨/📊/⚙) + 우측 시트 패턴을 적용했습니다.
- Notes(쓰기)와 Inbox(읽기) 패널을 분리하고 더미 토스트 동작을 연결했습니다.
- FSD 분리를 위해 features(space-select/session-goal/inbox)와 widgets(space-workspace/space-setup-drawer/space-focus-hud/space-sheet-shell)를 추가했습니다.
- 기존 space-shell은 신규 워크스페이스로 연결되는 얇은 래퍼로 정리했습니다.

검증:
- npx tsc --noEmit
- npm run build

세션-상태: /space 단일 워크스페이스에서 Setup→Focus 전환이 동작합니다.
세션-다음: 진입 경로를 /space로 통일하고 레거시 /app 라우트를 정리합니다.
세션-리스크: useSearchParams 기반 초기값은 클라이언트 최초 렌더 기준으로만 반영됩니다.
This commit is contained in:
2026-03-02 12:49:47 +09:00
parent a2bebb3485
commit 2718997735
24 changed files with 898 additions and 303 deletions

View File

@@ -0,0 +1,65 @@
'use client';
import { useState } from 'react';
import { DEFAULT_PRESET_OPTIONS } from '@/shared/config/settingsOptions';
import { cn } from '@/shared/lib/cn';
export const SettingsToolPanel = () => {
const [reduceMotion, setReduceMotion] = useState(false);
const [defaultPresetId, setDefaultPresetId] = useState<
(typeof DEFAULT_PRESET_OPTIONS)[number]['id']
>(DEFAULT_PRESET_OPTIONS[0].id);
return (
<div className="space-y-4">
<section className="rounded-2xl border border-white/14 bg-white/7 px-3.5 py-3">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-sm font-medium text-white">Reduce Motion</p>
<p className="mt-1 text-xs text-white/58"> .</p>
</div>
<button
type="button"
role="switch"
aria-checked={reduceMotion}
onClick={() => setReduceMotion((current) => !current)}
className={cn(
'inline-flex w-14 items-center rounded-full border px-1 py-1 transition-colors',
reduceMotion
? 'border-sky-200/44 bg-sky-200/24'
: 'border-white/24 bg-white/9',
)}
>
<span
className={cn(
'h-4.5 w-4.5 rounded-full bg-white transition-transform duration-200 motion-reduce:transition-none',
reduceMotion ? 'translate-x-7' : 'translate-x-0',
)}
/>
</button>
</div>
</section>
<section className="rounded-2xl border border-white/14 bg-white/7 px-3.5 py-3">
<p className="text-sm font-medium text-white"> </p>
<div className="mt-2 flex flex-wrap gap-2">
{DEFAULT_PRESET_OPTIONS.map((preset) => (
<button
key={preset.id}
type="button"
onClick={() => setDefaultPresetId(preset.id)}
className={cn(
'rounded-full border px-3 py-1.5 text-xs transition-colors',
defaultPresetId === preset.id
? 'border-sky-200/44 bg-sky-200/20 text-sky-100'
: 'border-white/18 bg-white/8 text-white/80 hover:bg-white/14',
)}
>
{preset.label}
</button>
))}
</div>
</section>
</div>
);
};