mirror of
https://github.com/SamyRai/tercul-frontend.git
synced 2025-12-27 04:51:34 +00:00
Implement ActivityFeed, DashboardHeader, EmptyState, StatCard, TagInput, DataTable, WorkPreview components and add component implementation tracker. Replit-Commit-Author: Agent Replit-Commit-Session-Id: bddfbb2b-6d6b-457b-b18c-05792cdaa035 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/39b5c689-6e8a-4d5a-9792-69cc81a56534/90ee7bd9-7d3b-4bfb-93e2-2aa13b83f414.jpg
141 lines
3.9 KiB
TypeScript
141 lines
3.9 KiB
TypeScript
import { cn } from "@/lib/utils";
|
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
import { LucideIcon } from "lucide-react";
|
|
|
|
/**
|
|
* Stat card component for displaying statistical metrics
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <StatCard
|
|
* title="Total Works"
|
|
* value="124"
|
|
* description="+12% from last month"
|
|
* trend="up"
|
|
* icon={Book}
|
|
* />
|
|
* ```
|
|
*/
|
|
|
|
const statCardVariants = cva(
|
|
"rounded-lg border p-4 flex flex-col space-y-2",
|
|
{
|
|
variants: {
|
|
variant: {
|
|
default: "bg-background border-border",
|
|
primary: "bg-primary/10 border-primary/20",
|
|
secondary: "bg-secondary/10 border-secondary/20",
|
|
accent: "bg-russet/10 border-russet/20 dark:bg-russet/5",
|
|
success: "bg-emerald-50 border-emerald-200 dark:bg-emerald-950/20 dark:border-emerald-900/30",
|
|
warning: "bg-amber-50 border-amber-200 dark:bg-amber-950/20 dark:border-amber-900/30",
|
|
danger: "bg-rose-50 border-rose-200 dark:bg-rose-950/20 dark:border-rose-900/30",
|
|
},
|
|
size: {
|
|
default: "p-4",
|
|
sm: "p-3",
|
|
lg: "p-6",
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
variant: "default",
|
|
size: "default",
|
|
},
|
|
}
|
|
);
|
|
|
|
export interface StatCardProps extends VariantProps<typeof statCardVariants> {
|
|
/**
|
|
* The title of the stat
|
|
*/
|
|
title: string;
|
|
/**
|
|
* The value to display
|
|
*/
|
|
value: string | number;
|
|
/**
|
|
* Optional description text (like change from previous period)
|
|
*/
|
|
description?: string;
|
|
/**
|
|
* Optional trend direction
|
|
*/
|
|
trend?: "up" | "down" | "neutral";
|
|
/**
|
|
* Optional icon to display
|
|
*/
|
|
icon?: LucideIcon;
|
|
/**
|
|
* Additional CSS classes
|
|
*/
|
|
className?: string;
|
|
/**
|
|
* Whether the stat is loading
|
|
*/
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
export function StatCard({
|
|
title,
|
|
value,
|
|
description,
|
|
trend,
|
|
icon: Icon,
|
|
variant,
|
|
size,
|
|
className,
|
|
isLoading = false,
|
|
}: StatCardProps) {
|
|
// Determine the color for trend
|
|
const trendColor = trend === "up"
|
|
? "text-emerald-600 dark:text-emerald-400"
|
|
: trend === "down"
|
|
? "text-rose-600 dark:text-rose-400"
|
|
: "text-gray-600 dark:text-gray-400";
|
|
|
|
// Determine the arrow for trend
|
|
const trendSymbol = trend === "up"
|
|
? "↑"
|
|
: trend === "down"
|
|
? "↓"
|
|
: "→";
|
|
|
|
return (
|
|
<div className={cn(statCardVariants({ variant, size }), className)}>
|
|
{/* Header with icon and title */}
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="text-sm font-medium text-muted-foreground">{title}</h3>
|
|
{Icon && (
|
|
<div className={cn(
|
|
"rounded-full p-1.5",
|
|
variant === "primary" && "bg-primary/10 text-primary",
|
|
variant === "secondary" && "bg-secondary/10 text-secondary",
|
|
variant === "accent" && "bg-russet/10 text-russet dark:bg-russet/5",
|
|
variant === "success" && "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400",
|
|
variant === "warning" && "bg-amber-100 text-amber-600 dark:bg-amber-900/30 dark:text-amber-400",
|
|
variant === "danger" && "bg-rose-100 text-rose-600 dark:bg-rose-900/30 dark:text-rose-400",
|
|
!variant && "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400",
|
|
)}>
|
|
<Icon className="h-4 w-4" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Value */}
|
|
{isLoading ? (
|
|
<div className="h-8 w-24 bg-gray-200 dark:bg-gray-800 animate-pulse rounded" />
|
|
) : (
|
|
<div className="text-2xl font-bold">{value}</div>
|
|
)}
|
|
|
|
{/* Description with trend */}
|
|
{description && (
|
|
<div className="text-xs flex items-center gap-1">
|
|
{trend && <span className={cn("font-medium", trendColor)}>{trendSymbol}</span>}
|
|
<span className={cn(trend ? trendColor : "text-muted-foreground")}>
|
|
{description}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
} |