feat(ui): Phase D.1 — 3-panel layout + DocumentMetaRail + useMedia
- 가로 flex 최상위 + 가운데 flex-1 (기존 list/viewer 세로 split 그대로 보존) - xl+ (≥1280px): 우측 320px persistent rail, 접기 시 40px sliver. localStorage.metaRailOpen 으로 상태 유지. - < xl : 기존 수동 drawer 제거하고 ui/Drawer primitive + uiState 사용. - 리사이즈 시 xl+ 진입하면 drawer 자동 close (rail로 승계). - handleKeydown → ui.handleEscape() 로 중앙화. - ℹ 버튼 token 기반 재작성 (isXl 분기로 rail/drawer 토글). - PreviewPanel.svelte 한 글자도 수정 없음 (Phase E 영역). 신규: - frontend/src/lib/composables/useMedia.svelte.ts — matchMedia runes 컴포저블 - frontend/src/lib/components/DocumentMetaRail.svelte — PreviewPanel wrapper 검증: - npm run build 통과 - npm run lint:tokens 241 → 236 (신규 코드 0 위반, 레거시 drawer/ℹ 버튼 제거로 5건 organically 감소) - PreviewPanel diff 0줄 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
27
frontend/src/lib/components/DocumentMetaRail.svelte
Normal file
27
frontend/src/lib/components/DocumentMetaRail.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
// Phase D.1 신규 — 얇은 wrapper.
|
||||
// - PreviewPanel을 그대로 import해서 rail/drawer 양쪽 컨텍스트에서 재사용.
|
||||
// - Phase E에서 editors/* 로 분할될 때 이 wrapper 자체는 유지되고
|
||||
// 내부만 <NoteEditor/>, <TagsEditor/>... 조합으로 교체된다.
|
||||
// - rail 모드(xl+ inline)와 drawer 모드(< xl)에서 똑같이 사용되며,
|
||||
// onclose 콜백만 부모가 다르게 준다:
|
||||
// rail → metaRailOpen = false + localStorage 저장
|
||||
// drawer → ui.closeDrawer()
|
||||
//
|
||||
// PreviewPanel 은 자기 <aside> 내부에 bg-sidebar/border-l 스타일을 이미
|
||||
// 갖고 있으므로, 여기서는 최소 <div> flex wrapper만 씌운다. border 중복
|
||||
// 방지.
|
||||
import PreviewPanel from './PreviewPanel.svelte';
|
||||
|
||||
interface Props {
|
||||
doc: unknown;
|
||||
onclose: () => void;
|
||||
ondelete?: () => void;
|
||||
}
|
||||
|
||||
let { doc, onclose, ondelete = () => {} }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="h-full w-full flex flex-col">
|
||||
<PreviewPanel {doc} {onclose} {ondelete} />
|
||||
</div>
|
||||
38
frontend/src/lib/composables/useMedia.svelte.ts
Normal file
38
frontend/src/lib/composables/useMedia.svelte.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// matchMedia 기반 runes 반응형 flag. 컴포넌트 script 최상위에서만 호출해야
|
||||
// $effect가 owner를 찾는다. SSR에서는 초기값 false로 가정하고, 클라이언트
|
||||
// mount 후 실제 값으로 업데이트.
|
||||
//
|
||||
// Phase D.1 신규 — documents/+page.svelte 에서 xl 브레이크포인트 기반으로
|
||||
// 메타 rail(persistent) 과 Drawer(폴백) 중 어느 쪽을 쓸지 분기.
|
||||
//
|
||||
// 사용:
|
||||
// import { useIsXl } from '$lib/composables/useMedia.svelte';
|
||||
// const isXl = useIsXl();
|
||||
// {#if isXl.current} ... {/if}
|
||||
//
|
||||
// Tailwind v4 xl breakpoint = 1280px (app.css @theme 기본값과 일치).
|
||||
|
||||
export interface MediaFlag {
|
||||
readonly current: boolean;
|
||||
}
|
||||
|
||||
export function useMedia(query: string): MediaFlag {
|
||||
let matches = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
const mql = window.matchMedia(query);
|
||||
matches = mql.matches;
|
||||
const handler = (e: MediaQueryListEvent) => (matches = e.matches);
|
||||
mql.addEventListener('change', handler);
|
||||
return () => mql.removeEventListener('change', handler);
|
||||
});
|
||||
|
||||
return {
|
||||
get current() {
|
||||
return matches;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const useIsXl = (): MediaFlag => useMedia('(min-width: 1280px)');
|
||||
Reference in New Issue
Block a user