ui: migrate to shadcn/ui primitives across dialogs, filters, and forms
Adopts shadcn/ui components (Dialog, Button, Input, Select, Popover, Command, Checkbox, Switch, Toggle, Calendar, etc.) across the app, replacing hand-rolled modals, dropdowns, and form controls. Adds a reusable cmdk-backed MultiSelect for the Type, Tags, and Flag filters so all multi-value filter popovers share one component and layout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,12 @@ import { useHeapsQuery, HEAPS_QUERY_KEY } from '../../hooks/useHeapsQuery'
|
||||
import { FOLDER_TREE_QUERY_KEY } from '../../hooks/useFolderTreeQuery'
|
||||
import { LIBRARY_STATS_QUERY_KEY } from '../../hooks/useLibraryStatsQuery'
|
||||
import { toast } from '../ToastContainer'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
|
||||
interface UploadModalProps {
|
||||
isOpen: boolean
|
||||
@@ -123,15 +129,8 @@ export function UploadModal({ isOpen, onClose, initialFolderId }: UploadModalPro
|
||||
}
|
||||
}, [isOpen, initialFolderId, folderTree, destFolderId])
|
||||
|
||||
// Esc closes (unless mid-upload — don't orphan in-flight requests).
|
||||
useEffect(() => {
|
||||
if (!isOpen) return
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && !isUploading) onClose()
|
||||
}
|
||||
window.addEventListener('keydown', handler)
|
||||
return () => window.removeEventListener('keydown', handler)
|
||||
}, [isOpen, isUploading, onClose])
|
||||
// Esc / overlay-click dismissal lives on the Dialog primitive below.
|
||||
// We only need to guard against closing while an upload is in flight.
|
||||
|
||||
// Reset transient state on open so a previous session's queue doesn't
|
||||
// bleed into a fresh one.
|
||||
@@ -324,31 +323,20 @@ export function UploadModal({ isOpen, onClose, initialFolderId }: UploadModalPro
|
||||
)
|
||||
const overallPct = totalBytes > 0 ? Math.round((uploadedBytes / totalBytes) * 100) : 0
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50">
|
||||
<div
|
||||
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
|
||||
onClick={!isUploading ? onClose : undefined}
|
||||
/>
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
<div className="relative z-10 flex max-h-[85vh] w-[760px] flex-col rounded-lg border border-border bg-surface shadow-2xl">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b border-border px-5 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<UploadIcon className="h-4 w-4 text-text-muted" />
|
||||
<h2 className="text-base font-semibold text-text">Upload photos</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
disabled={isUploading}
|
||||
className="rounded p-1 text-text-muted hover:bg-surface-2 hover:text-text disabled:opacity-40"
|
||||
aria-label="Close"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onOpenChange={(o) => {
|
||||
if (!o && !isUploading) onClose()
|
||||
}}
|
||||
>
|
||||
<DialogContent className="flex max-h-[85vh] w-[760px] max-w-[760px] flex-col p-0">
|
||||
<DialogHeader className="border-b border-border px-5 py-3">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<UploadIcon className="h-4 w-4 text-text-muted" />
|
||||
Upload photos
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{/* Body */}
|
||||
<div className="flex min-h-0 flex-1 gap-4 overflow-hidden p-5">
|
||||
@@ -535,9 +523,8 @@ export function UploadModal({ isOpen, onClose, initialFolderId }: UploadModalPro
|
||||
{isUploading ? 'Uploading…' : `Upload ${queue.length || ''}`.trim()}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user