173 lines
5.2 KiB
TypeScript
173 lines
5.2 KiB
TypeScript
import { useState } from 'react'
|
|
import {
|
|
ChevronRight,
|
|
ChevronDown,
|
|
Folder,
|
|
|
|
Image,
|
|
Calendar,
|
|
Star,
|
|
Flag,
|
|
Trash2,
|
|
Plus,
|
|
MoreHorizontal,
|
|
HardDrive
|
|
} from 'lucide-react'
|
|
import clsx from 'clsx'
|
|
|
|
interface TreeItem {
|
|
id: string
|
|
label: string
|
|
icon?: React.ReactNode
|
|
count?: number
|
|
children?: TreeItem[]
|
|
type?: 'folder' | 'heap' | 'special'
|
|
}
|
|
|
|
export function LeftSidebar() {
|
|
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set(['library', 'folders', 'heaps']))
|
|
const [selectedItem, setSelectedItem] = useState<string | null>('all-photos')
|
|
|
|
const toggleExpanded = (id: string) => {
|
|
const newExpanded = new Set(expandedItems)
|
|
if (newExpanded.has(id)) {
|
|
newExpanded.delete(id)
|
|
} else {
|
|
newExpanded.add(id)
|
|
}
|
|
setExpandedItems(newExpanded)
|
|
}
|
|
|
|
const libraryTree: TreeItem[] = [
|
|
{
|
|
id: 'library',
|
|
label: 'Library',
|
|
icon: <HardDrive className="h-4 w-4" />,
|
|
children: [
|
|
{ id: 'all-photos', label: 'All Photos', icon: <Image className="h-4 w-4" />, count: 0 },
|
|
{ id: 'by-date', label: 'By Date', icon: <Calendar className="h-4 w-4" /> },
|
|
{ id: 'rated', label: 'Rated', icon: <Star className="h-4 w-4" />, count: 0 },
|
|
{ id: 'flagged', label: 'Flagged', icon: <Flag className="h-4 w-4" />, count: 0 },
|
|
{ id: 'trash', label: 'Trash', icon: <Trash2 className="h-4 w-4" />, count: 0 },
|
|
],
|
|
},
|
|
{
|
|
id: 'folders',
|
|
label: 'Folders',
|
|
icon: <Folder className="h-4 w-4" />,
|
|
children: [], // Will be populated from API
|
|
},
|
|
{
|
|
id: 'heaps',
|
|
label: 'Heaps',
|
|
icon: <Folder className="h-4 w-4" />,
|
|
children: [], // Will be populated from API
|
|
},
|
|
]
|
|
|
|
const renderTreeItem = (item: TreeItem, depth: number = 0) => {
|
|
const hasChildren = item.children && item.children.length > 0
|
|
const isExpanded = expandedItems.has(item.id)
|
|
const isSelected = selectedItem === item.id
|
|
|
|
return (
|
|
<div key={item.id}>
|
|
<div
|
|
className={clsx(
|
|
'group flex cursor-pointer items-center gap-1 rounded px-2 py-1 text-sm',
|
|
isSelected ? 'bg-primary/20 text-primary' : 'text-text hover:bg-surface-2',
|
|
depth > 0 && 'text-[13px]'
|
|
)}
|
|
style={{ paddingLeft: `${8 + depth * 16}px` }}
|
|
onClick={() => {
|
|
setSelectedItem(item.id)
|
|
if (hasChildren) {
|
|
toggleExpanded(item.id)
|
|
}
|
|
}}
|
|
>
|
|
{/* Expand/Collapse Icon */}
|
|
{hasChildren ? (
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
toggleExpanded(item.id)
|
|
}}
|
|
className="rounded p-0.5 hover:bg-surface-offset"
|
|
>
|
|
{isExpanded ? (
|
|
<ChevronDown className="h-3 w-3" />
|
|
) : (
|
|
<ChevronRight className="h-3 w-3" />
|
|
)}
|
|
</button>
|
|
) : (
|
|
<div className="w-4" />
|
|
)}
|
|
|
|
{/* Item Icon */}
|
|
{item.icon && (
|
|
<span className={clsx('flex-shrink-0', isSelected ? 'text-primary' : 'text-text-muted')}>
|
|
{item.icon}
|
|
</span>
|
|
)}
|
|
|
|
{/* Label */}
|
|
<span className="flex-1 truncate">{item.label}</span>
|
|
|
|
{/* Count Badge */}
|
|
{item.count !== undefined && item.count > 0 && (
|
|
<span className="rounded bg-surface-offset px-1.5 py-0.5 text-xs text-text-muted">
|
|
{item.count}
|
|
</span>
|
|
)}
|
|
|
|
{/* Actions (shown on hover) */}
|
|
{(item.id === 'folders' || item.id === 'heaps') && (
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
// Handle add folder/heap
|
|
}}
|
|
className="invisible rounded p-0.5 text-text-muted hover:bg-surface-offset hover:text-text group-hover:visible"
|
|
>
|
|
<Plus className="h-3 w-3" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Render Children */}
|
|
{hasChildren && isExpanded && (
|
|
<div>
|
|
{item.children!.map((child) => renderTreeItem(child, depth + 1))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="flex h-full flex-col bg-surface">
|
|
{/* Sidebar Header */}
|
|
<div className="flex items-center justify-between border-b border-border px-3 py-2">
|
|
<h2 className="text-sm font-semibold text-text">Library</h2>
|
|
<button className="rounded p-1 text-text-muted hover:bg-surface-2 hover:text-text">
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Tree View */}
|
|
<div className="flex-1 overflow-y-auto py-2">
|
|
{libraryTree.map((item) => renderTreeItem(item))}
|
|
</div>
|
|
|
|
{/* Bottom Actions */}
|
|
<div className="border-t border-border p-3">
|
|
<button className="flex w-full items-center gap-2 rounded bg-surface-2 px-3 py-2 text-sm text-text hover:bg-surface-offset">
|
|
<Plus className="h-4 w-4" />
|
|
Add Source Folder
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
} |