117 lines
3.3 KiB
TypeScript
117 lines
3.3 KiB
TypeScript
|
|
import React from "react";
|
||
|
|
import {
|
||
|
|
Empty,
|
||
|
|
EmptyContent,
|
||
|
|
EmptyDescription,
|
||
|
|
EmptyHeader,
|
||
|
|
EmptyMedia,
|
||
|
|
EmptyTitle,
|
||
|
|
} from "./ui/empty";
|
||
|
|
import {
|
||
|
|
IconFolderCode,
|
||
|
|
IconFileX,
|
||
|
|
IconSearch,
|
||
|
|
IconInbox,
|
||
|
|
IconDatabaseOff,
|
||
|
|
IconClipboardX,
|
||
|
|
} from "@tabler/icons-react";
|
||
|
|
import { ArrowUpRightIcon } from "lucide-react";
|
||
|
|
|
||
|
|
interface NoDataProps {
|
||
|
|
title?: string;
|
||
|
|
description?: string;
|
||
|
|
icon?:
|
||
|
|
| "search"
|
||
|
|
| "file"
|
||
|
|
| "inbox"
|
||
|
|
| "folder"
|
||
|
|
| "database"
|
||
|
|
| "clipboard"
|
||
|
|
| React.ReactNode;
|
||
|
|
action?: React.ReactNode;
|
||
|
|
variant?: "default" | "outline" | "muted" | "gradient";
|
||
|
|
showLearnMore?: boolean;
|
||
|
|
learnMoreHref?: string;
|
||
|
|
learnMoreText?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
const NoData: React.FC<NoDataProps> = ({
|
||
|
|
title = "Nenhum resultado encontrado",
|
||
|
|
description = "Não foram encontrados registros com os filtros informados. Tente ajustar os parâmetros de pesquisa.",
|
||
|
|
icon = "search",
|
||
|
|
action,
|
||
|
|
variant = "outline",
|
||
|
|
showLearnMore = false,
|
||
|
|
learnMoreHref = "#",
|
||
|
|
learnMoreText = "Saiba mais",
|
||
|
|
}) => {
|
||
|
|
const getIcon = () => {
|
||
|
|
if (React.isValidElement(icon)) {
|
||
|
|
return icon;
|
||
|
|
}
|
||
|
|
|
||
|
|
const iconClass = "h-10 w-10 text-slate-400";
|
||
|
|
|
||
|
|
switch (icon) {
|
||
|
|
case "file":
|
||
|
|
return <IconFileX className={iconClass} stroke={1.5} />;
|
||
|
|
case "inbox":
|
||
|
|
return <IconInbox className={iconClass} stroke={1.5} />;
|
||
|
|
case "folder":
|
||
|
|
return <IconFolderCode className={iconClass} stroke={1.5} />;
|
||
|
|
case "database":
|
||
|
|
return <IconDatabaseOff className={iconClass} stroke={1.5} />;
|
||
|
|
case "clipboard":
|
||
|
|
return <IconClipboardX className={iconClass} stroke={1.5} />;
|
||
|
|
case "search":
|
||
|
|
default:
|
||
|
|
return <IconSearch className={iconClass} stroke={1.5} />;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getVariantClasses = () => {
|
||
|
|
const baseClasses = "min-h-[450px] w-full";
|
||
|
|
|
||
|
|
switch (variant) {
|
||
|
|
case "outline":
|
||
|
|
return `${baseClasses} border-2 border-dashed border-slate-200 bg-white`;
|
||
|
|
case "muted":
|
||
|
|
return `${baseClasses} bg-gradient-to-b from-slate-50/80 via-white to-white`;
|
||
|
|
case "gradient":
|
||
|
|
return `${baseClasses} bg-gradient-to-br from-slate-50 via-blue-50/30 to-white border border-slate-100`;
|
||
|
|
default:
|
||
|
|
return `${baseClasses} bg-white`;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Empty className={getVariantClasses()}>
|
||
|
|
<EmptyHeader>
|
||
|
|
<EmptyMedia variant="icon" className="mb-6">
|
||
|
|
<div className="flex h-20 w-20 items-center justify-center rounded-2xl bg-gradient-to-br from-slate-50 to-slate-100 border border-slate-200/50 shadow-sm">
|
||
|
|
{getIcon()}
|
||
|
|
</div>
|
||
|
|
</EmptyMedia>
|
||
|
|
<EmptyTitle className="text-xl font-black text-slate-900 mb-3">
|
||
|
|
{title}
|
||
|
|
</EmptyTitle>
|
||
|
|
<EmptyDescription className="text-sm text-slate-600 max-w-md leading-relaxed">
|
||
|
|
{description}
|
||
|
|
</EmptyDescription>
|
||
|
|
</EmptyHeader>
|
||
|
|
{action && <EmptyContent className="mt-8">{action}</EmptyContent>}
|
||
|
|
{showLearnMore && (
|
||
|
|
<a
|
||
|
|
href={learnMoreHref}
|
||
|
|
className="mt-6 inline-flex items-center gap-1 text-sm font-medium text-slate-500 hover:text-[#002147] transition-colors"
|
||
|
|
>
|
||
|
|
{learnMoreText}
|
||
|
|
<ArrowUpRightIcon className="h-4 w-4" />
|
||
|
|
</a>
|
||
|
|
)}
|
||
|
|
</Empty>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default NoData;
|