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:
2026-04-15 09:07:20 +02:00
parent 8529771122
commit 7efac4354e
51 changed files with 3032 additions and 1660 deletions

View File

@@ -1,5 +1,12 @@
import { useEffect } from 'react'
import clsx from 'clsx'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
interface ConfirmDialogProps {
isOpen: boolean
@@ -14,8 +21,9 @@ interface ConfirmDialogProps {
}
/**
* Tiny modal-confirmation dialog. Mirrors the AddSourceFolderDialog overlay
* pattern (custom fixed inset-0 backdrop, no shadcn Dialog dep). Esc closes.
* Modal confirmation dialog. Built on the shadcn Dialog primitive:
* Radix handles focus trap, portal, overlay-click dismissal, Esc, and
* animations. We only supply title, body, and the two action buttons.
*/
export function ConfirmDialog({
isOpen,
@@ -27,49 +35,27 @@ export function ConfirmDialog({
onConfirm,
onClose,
}: ConfirmDialogProps) {
// Esc to close.
useEffect(() => {
if (!isOpen) return
const handler = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}
window.addEventListener('keydown', handler)
return () => window.removeEventListener('keydown', handler)
}, [isOpen, onClose])
if (!isOpen) return null
return (
<div className="fixed inset-0 z-50">
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
onClick={onClose}
/>
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
<div className="relative z-10 w-96 rounded-lg border border-border bg-surface p-5 shadow-2xl">
<h2 className="mb-2 text-base font-semibold text-text">{title}</h2>
<div className="mb-4 text-sm text-text-muted">{message}</div>
<div className="flex justify-end gap-2">
<button
onClick={onClose}
className="rounded border border-border px-3 py-1.5 text-sm text-text hover:bg-surface-2"
>
{cancelLabel}
</button>
<button
onClick={onConfirm}
className={clsx(
'rounded px-3 py-1.5 text-sm font-medium text-white',
destructive
? 'bg-reject hover:bg-reject/80'
: 'bg-primary hover:bg-primary/80'
)}
>
{confirmLabel}
</button>
</div>
</div>
</div>
</div>
<Dialog open={isOpen} onOpenChange={(o) => !o && onClose()}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription asChild>
<div>{message}</div>
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={onClose}>
{cancelLabel}
</Button>
<Button
variant={destructive ? 'destructive' : 'default'}
onClick={onConfirm}
>
{confirmLabel}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}