ui: tidy sidebar tree indent and consolidate sidebar toggles into filter bar
Folder tree now indents 20px per level (chevron width + gap) so a child's chevron column lines up under its parent's label, and depth-1 rows nest under the section eyebrow instead of starting flush with it. Spacer for leaf rows matches the chevron button footprint so rows align regardless of expandability. Sidebar open/close buttons (previously split between TopBar and each panel header) collapse into two toggles at the ends of the FilterBar. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -56,12 +56,7 @@ function MainApp() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen bg-bg text-text">
|
||||
<TopBar
|
||||
leftSidebarOpen={leftSidebarOpen}
|
||||
rightSidebarOpen={showRightSidebar}
|
||||
onExpandLeft={() => setLeftSidebarOpen(true)}
|
||||
onExpandRight={() => setRightSidebarOpen(true)}
|
||||
/>
|
||||
<TopBar />
|
||||
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Left Sidebar */}
|
||||
@@ -70,9 +65,7 @@ function MainApp() {
|
||||
leftSidebarOpen ? 'w-60' : 'w-0'
|
||||
} overflow-hidden border-r border-border bg-surface`}
|
||||
>
|
||||
<LeftSidebar
|
||||
onCollapse={() => setLeftSidebarOpen(false)}
|
||||
/>
|
||||
<LeftSidebar />
|
||||
</div>
|
||||
|
||||
{/* Main column — filter bar, discard bar, timeline. Lives to the
|
||||
@@ -80,7 +73,14 @@ function MainApp() {
|
||||
* across the sidebar. relative so the KeyboardHints overlay
|
||||
* centers against this column, not the viewport. */}
|
||||
<div className="relative flex min-w-0 flex-1 flex-col">
|
||||
{!isSettings && <FilterBar />}
|
||||
{!isSettings && (
|
||||
<FilterBar
|
||||
leftSidebarOpen={leftSidebarOpen}
|
||||
rightSidebarOpen={showRightSidebar}
|
||||
onToggleLeftSidebar={() => setLeftSidebarOpen(!leftSidebarOpen)}
|
||||
onToggleRightSidebar={() => setRightSidebarOpen(!rightSidebarOpen)}
|
||||
/>
|
||||
)}
|
||||
{!isSettings && <DiscardActionBar />}
|
||||
<div className="flex-1 overflow-auto">
|
||||
{currentSection === 'settings' ? (
|
||||
@@ -110,7 +110,7 @@ function MainApp() {
|
||||
showRightSidebar ? 'w-72' : 'w-0'
|
||||
} overflow-hidden border-l border-border bg-surface`}
|
||||
>
|
||||
<RightSidebar onCollapse={() => setRightSidebarOpen(false)} />
|
||||
<RightSidebar />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Star, X, ArrowDown, ArrowUp, Search, AlertTriangle, Check } from 'lucide-react'
|
||||
import {
|
||||
Star,
|
||||
X,
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
Search,
|
||||
AlertTriangle,
|
||||
Check,
|
||||
PanelLeftOpen,
|
||||
PanelLeftClose,
|
||||
PanelRightOpen,
|
||||
PanelRightClose,
|
||||
} from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
import {
|
||||
useFilterStore,
|
||||
@@ -35,7 +47,19 @@ const SORT_OPTIONS: { value: SortField; label: string }[] = [
|
||||
* underlying control, and shows a short value summary inline when active.
|
||||
* Replaces the old expandable FilterBar + ActiveFilterChips combo.
|
||||
*/
|
||||
export function FilterBar() {
|
||||
interface FilterBarProps {
|
||||
leftSidebarOpen: boolean
|
||||
rightSidebarOpen: boolean
|
||||
onToggleLeftSidebar: () => void
|
||||
onToggleRightSidebar: () => void
|
||||
}
|
||||
|
||||
export function FilterBar({
|
||||
leftSidebarOpen,
|
||||
rightSidebarOpen,
|
||||
onToggleLeftSidebar,
|
||||
onToggleRightSidebar,
|
||||
}: FilterBarProps) {
|
||||
const filterState = useFilterStore()
|
||||
const dateFrom = useFilterStore((s) => s.dateFrom)
|
||||
const dateTo = useFilterStore((s) => s.dateTo)
|
||||
@@ -132,6 +156,22 @@ export function FilterBar() {
|
||||
|
||||
return (
|
||||
<div className="flex h-9 items-center gap-3 border-b border-border bg-surface px-3 py-0">
|
||||
{/* Left sidebar toggle — pinned to the far-left edge of the bar so it
|
||||
* sits flush against the panel it controls (or the viewport edge
|
||||
* when collapsed). */}
|
||||
<button
|
||||
onClick={onToggleLeftSidebar}
|
||||
className="flex-shrink-0 rounded p-1 text-text-muted hover:bg-surface-2 hover:text-text"
|
||||
title={leftSidebarOpen ? 'Collapse panel (Tab)' : 'Expand panel (Tab)'}
|
||||
aria-label={leftSidebarOpen ? 'Collapse left panel' : 'Expand left panel'}
|
||||
>
|
||||
{leftSidebarOpen ? (
|
||||
<PanelLeftClose className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<PanelLeftOpen className="h-3.5 w-3.5" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Pills — left side, scroll horizontally if they overflow. */}
|
||||
<div className="flex min-w-0 flex-1 items-center gap-1.5 overflow-x-auto">
|
||||
{/* Date */}
|
||||
@@ -423,6 +463,20 @@ export function FilterBar() {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right sidebar toggle — pinned to the far-right edge. */}
|
||||
<button
|
||||
onClick={onToggleRightSidebar}
|
||||
className="flex-shrink-0 rounded p-1 text-text-muted hover:bg-surface-2 hover:text-text"
|
||||
title={rightSidebarOpen ? 'Collapse panel (I)' : 'Expand panel (I)'}
|
||||
aria-label={rightSidebarOpen ? 'Collapse right panel' : 'Expand right panel'}
|
||||
>
|
||||
{rightSidebarOpen ? (
|
||||
<PanelRightClose className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<PanelRightOpen className="h-3.5 w-3.5" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -125,16 +125,14 @@ export function FilterPill({
|
||||
onClear()
|
||||
}
|
||||
}}
|
||||
// Same h-4 w-4 as the chevron slot below so swapping the
|
||||
// two doesn't change the pill's footprint.
|
||||
className="ml-0.5 inline-flex h-4 w-4 cursor-pointer items-center justify-center rounded-full hover:bg-primary/30"
|
||||
className="ml-0.5 inline-flex h-3.5 w-4 cursor-pointer items-center justify-center rounded-full hover:bg-primary/30"
|
||||
title={`Clear ${label}`}
|
||||
aria-label={`Clear ${label}`}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex h-4 w-4 items-center justify-center">
|
||||
<span className="inline-flex h-3.5 w-4 items-center justify-center">
|
||||
<ChevronDown className="h-3 w-3 opacity-60" />
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
Layers2,
|
||||
MoreHorizontal,
|
||||
Pencil,
|
||||
PanelLeftClose,
|
||||
Settings,
|
||||
Users,
|
||||
Eye,
|
||||
@@ -64,11 +63,7 @@ interface TreeItem {
|
||||
isHidden?: boolean
|
||||
}
|
||||
|
||||
interface LeftSidebarProps {
|
||||
onCollapse: () => void
|
||||
}
|
||||
|
||||
export function LeftSidebar({ onCollapse }: LeftSidebarProps) {
|
||||
export function LeftSidebar() {
|
||||
const { user, isAdmin, logout } = useAuth()
|
||||
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set(['library', 'folders', 'heaps']))
|
||||
// Inline rename state for source-root rows. Stores the id being edited
|
||||
@@ -839,14 +834,6 @@ export function LeftSidebar({ onCollapse }: LeftSidebarProps) {
|
||||
>
|
||||
<UploadIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={onCollapse}
|
||||
className="rounded p-0.5 text-text-muted hover:bg-surface-2 hover:text-text"
|
||||
title="Collapse panel (Tab)"
|
||||
aria-label="Collapse panel"
|
||||
>
|
||||
<PanelLeftClose className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Active heap card — pinned just below the Library header so
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import { X, Star, ShoppingBasket, Trash2, Plus, PanelRightClose } from 'lucide-react'
|
||||
import { X, Star, ShoppingBasket, Trash2, Plus } from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { format } from 'date-fns'
|
||||
@@ -29,11 +29,7 @@ import { COLOR_LABEL_OPTIONS } from '../../constants/colorLabels'
|
||||
* - 2+ photos selected → renders a slim bulk-action panel that fans out
|
||||
* rating / color / discard / pick across the entire selection.
|
||||
*/
|
||||
interface RightSidebarProps {
|
||||
onCollapse: () => void
|
||||
}
|
||||
|
||||
export function RightSidebar({ onCollapse }: RightSidebarProps) {
|
||||
export function RightSidebar() {
|
||||
const { selectedPhotos, activePhotoId, clearSelection } = usePhotoStore()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
@@ -221,14 +217,6 @@ export function RightSidebar({ onCollapse }: RightSidebarProps) {
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={onCollapse}
|
||||
className="rounded p-0.5 text-text-muted hover:bg-surface-2 hover:text-text"
|
||||
title="Collapse panel (I)"
|
||||
aria-label="Collapse panel"
|
||||
>
|
||||
<PanelRightClose className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import { PanelLeftOpen, PanelRightOpen } from 'lucide-react'
|
||||
import desertBg from '../../assets/desert.png'
|
||||
import muleSprites from '../../assets/mule-sprites.png'
|
||||
|
||||
interface TopBarProps {
|
||||
leftSidebarOpen: boolean
|
||||
rightSidebarOpen: boolean
|
||||
onExpandLeft: () => void
|
||||
onExpandRight: () => void
|
||||
}
|
||||
|
||||
// Block-character ASCII rendering of "Mulimago" — sits on a black plate
|
||||
// in place of the old text title.
|
||||
const MULIMAGO_ASCII = `▖ ▖ ▜ ▘
|
||||
@@ -34,22 +26,9 @@ function toRoman(n: number): string {
|
||||
|
||||
/**
|
||||
* Slim top bar — animated walking mule on the left over a tiled desert
|
||||
* backdrop, settings gear on the right. The active heap badge moved into
|
||||
* the Heaps panel in the left sidebar (where it actually relates to the
|
||||
* heap rows the user navigates to).
|
||||
*
|
||||
* Also hosts the "expand sidebar" affordances: when a side panel is
|
||||
* collapsed, a small panel-open icon appears on the corresponding edge
|
||||
* so the user has a way to bring it back without hunting for the
|
||||
* keyboard shortcut. When the panel is open, the button hides — its
|
||||
* collapse twin lives in the panel's own header.
|
||||
* backdrop. Sidebar toggle buttons live on the FilterBar.
|
||||
*/
|
||||
export function TopBar({
|
||||
leftSidebarOpen,
|
||||
rightSidebarOpen,
|
||||
onExpandLeft,
|
||||
onExpandRight,
|
||||
}: TopBarProps) {
|
||||
export function TopBar() {
|
||||
return (
|
||||
<header
|
||||
className="relative flex h-16 items-center justify-between overflow-hidden border-b border-border px-4"
|
||||
@@ -67,16 +46,6 @@ export function TopBar({
|
||||
}}
|
||||
>
|
||||
<div className="relative flex items-center gap-3">
|
||||
{!leftSidebarOpen && (
|
||||
<button
|
||||
onClick={onExpandLeft}
|
||||
className="rounded bg-black/30 p-1.5 text-text-muted backdrop-blur-sm transition-colors hover:bg-black/50 hover:text-text"
|
||||
title="Expand panel (Tab)"
|
||||
aria-label="Expand left panel"
|
||||
>
|
||||
<PanelLeftOpen className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
aria-label="Mulimago"
|
||||
className="h-12 w-14"
|
||||
@@ -100,16 +69,6 @@ export function TopBar({
|
||||
<span className="text-[10px] font-serif text-black/80">
|
||||
Built with hubris • {toRoman(new Date().getFullYear())}
|
||||
</span>
|
||||
{!rightSidebarOpen && (
|
||||
<button
|
||||
onClick={onExpandRight}
|
||||
className="rounded bg-black/30 p-1.5 text-text-muted backdrop-blur-sm transition-colors hover:bg-black/50 hover:text-text"
|
||||
title="Expand panel (I)"
|
||||
aria-label="Expand right panel"
|
||||
>
|
||||
<PanelRightOpen className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user