feat(ui): F1+F2 — component primitives + rebuilt app shell
F1: shadcn-style primitive set in components/ui (Button, Card, Badge, Input, Switch, Separator, Skeleton, Tooltip, DropdownMenu, Dialog, Sheet, Tabs, Table, Toast+useToast, ThemeToggle, PageHeader/StatCard/EmptyState) on Radix + cva. F2: Layout rebuilt on the new system — sidebar with primary nav (lucide icons) + favorites/saved-searches/mailbox/smart-folder sections, sticky topbar with search, sync status, theme toggle, digest toggle, and an account dropdown. All prior behavior preserved (auth, sync, collapse/section persistence, "/" search focus). App wrapped in ToastProvider + TooltipProvider. Build green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { cn } from '../../lib/utils.js';
|
||||
|
||||
/** Standard page title + optional description + right-aligned actions. */
|
||||
export function PageHeader({ title, description, actions, className }) {
|
||||
return (
|
||||
<div className={cn('mb-6 flex flex-wrap items-start justify-between gap-4', className)}>
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
|
||||
{description && <p className="mt-1 text-sm text-muted-foreground">{description}</p>}
|
||||
</div>
|
||||
{actions && <div className="flex items-center gap-2">{actions}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Dashboard metric tile. */
|
||||
export function StatCard({ label, value, hint, icon: Icon, tone = 'primary', className }) {
|
||||
const toneClass =
|
||||
tone === 'success'
|
||||
? 'text-success'
|
||||
: tone === 'warning'
|
||||
? 'text-warning'
|
||||
: tone === 'danger'
|
||||
? 'text-danger'
|
||||
: 'text-primary';
|
||||
return (
|
||||
<div className={cn('rounded-lg border border-border bg-card p-5 shadow-sm', className)}>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-muted-foreground">{label}</span>
|
||||
{Icon && <Icon className={cn('h-4 w-4', toneClass)} />}
|
||||
</div>
|
||||
<div className="mt-2 text-2xl font-semibold tracking-tight">{value}</div>
|
||||
{hint && <div className="mt-1 text-xs text-muted-foreground">{hint}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Empty / zero-state placeholder for lists and tables. */
|
||||
export function EmptyState({ icon: Icon, title, description, action, className }) {
|
||||
return (
|
||||
<div className={cn('flex flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-border p-10 text-center', className)}>
|
||||
{Icon && (
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-muted text-muted-foreground">
|
||||
<Icon className="h-6 w-6" />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<p className="font-medium">{title}</p>
|
||||
{description && <p className="mt-1 text-sm text-muted-foreground">{description}</p>}
|
||||
</div>
|
||||
{action}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user