- Apply Climate Monitoring design system to all 81 files - Replace 931 hardcoded color references with CSS variables - Consistent theming: --color-primary, --color-text-*, --color-bg-* - Status colors: --color-error, --color-warning, --color-accent
129 lines
4.4 KiB
TypeScript
129 lines
4.4 KiB
TypeScript
import { ReactNode } from "react";
|
|
import { ArrowRight, LucideIcon } from "lucide-react";
|
|
import { Link } from "react-router-dom";
|
|
import { cn } from "../../lib/utils";
|
|
|
|
/**
|
|
* Bento Grid Components - Feature 14 from AuraUI
|
|
*
|
|
* A responsive, modular grid system for dashboard widgets.
|
|
*/
|
|
|
|
// --- Grid Container ---
|
|
export const BentoGrid = ({
|
|
children,
|
|
className,
|
|
}: {
|
|
children: ReactNode;
|
|
className?: string;
|
|
}) => {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"grid w-full auto-rows-[22rem] grid-cols-1 md:grid-cols-3 gap-4",
|
|
className
|
|
)}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// --- Grid Item (Card) ---
|
|
interface BentoCardProps {
|
|
name: string;
|
|
className: string;
|
|
background?: ReactNode;
|
|
Icon: LucideIcon;
|
|
description: string;
|
|
href: string;
|
|
cta?: string;
|
|
children?: ReactNode; // For custom content inside the card
|
|
value?: string | number; // For metric displays
|
|
}
|
|
|
|
export const BentoCard = ({
|
|
name,
|
|
className,
|
|
background,
|
|
Icon,
|
|
description,
|
|
href,
|
|
cta = "View Details",
|
|
children,
|
|
value
|
|
}: BentoCardProps) => {
|
|
return (
|
|
<div
|
|
key={name}
|
|
className={cn(
|
|
"group relative col-span-3 flex flex-col justify-between overflow-hidden rounded-xl",
|
|
// Light mode styles
|
|
"bg-white border border-slate-200", // shadow-sm
|
|
// Dark mode styles
|
|
"dark:bg-slate-950 dark:border-slate-800",
|
|
className
|
|
)}
|
|
>
|
|
{/* Background Graphic (optional) */}
|
|
<div className="absolute inset-0 z-0 opacity-20 group-hover:opacity-40 transition-opacity duration-500">
|
|
{background}
|
|
</div>
|
|
|
|
{/* Content Area */}
|
|
<div className="relative z-10 p-6 flex flex-col h-full pointer-events-none">
|
|
|
|
{/* Header */}
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<div className="p-2 rounded-lg bg-[var(--color-bg-tertiary)] group-hover:bg-cyan-50 dark:group-hover:bg-cyan-950/30 transition-colors duration-300">
|
|
<Icon className="h-5 w-5 text-slate-600 dark:text-[var(--color-text-tertiary)] group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors" />
|
|
</div>
|
|
<h3 className="font-semibold text-slate-900 dark:text-slate-50 text-lg">
|
|
{name}
|
|
</h3>
|
|
</div>
|
|
|
|
{/* Metric Value (if provided) - Large display */}
|
|
{value && (
|
|
<div className="mt-4 mb-2">
|
|
<span className="text-4xl font-bold text-[var(--color-text-primary)] tracking-tight">
|
|
{value}
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Description / Subtext */}
|
|
<p className="text-[var(--color-text-tertiary)] text-sm leading-relaxed max-w-[90%]">
|
|
{description}
|
|
</p>
|
|
|
|
{/* Custom Children (Charts, Lists, etc) */}
|
|
{children && (
|
|
<div className="flex-1 mt-4 min-h-0 overflow-hidden">
|
|
{children}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Interactive Footer / CTA */}
|
|
<div
|
|
className={cn(
|
|
"pointer-events-auto",
|
|
"absolute bottom-0 flex w-full translate-y-10 flex-row items-center p-4 opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100",
|
|
"bg-gradient-to-t from-white via-white to-transparent dark:from-slate-950 dark:via-slate-950"
|
|
)}
|
|
>
|
|
<Link
|
|
to={href}
|
|
className="flex items-center gap-2 text-sm font-medium text-cyan-600 dark:text-cyan-400 hover:text-cyan-700 dark:hover:text-cyan-300"
|
|
>
|
|
{cta}
|
|
<ArrowRight className="ml-2 h-4 w-4" />
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Hover Effect Details */}
|
|
<div className="pointer-events-none absolute inset-0 transform-gpu transition-all duration-300 group-hover:bg-black/[.03] group-hover:dark:bg-neutral-800/10" />
|
|
</div>
|
|
);
|
|
};
|