| "use client"; |
|
|
| import { cn } from "@/shared/utils/cn"; |
|
|
| |
| export function Spinner({ size = "md", className }: { size?: string; className?: string }) { |
| const sizes: Record<string, string> = { |
| sm: "size-4", |
| md: "size-6", |
| lg: "size-8", |
| xl: "size-12", |
| }; |
|
|
| return ( |
| <span |
| role="status" |
| aria-label="Loading" |
| className={cn("material-symbols-outlined animate-spin text-primary", sizes[size], className)} |
| > |
| progress_activity |
| </span> |
| ); |
| } |
|
|
| |
| export function PageLoading({ message = "Loading..." }: { message?: string }) { |
| return ( |
| <div className="fixed inset-0 z-50 flex flex-col items-center justify-center bg-bg"> |
| <Spinner size="xl" /> |
| <p className="mt-4 text-text-muted">{message}</p> |
| </div> |
| ); |
| } |
|
|
| |
| export function Skeleton({ className, ...props }: { className?: string; [key: string]: any }) { |
| return ( |
| <div |
| aria-hidden="true" |
| className={cn("animate-pulse rounded-lg bg-border", className)} |
| {...props} |
| /> |
| ); |
| } |
|
|
| |
| export function CardSkeleton() { |
| return ( |
| <div className="p-6 rounded-xl border border-border bg-surface"> |
| <div className="flex items-center justify-between mb-4"> |
| <Skeleton className="h-4 w-24" /> |
| <Skeleton className="size-10 rounded-lg" /> |
| </div> |
| <Skeleton className="h-8 w-16 mb-2" /> |
| <Skeleton className="h-3 w-20" /> |
| </div> |
| ); |
| } |
|
|
| |
| export default function Loading({ |
| type = "spinner", |
| ...props |
| }: { |
| type?: string; |
| [key: string]: any; |
| }) { |
| switch (type) { |
| case "page": |
| return <PageLoading {...props} />; |
| case "skeleton": |
| return <Skeleton {...props} />; |
| case "card": |
| return <CardSkeleton {...props} />; |
| default: |
| return <Spinner {...props} />; |
| } |
| } |
|
|