fix(study/sources): 모바일 카테고리 진입 — drawer + breadcrumb

증상: 모바일에서 좌측 트리 hidden md:block 으로 숨겨져 정렬된 최근
자료 외에는 원하는 카테고리를 찾기 어려움.

Fix:
- 헤더 아래 모바일 전용 (md:hidden) 카테고리 진입 바:
  · "카테고리" 버튼 (FolderTree 아이콘) — 좌측 drawer 띄움
  · breadcrumb: 전체 / 가스기사 / 01_유체역학 / 01_basics
    각 segment 클릭 시 해당 path 로 즉시 이동
  · 가로 스크롤 (overflow-x-auto) — 깊은 path 도 자연스럽게
- aside 좌측 트리 모바일 drawer 화:
  · mobileTreeOpen state. fixed left-0 top-0 bottom-0 w-72 max-w-[85vw]
  · 백드롭 클릭 / X 버튼 / 카테고리 선택 시 자동 닫기
  · 데스크톱(md+)에선 기존 normal layout 유지
- navigateAndClose 헬퍼 — 카테고리 클릭 시 navigate + close 한 번에
This commit is contained in:
Hyungi Ahn
2026-04-27 12:51:43 +09:00
parent 6e3ce91de6
commit 8f1c7175d4
+78 -12
View File
@@ -16,7 +16,7 @@
import { api } from '$lib/api';
import { addToast } from '$lib/stores/toast';
import {
ChevronRight, ChevronDown, FolderOpen, BookOpen, ArrowLeft,
ChevronRight, ChevronDown, FolderOpen, BookOpen, ArrowLeft, FolderTree, X,
} from 'lucide-svelte';
import DocumentCard from '$lib/components/DocumentCard.svelte';
import EmptyState from '$lib/components/ui/EmptyState.svelte';
@@ -124,33 +124,98 @@
function toggleExpand(path) {
expanded[path] = !expanded[path];
}
// 모바일 트리 drawer
let mobileTreeOpen = $state(false);
function openTree() { mobileTreeOpen = true; }
function closeTree() { mobileTreeOpen = false; }
// 카테고리 선택 시 drawer 자동 닫기 (모바일)
function navigateAndClose(path) {
navigate(path);
mobileTreeOpen = false;
}
// 현재 경로 breadcrumb 분해
let crumbs = $derived(activePath ? activePath.split('/') : []);
</script>
<svelte:head><title>자료 학습 — 공부</title></svelte:head>
<div class="h-full flex flex-col">
<!-- 헤더 -->
<div class="flex items-center justify-between gap-2 px-4 py-3 border-b border-default bg-surface shrink-0">
<div class="flex items-center gap-2 text-sm">
<a href="/study" class="text-dim hover:text-text flex items-center gap-1">
<div class="flex items-center justify-between gap-2 px-3 md:px-4 py-2 md:py-3 border-b border-default bg-surface shrink-0">
<div class="flex items-center gap-2 text-xs md:text-sm min-w-0">
<a href="/study" class="text-dim hover:text-text flex items-center gap-1 shrink-0">
<ArrowLeft size={14} /> 공부
</a>
<span class="text-faint">/</span>
<span class="text-text font-medium flex items-center gap-1.5">
<span class="text-faint shrink-0">/</span>
<span class="text-text font-medium flex items-center gap-1.5 shrink-0">
<BookOpen size={14} class="text-accent" /> 자료 학습
</span>
</div>
<div class="text-xs text-dim">
{totalCount} · <span class="text-text">안 본 {totalUnread}</span>
<div class="text-[10px] md:text-xs text-dim shrink-0">
{totalCount} · <span class="text-text">안 본 {totalUnread}</span>
</div>
</div>
<div class="flex-1 min-h-0 grid grid-cols-1 md:grid-cols-[260px_1fr] gap-0">
<!-- 좌측 트리 -->
<aside class="border-r border-default overflow-y-auto p-2 hidden md:block">
<!-- 모바일 전용: 카테고리 진입 + breadcrumb -->
<div class="md:hidden flex items-center gap-2 px-3 py-2 border-b border-default bg-surface shrink-0 overflow-x-auto">
<button
type="button"
onclick={openTree}
style="touch-action: manipulation; user-select: none; -webkit-tap-highlight-color: transparent;"
class="flex items-center gap-1 px-3 py-1.5 rounded text-xs bg-accent/10 text-accent border border-accent/30 shrink-0"
aria-label="카테고리 선택"
>
<FolderTree size={14} /> 카테고리
</button>
{#if crumbs.length > 0}
<button
type="button"
onclick={() => navigate(null)}
class="text-xs text-dim hover:text-text shrink-0"
>전체</button>
{#each crumbs as part, i}
<span class="text-faint shrink-0">/</span>
<button
type="button"
onclick={() => navigate(crumbs.slice(0, i + 1).join('/'))}
class="text-xs whitespace-nowrap shrink-0
{i === crumbs.length - 1 ? 'text-accent font-medium' : 'text-dim hover:text-text'}"
>{part}</button>
{/each}
{:else}
<span class="text-xs text-dim shrink-0">전체 자료</span>
{/if}
</div>
<div class="flex-1 min-h-0 grid grid-cols-1 md:grid-cols-[260px_1fr] gap-0 relative">
<!-- 좌측 트리 (데스크톱 normal, 모바일 drawer overlay) -->
{#if mobileTreeOpen}
<button
type="button"
aria-label="카테고리 닫기"
onclick={closeTree}
class="md:hidden fixed inset-0 z-40 bg-black/40"
></button>
{/if}
<aside
class="border-r border-default overflow-y-auto p-2
{mobileTreeOpen ? 'fixed left-0 top-0 bottom-0 w-72 max-w-[85vw] z-50 bg-surface shadow-2xl md:static md:shadow-none md:w-auto md:bg-transparent' : 'hidden md:block'}"
>
<div class="md:hidden flex items-center justify-between mb-2 px-1">
<span class="text-sm font-semibold text-text">카테고리</span>
<button
type="button"
onclick={closeTree}
class="p-1 rounded hover:bg-bg text-dim"
aria-label="닫기"
><X size={16} /></button>
</div>
<button
type="button"
onclick={() => navigateAndClose(null)}
style="touch-action: manipulation; user-select: none; -webkit-tap-highlight-color: transparent;"
class="w-full flex items-center justify-between gap-2 px-2 py-1.5 rounded text-sm transition-colors
{!activePath ? 'bg-accent/15 text-accent' : 'text-text hover:bg-surface'}"
>
@@ -185,7 +250,8 @@
<button
type="button"
onclick={() => navigate(n.path)}
onclick={() => navigateAndClose(n.path)}
style="touch-action: manipulation; user-select: none; -webkit-tap-highlight-color: transparent;"
class="flex-1 flex items-center justify-between gap-2 px-2 py-1 rounded text-xs transition-colors text-left
{isActive ? 'bg-accent/15 text-accent' : isParent ? 'text-text' : 'text-dim hover:bg-surface hover:text-text'}"
aria-current={isActive ? 'page' : undefined}