feat: brain mobile sidebar — slide-out drawer with overlay
- "Folders & Tags" button shown only on mobile - Click opens sidebar as slide-out drawer from left - Dark overlay behind, click to dismiss - Selecting a folder/tag auto-closes the drawer - Desktop sidebar unchanged Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,7 @@
|
||||
let sidebarFolders = $state<SidebarFolder[]>([]);
|
||||
let sidebarTags = $state<SidebarTag[]>([]);
|
||||
let sidebarView = $state<'folders' | 'tags'>('folders');
|
||||
let mobileSidebarOpen = $state(false);
|
||||
let showManage = $state(false);
|
||||
let newTaxName = $state('');
|
||||
|
||||
@@ -327,7 +328,11 @@
|
||||
<div class="brain-layout">
|
||||
|
||||
<!-- Second sidebar (like Reader) -->
|
||||
<aside class="brain-sidebar">
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
{#if mobileSidebarOpen}
|
||||
<div class="mobile-sidebar-overlay" onclick={() => mobileSidebarOpen = false}></div>
|
||||
{/if}
|
||||
<aside class="brain-sidebar" class:mobile-open={mobileSidebarOpen}>
|
||||
<div class="sidebar-header">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
||||
<span class="sidebar-title">Brain</span>
|
||||
@@ -341,7 +346,7 @@
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav">
|
||||
<button class="nav-item" class:active={!activeFolder && !activeTag} onclick={() => { activeFolder = null; activeTag = null; activeFolderId = null; activeTagId = null; loadItems(); }}>
|
||||
<button class="nav-item" class:active={!activeFolder && !activeTag} onclick={() => { activeFolder = null; activeTag = null; activeFolderId = null; activeTagId = null; mobileSidebarOpen = false; loadItems(); }}>
|
||||
<span class="nav-label">All items</span>
|
||||
<span class="nav-count">{total}</span>
|
||||
</button>
|
||||
@@ -360,7 +365,7 @@
|
||||
{/if}
|
||||
<nav class="sidebar-nav">
|
||||
{#each sidebarFolders.filter(f => f.is_active) as folder}
|
||||
<button class="nav-item" class:active={activeFolder === folder.name} onclick={() => { activeFolder = folder.name; activeFolderId = folder.id; activeTag = null; activeTagId = null; loadItems(); }}>
|
||||
<button class="nav-item" class:active={activeFolder === folder.name} onclick={() => { activeFolder = folder.name; activeFolderId = folder.id; activeTag = null; activeTagId = null; mobileSidebarOpen = false; loadItems(); }}>
|
||||
<span class="nav-label">{folder.name}</span>
|
||||
{#if showManage}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions --><span class="nav-delete" onclick={(e) => { e.stopPropagation(); if (confirm(`Delete "${folder.name}"? Items will be moved.`)) deleteTaxonomy(folder.id); }}>×</span>
|
||||
@@ -383,7 +388,7 @@
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
{#each sidebarTags.filter(t => t.is_active) as tag}
|
||||
<button class="nav-item" class:active={activeTag === tag.name} onclick={() => { activeTag = tag.name; activeTagId = tag.id; activeFolder = null; activeFolderId = null; loadItems(); }}>
|
||||
<button class="nav-item" class:active={activeTag === tag.name} onclick={() => { activeTag = tag.name; activeTagId = tag.id; activeFolder = null; activeFolderId = null; mobileSidebarOpen = false; loadItems(); }}>
|
||||
<span class="nav-label">{tag.name}</span>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions --><span class="nav-delete" onclick={(e) => { e.stopPropagation(); if (confirm(`Delete tag "${tag.name}"?`)) deleteTaxonomy(tag.id); }}>×</span>
|
||||
</button>
|
||||
@@ -392,7 +397,7 @@
|
||||
{:else}
|
||||
<nav class="sidebar-nav">
|
||||
{#each sidebarTags.filter(t => t.is_active && t.item_count > 0) as tag}
|
||||
<button class="nav-item" class:active={activeTag === tag.name} onclick={() => { activeTag = tag.name; activeTagId = tag.id; activeFolder = null; activeFolderId = null; loadItems(); }}>
|
||||
<button class="nav-item" class:active={activeTag === tag.name} onclick={() => { activeTag = tag.name; activeTagId = tag.id; activeFolder = null; activeFolderId = null; mobileSidebarOpen = false; loadItems(); }}>
|
||||
<span class="nav-label">{tag.name}</span>
|
||||
<span class="nav-count">{tag.item_count}</span>
|
||||
</button>
|
||||
@@ -432,12 +437,18 @@
|
||||
{#if uploading}<div class="upload-status">Uploading...</div>{/if}
|
||||
</section>
|
||||
|
||||
<!-- Mobile sidebar toggle -->
|
||||
<button class="mobile-filter-btn" onclick={() => mobileSidebarOpen = true}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/></svg>
|
||||
Folders & Tags
|
||||
</button>
|
||||
|
||||
<!-- Active filter indicator -->
|
||||
{#if activeFolder || activeTag}
|
||||
<div class="active-filter">
|
||||
<span class="filter-label">Filtered by {activeFolder ? 'folder' : 'tag'}:</span>
|
||||
<span class="filter-tag">{activeFolder || activeTag}</span>
|
||||
<button class="filter-clear" onclick={() => { activeFolder = null; activeTag = null; activeFolderId = null; activeTagId = null; loadItems(); }}>
|
||||
<button class="filter-clear" onclick={() => { activeFolder = null; activeTag = null; activeFolderId = null; activeTagId = null; mobileSidebarOpen = false; loadItems(); }}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
Clear
|
||||
</button>
|
||||
@@ -856,6 +867,28 @@
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* ═══ Mobile filter button ═══ */
|
||||
.mobile-filter-btn {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(35,26,17,0.1);
|
||||
background: rgba(255,252,248,0.7);
|
||||
color: #5c5046;
|
||||
font-size: 0.82rem;
|
||||
font-family: var(--font);
|
||||
cursor: pointer;
|
||||
margin-bottom: 12px;
|
||||
transition: all 160ms;
|
||||
}
|
||||
.mobile-filter-btn:hover { background: rgba(255,248,241,0.9); color: #1e1812; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.mobile-filter-btn { display: flex; }
|
||||
}
|
||||
|
||||
/* ═══ Active filter ═══ */
|
||||
.active-filter {
|
||||
display: flex;
|
||||
@@ -1288,7 +1321,16 @@
|
||||
@media (max-width: 768px) {
|
||||
.brain-command { display: grid; gap: 14px; }
|
||||
.command-actions { justify-items: start; }
|
||||
.brain-sidebar { display: none; }
|
||||
.brain-sidebar {
|
||||
display: none;
|
||||
position: fixed; left: 0; top: 0; bottom: 0; z-index: 50;
|
||||
width: 280px; box-shadow: 8px 0 24px rgba(0,0,0,0.1);
|
||||
}
|
||||
.brain-sidebar.mobile-open { display: flex; }
|
||||
.mobile-sidebar-overlay {
|
||||
position: fixed; inset: 0; z-index: 49;
|
||||
background: rgba(17,13,10,0.3);
|
||||
}
|
||||
.brain-layout { margin: 0; }
|
||||
.masonry { columns: 1; }
|
||||
.detail-sheet { width: 100%; padding: 20px; }
|
||||
|
||||
Reference in New Issue
Block a user