159 lines
5.7 KiB
TypeScript
159 lines
5.7 KiB
TypeScript
import { useState } from 'react'
|
|
import {
|
|
Search,
|
|
Grid,
|
|
List,
|
|
SlidersHorizontal,
|
|
FolderOpen,
|
|
Upload,
|
|
Settings,
|
|
Menu,
|
|
Trash2
|
|
} from 'lucide-react'
|
|
import clsx from 'clsx'
|
|
import { usePhotoStore } from '../../store/photoStore'
|
|
import { photos } from '../../services/api'
|
|
import { toast } from '../ToastContainer'
|
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
import muliLogo from '../../assets/muli-logo.png'
|
|
|
|
export function TopBar() {
|
|
const [searchQuery, setSearchQuery] = useState('')
|
|
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
|
const selectedPhotos = usePhotoStore((state) => state.selectedPhotos)
|
|
const clearSelection = usePhotoStore((state) => state.clearSelection)
|
|
const selectedCount = selectedPhotos.length
|
|
const queryClient = useQueryClient()
|
|
|
|
// Mutation for moving photos to trash
|
|
const trashPhotosMutation = useMutation({
|
|
mutationFn: async () => {
|
|
await photos.bulkUpdate(selectedPhotos, { trash: true })
|
|
},
|
|
onSuccess: () => {
|
|
toast.success('Moved to Trash', `${selectedCount} photo${selectedCount > 1 ? 's' : ''} moved to trash`)
|
|
clearSelection()
|
|
queryClient.invalidateQueries({ queryKey: ['photos'] })
|
|
},
|
|
onError: (error: any) => {
|
|
toast.error('Failed to Move to Trash', error.message || 'An error occurred')
|
|
},
|
|
})
|
|
|
|
return (
|
|
<header className="flex h-12 items-center justify-between border-b border-border bg-surface px-4">
|
|
{/* Left Section - Menu and App Name */}
|
|
<div className="flex items-center gap-3">
|
|
<button
|
|
className="group relative rounded p-1 text-text-muted hover:bg-surface-2 hover:text-text"
|
|
title="Toggle sidebar (Tab)"
|
|
>
|
|
<Menu className="h-5 w-5" />
|
|
<kbd className="absolute -bottom-5 left-1/2 -translate-x-1/2 rounded bg-surface-offset px-1 py-0.5 text-[9px] font-medium text-text opacity-0 group-hover:opacity-100">
|
|
Tab
|
|
</kbd>
|
|
</button>
|
|
<div className="flex items-center gap-2">
|
|
<img src={muliLogo} alt="Mulita" className="h-7 w-7 object-contain" />
|
|
<h1 className="text-lg font-semibold text-text">Mulita</h1>
|
|
</div>
|
|
{selectedCount > 0 && (
|
|
<>
|
|
<span className="rounded bg-primary/20 px-2 py-0.5 text-sm text-primary">
|
|
{selectedCount} selected
|
|
</span>
|
|
<button
|
|
onClick={() => trashPhotosMutation.mutate()}
|
|
disabled={trashPhotosMutation.isPending}
|
|
className="flex items-center gap-1 rounded bg-reject/20 px-2 py-0.5 text-sm text-reject hover:bg-reject/30 disabled:opacity-50"
|
|
title="Move to trash"
|
|
>
|
|
<Trash2 className="h-3.5 w-3.5" />
|
|
Trash
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{/* Center Section - Search */}
|
|
<div className="flex max-w-xl flex-1 items-center px-8">
|
|
<div className="relative w-full">
|
|
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted" />
|
|
<input
|
|
type="text"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
placeholder="Search photos..."
|
|
className="w-full rounded-md border border-border bg-bg py-1.5 pl-9 pr-3 text-sm text-text placeholder-text-muted focus:border-primary focus:outline-none"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Section - View Controls and Actions */}
|
|
<div className="flex items-center gap-2">
|
|
{/* View Mode Toggle */}
|
|
<div className="flex rounded-md border border-border">
|
|
<button
|
|
className={clsx(
|
|
'rounded-l-md px-2 py-1',
|
|
viewMode === 'grid'
|
|
? 'bg-primary text-white'
|
|
: 'bg-surface text-text-muted hover:bg-surface-2'
|
|
)}
|
|
onClick={() => setViewMode('grid')}
|
|
title="Grid view"
|
|
>
|
|
<Grid className="h-4 w-4" />
|
|
</button>
|
|
<button
|
|
className={clsx(
|
|
'rounded-r-md px-2 py-1',
|
|
viewMode === 'list'
|
|
? 'bg-primary text-white'
|
|
: 'bg-surface text-text-muted hover:bg-surface-2'
|
|
)}
|
|
onClick={() => setViewMode('list')}
|
|
title="List view"
|
|
>
|
|
<List className="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Filter Button */}
|
|
<button
|
|
className="group relative rounded p-1.5 text-text-muted hover:bg-surface-2 hover:text-text"
|
|
title="Filter photos (Ctrl+F)"
|
|
>
|
|
<SlidersHorizontal className="h-4 w-4" />
|
|
<kbd className="absolute -bottom-5 left-1/2 -translate-x-1/2 whitespace-nowrap rounded bg-surface-offset px-1 py-0.5 text-[9px] font-medium text-text opacity-0 group-hover:opacity-100">
|
|
Ctrl+F
|
|
</kbd>
|
|
</button>
|
|
|
|
<div className="mx-1 h-6 w-px bg-border" />
|
|
|
|
{/* Action Buttons */}
|
|
<button
|
|
className="rounded p-1.5 text-text-muted hover:bg-surface-2 hover:text-text"
|
|
title="Add folder"
|
|
>
|
|
<FolderOpen className="h-4 w-4" />
|
|
</button>
|
|
|
|
<button
|
|
className="rounded p-1.5 text-text-muted hover:bg-surface-2 hover:text-text"
|
|
title="Import photos"
|
|
>
|
|
<Upload className="h-4 w-4" />
|
|
</button>
|
|
|
|
<button
|
|
className="rounded p-1.5 text-text-muted hover:bg-surface-2 hover:text-text"
|
|
title="Settings"
|
|
>
|
|
<Settings className="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
</header>
|
|
)
|
|
} |