67 lines
1.8 KiB
TypeScript
67 lines
1.8 KiB
TypeScript
|
|
/* EmptyState — shared empty / zero-data placeholder.
|
||
|
|
*
|
||
|
|
* Replaces the "No reports yet." centered text scattered across pages.
|
||
|
|
* Renders a dashed-border panel with a lucide icon, headline, optional
|
||
|
|
* description, and an optional CTA button.
|
||
|
|
*
|
||
|
|
* Use within Card content, table empty rows, or dialog bodies. */
|
||
|
|
|
||
|
|
import Link from 'next/link';
|
||
|
|
import type { LucideIcon } from 'lucide-react';
|
||
|
|
import { cn } from '@/lib/utils';
|
||
|
|
import { Button } from '@/components/ui/button';
|
||
|
|
|
||
|
|
interface EmptyStateAction {
|
||
|
|
label: string;
|
||
|
|
href?: string;
|
||
|
|
onClick?: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface EmptyStateProps {
|
||
|
|
icon?: LucideIcon;
|
||
|
|
title: string;
|
||
|
|
description?: string;
|
||
|
|
action?: EmptyStateAction;
|
||
|
|
size?: 'sm' | 'md';
|
||
|
|
className?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function EmptyState({
|
||
|
|
icon: Icon,
|
||
|
|
title,
|
||
|
|
description,
|
||
|
|
action,
|
||
|
|
size = 'md',
|
||
|
|
className,
|
||
|
|
}: EmptyStateProps) {
|
||
|
|
const padding = size === 'sm' ? 'py-6 px-4' : 'py-10 px-6';
|
||
|
|
const iconSize = size === 'sm' ? 'h-5 w-5' : 'h-6 w-6';
|
||
|
|
|
||
|
|
const button = action ? (
|
||
|
|
<Button asChild={!!action.href} variant="outline" size="sm" onClick={action.onClick}>
|
||
|
|
{action.href ? <Link href={action.href}>{action.label}</Link> : <span>{action.label}</span>}
|
||
|
|
</Button>
|
||
|
|
) : null;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className={cn(
|
||
|
|
'flex flex-col items-center justify-center text-center gap-2 rounded-md border border-dashed border-border/60',
|
||
|
|
padding,
|
||
|
|
className,
|
||
|
|
)}
|
||
|
|
>
|
||
|
|
{Icon && (
|
||
|
|
<span className="flex h-10 w-10 items-center justify-center rounded-md bg-muted text-muted-foreground">
|
||
|
|
<Icon className={iconSize} />
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
<p className="text-sm font-medium text-foreground">{title}</p>
|
||
|
|
{description && (
|
||
|
|
<p className="text-sm text-muted-foreground max-w-prose">{description}</p>
|
||
|
|
)}
|
||
|
|
{button && <div className="mt-2">{button}</div>}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|