fix+upstream: hooks-order crash, ConfirmDialog/PageHeader/skeletons, styled error boundary
Hoists hooks above early return (render-time Navigate); ports finance's ConfirmDialog/PageHeader/loading/states + APC error-copy mapper; removes window.confirm, dead appbar search, and seeded fake notifications; wires toasts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
114
app/components/states.tsx
Normal file
114
app/components/states.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import type { ReactNode } from "react"
|
||||
import { AlertTriangle } from "lucide-react"
|
||||
|
||||
import { Card, CardContent, CardHeader } from "~/components/ui/card"
|
||||
import { Skeleton } from "~/components/ui/skeleton"
|
||||
|
||||
type EmptyStateProps = {
|
||||
icon?: ReactNode
|
||||
title: string
|
||||
description?: ReactNode
|
||||
action?: ReactNode
|
||||
secondaryAction?: ReactNode
|
||||
/** When true, wrap the body in a bordered card frame. Default false —
|
||||
* a bare dashed surface that reads as "nothing here yet". */
|
||||
framed?: boolean
|
||||
}
|
||||
|
||||
/** Shared empty state. Centred icon disc + title + description + optional
|
||||
* actions, using theme tokens only. Pair with EmptyState → ErrorState →
|
||||
* content branching so a route never renders a bare blank surface. */
|
||||
export function EmptyState({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
action,
|
||||
secondaryAction,
|
||||
framed = false,
|
||||
}: EmptyStateProps) {
|
||||
const body = (
|
||||
<div className="flex flex-col items-center gap-5 px-8 py-16 text-center">
|
||||
{icon ? (
|
||||
<div className="flex size-14 items-center justify-center rounded-2xl bg-primary/10 text-primary">
|
||||
{icon}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="space-y-2">
|
||||
<p className="text-title font-semibold tracking-tight">{title}</p>
|
||||
{description ? (
|
||||
<p className="mx-auto max-w-md text-[15px] leading-relaxed text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
{action || secondaryAction ? (
|
||||
<div className="mt-1 flex flex-wrap items-center justify-center gap-2">
|
||||
{action}
|
||||
{secondaryAction}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
if (framed) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-0">{body}</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="overflow-hidden rounded-2xl border-2 border-dashed border-muted-foreground/20 bg-muted/30">
|
||||
{body}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** Shared error state — a soft destructive-tinted banner. Used both by
|
||||
* routes (data-fetch failures) and as the middle rung of the
|
||||
* skeleton → error → empty → content grammar. */
|
||||
export function ErrorState({
|
||||
title = "Something went wrong",
|
||||
message,
|
||||
action,
|
||||
}: {
|
||||
title?: string
|
||||
message: string
|
||||
action?: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-start gap-3 rounded-xl border border-destructive/30 bg-destructive/5 p-4">
|
||||
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-destructive" />
|
||||
<div className="flex-1 space-y-1">
|
||||
<p className="text-sm font-medium text-destructive">{title}</p>
|
||||
<p className="text-sm text-muted-foreground">{message}</p>
|
||||
</div>
|
||||
{action ? <div className="shrink-0">{action}</div> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** Card-shaped loading placeholder for card lists/grids. */
|
||||
export function LoadingCards({
|
||||
count = 3,
|
||||
variant = "row",
|
||||
}: {
|
||||
count?: number
|
||||
variant?: "row" | "grid"
|
||||
}) {
|
||||
const items = Array.from({ length: count })
|
||||
return (
|
||||
<div className={variant === "grid" ? "grid gap-5 sm:grid-cols-2" : "space-y-3"}>
|
||||
{items.map((_, i) => (
|
||||
<Card key={i}>
|
||||
<CardHeader className="space-y-2 pb-2">
|
||||
<Skeleton className="h-4 w-40" />
|
||||
<Skeleton className="h-3 w-64" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Skeleton className="h-3 w-3/4" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user