Add global animation system and entrance effects to UI

This commit is contained in:
2025-08-01 14:21:10 -04:00
parent eaf185d89e
commit 93ffdf3c86
18 changed files with 1363 additions and 124 deletions
+352
View File
@@ -0,0 +1,352 @@
import { cn } from "~/lib/utils";
interface SkeletonProps {
className?: string;
}
export function Skeleton({ className }: SkeletonProps) {
return (
<div className={cn("skeleton bg-muted animate-pulse rounded", className)} />
);
}
// Page Header Skeleton
export function PageHeaderSkeleton() {
return (
<div className="mb-8">
<div className="flex items-start justify-between gap-4">
<div className="space-y-2">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-5 w-96" />
</div>
<div className="flex gap-2">
<Skeleton className="h-10 w-24" />
<Skeleton className="h-10 w-32" />
</div>
</div>
</div>
);
}
// Invoice Items Skeleton
export function InvoiceItemsSkeleton() {
return (
<div className="space-y-4">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="rounded-lg border p-4">
<div className="space-y-3">
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0 flex-1 space-y-2">
<Skeleton className="h-5 w-3/4" />
<div className="flex flex-wrap gap-x-4 gap-y-1">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-4 w-16" />
<Skeleton className="h-4 w-24" />
</div>
</div>
<Skeleton className="h-6 w-20" />
</div>
</div>
</div>
))}
</div>
);
}
// Recent Activity Skeleton
export function RecentActivitySkeleton() {
return (
<div className="space-y-3">
{Array.from({ length: 5 }).map((_, i) => (
<div
key={i}
className="bg-muted/50 border-foreground/20 rounded-lg border p-3"
>
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-center gap-3">
<Skeleton className="h-8 w-8 rounded" />
<div className="space-y-1">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-3 w-32" />
</div>
</div>
<div className="flex items-center gap-2">
<Skeleton className="h-5 w-12 rounded-full" />
<Skeleton className="h-4 w-16" />
</div>
</div>
</div>
))}
</div>
);
}
// Current Work Skeleton
export function CurrentWorkSkeleton() {
return (
<div className="space-y-4">
<div className="flex flex-row items-center justify-between space-y-0 pb-2">
<div className="flex items-center gap-2">
<Skeleton className="h-5 w-5" />
<Skeleton className="h-6 w-24" />
</div>
<Skeleton className="h-6 w-20 rounded-full" />
</div>
<div className="space-y-4">
<div className="space-y-2">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<Skeleton className="h-6 w-32" />
<Skeleton className="h-8 w-24" />
</div>
<div className="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between">
<Skeleton className="h-4 w-28" />
<Skeleton className="h-4 w-20" />
</div>
</div>
<div className="flex gap-2">
<Skeleton className="h-8 flex-1" />
<Skeleton className="h-8 flex-1" />
</div>
</div>
</div>
);
}
// Client Info Skeleton
export function ClientInfoSkeleton() {
return (
<div className="space-y-6">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="flex items-center space-x-3">
<Skeleton className="h-8 w-8" />
<div className="space-y-1">
<Skeleton className="h-4 w-12" />
<Skeleton className="h-4 w-32" />
</div>
</div>
<div className="flex items-center space-x-3">
<Skeleton className="h-8 w-8" />
<div className="space-y-1">
<Skeleton className="h-4 w-12" />
<Skeleton className="h-4 w-24" />
</div>
</div>
</div>
<div>
<Skeleton className="mb-4 h-6 w-32" />
<div className="flex items-start space-x-3">
<Skeleton className="h-8 w-8" />
<div className="space-y-1">
<Skeleton className="h-4 w-40" />
<Skeleton className="h-4 w-36" />
<Skeleton className="h-4 w-28" />
</div>
</div>
</div>
</div>
);
}
// Stats Summary Skeleton
export function StatsSummarySkeleton() {
return (
<div className="space-y-4">
<div className="space-y-2 text-center">
<Skeleton className="mx-auto h-8 w-24" />
<Skeleton className="mx-auto h-4 w-20" />
</div>
<div className="grid grid-cols-2 gap-4 text-center">
<div className="space-y-1">
<Skeleton className="mx-auto h-6 w-8" />
<Skeleton className="mx-auto h-3 w-12" />
</div>
<div className="space-y-1">
<Skeleton className="mx-auto h-6 w-8" />
<Skeleton className="mx-auto h-3 w-16" />
</div>
</div>
</div>
);
}
// Recent Invoices Skeleton
export function RecentInvoicesSkeleton() {
return (
<div className="space-y-3">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="card-secondary hover:bg-muted/50 border p-3">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div className="min-w-0 space-y-1">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-3 w-20" />
</div>
<div className="flex items-center gap-2">
<Skeleton className="h-4 w-16" />
<Skeleton className="h-5 w-12 rounded-full" />
</div>
</div>
</div>
))}
</div>
);
}
// Settings Form Skeleton
export function SettingsFormSkeleton() {
return (
<div className="space-y-6">
<div className="space-y-4">
<div className="space-y-2">
<Skeleton className="h-4 w-16" />
<Skeleton className="h-10 w-full" />
</div>
<div className="space-y-2">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-10 w-full" />
</div>
</div>
<div className="flex gap-2">
<Skeleton className="h-10 w-24" />
<Skeleton className="h-10 w-20" />
</div>
</div>
);
}
// Data Stats Skeleton
export function DataStatsSkeleton() {
return (
<div className="space-y-4">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="bg-card border p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Skeleton className="h-8 w-8" />
<Skeleton className="h-4 w-20" />
</div>
<Skeleton className="h-6 w-8" />
</div>
</div>
))}
</div>
);
}
// Invoice Summary Skeleton
export function InvoiceSummarySkeleton() {
return (
<div className="bg-muted/30 rounded-lg p-4">
<div className="space-y-3">
<div className="flex justify-between">
<Skeleton className="h-4 w-16" />
<Skeleton className="h-4 w-20" />
</div>
<div className="flex justify-between">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-4 w-16" />
</div>
<div className="bg-border h-px" />
<div className="flex justify-between">
<Skeleton className="h-5 w-12" />
<Skeleton className="h-5 w-24" />
</div>
</div>
</div>
);
}
// Actions Sidebar Skeleton
export function ActionsSidebarSkeleton() {
return (
<div className="space-y-3">
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
</div>
);
}
// Form Section Skeleton
export function FormSectionSkeleton() {
return (
<div className="space-y-6">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-10 w-full" />
</div>
<div className="space-y-2">
<Skeleton className="h-4 w-16" />
<Skeleton className="h-10 w-full" />
</div>
</div>
<div className="space-y-2">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-32 w-full" />
</div>
</div>
);
}
// Line Items Table Skeleton
export function LineItemsTableSkeleton() {
return (
<div className="space-y-2">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="grid grid-cols-5 gap-4 rounded border p-3">
<Skeleton className="h-8 w-full" />
<Skeleton className="h-8 w-full" />
<Skeleton className="h-8 w-full" />
<Skeleton className="h-8 w-full" />
<Skeleton className="h-8 w-full" />
</div>
))}
</div>
);
}
// Business Card Skeleton
export function BusinessCardSkeleton() {
return (
<div className="space-y-4 rounded-lg border p-4">
<div className="flex items-start justify-between">
<div className="space-y-2">
<Skeleton className="h-5 w-32" />
<Skeleton className="h-4 w-24" />
</div>
<Skeleton className="h-5 w-16 rounded-full" />
</div>
<div className="space-y-2">
<Skeleton className="h-4 w-40" />
<Skeleton className="h-4 w-28" />
</div>
</div>
);
}
// Generic Card Grid Skeleton
export function CardGridSkeleton({ count = 6 }: { count?: number }) {
return (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: count }).map((_, i) => (
<div key={i} className="rounded-lg border p-4">
<div className="space-y-3">
<div className="flex items-center gap-3">
<Skeleton className="h-8 w-8" />
<Skeleton className="h-5 w-24" />
</div>
<div className="space-y-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4" />
</div>
<div className="flex items-center justify-between">
<Skeleton className="h-4 w-16" />
<Skeleton className="h-8 w-20" />
</div>
</div>
</div>
))}
</div>
);
}