Files
hyungi_document_server/frontend/src/lib/stores/uiState.svelte.ts
Hyungi Ahn 8742367bc2 refactor: stores 분리 — toast / uiState 단일 책임화
UX/UI 개편 Phase A-2. lib/stores/ui.ts에 섞여 있던 toast 시스템과
UI layer 상태(미사용 dead export 포함)를 의미 단위로 분리한다.
한 파일이 비대해지는 시나리오를 처음부터 차단(plan 8대 원칙 #7).

- lib/stores/toast.ts 신규 — toasts/addToast/removeToast (Toast interface export)
- lib/stores/uiState.svelte.ts 신규 — drawer 단일 slot + modal stack 클래스 (5대 원칙 #2)
  · openDrawer/closeDrawer/isDrawerOpen
  · openModal/closeTopModal/isModalOpen/modalIndex/topModal
  · handleEscape (modal stack 우선 → drawer)
- lib/stores/ui.ts 삭제 — sidebarOpen/selectedDocId는 어디서도 import되지 않은 dead export였음
- 11개 파일 import 경로 갱신: \$lib/stores/ui → \$lib/stores/toast

uiState는 아직 어디서도 사용 안 함 — Phase B에서 sidebar/meta drawer가 전환될 때
ui.openDrawer('sidebar') 형태로 채택. 동작 변경 0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:26:11 +09:00

58 lines
1.9 KiB
TypeScript

// 중앙 UI layer 상태 — drawer는 단일 slot, modal은 stack.
// drawer/modal/toast 동시 오픈 시 focus/scroll/Esc 충돌을 차단하기 위한 single source.
// (toast는 별도 store. drawer가 persistent inline panel(예: xl+ meta rail)일 때는
// 여기 시스템 밖이다 — 그저 레이아웃의 일부.)
type Drawer = { id: 'sidebar' | 'meta' } | null;
type Modal = { id: string };
class UIState {
drawer = $state<Drawer>(null);
modalStack = $state<Modal[]>([]);
// ── Drawer (단일 slot) ──────────────────────────────
openDrawer(id: 'sidebar' | 'meta') {
// 새 drawer 열면 이전 drawer는 자동으로 사라진다 (단일 slot)
this.drawer = { id };
}
closeDrawer() {
this.drawer = null;
}
isDrawerOpen(id: 'sidebar' | 'meta') {
return this.drawer?.id === id;
}
// ── Modal (stack — confirm 위에 nested 가능) ─────────
openModal(id: string) {
this.modalStack = [...this.modalStack, { id }];
}
closeTopModal() {
this.modalStack = this.modalStack.slice(0, -1);
}
closeModal(id: string) {
this.modalStack = this.modalStack.filter((m) => m.id !== id);
}
isModalOpen(id: string) {
return this.modalStack.some((m) => m.id === id);
}
// 특정 modal의 stack 인덱스 (z-index 계산용; 없으면 -1)
modalIndex(id: string) {
return this.modalStack.findIndex((m) => m.id === id);
}
get topModal() {
return this.modalStack[this.modalStack.length - 1] ?? null;
}
// ── 글로벌 Esc 핸들러 ────────────────────────────────
// 우선순위: 가장 위 modal → drawer
handleEscape() {
if (this.modalStack.length > 0) {
this.closeTopModal();
} else if (this.drawer) {
this.closeDrawer();
}
}
}
export const ui = new UIState();