diff --git a/frontend/src/components/ui/Spinner.tsx b/frontend/src/components/ui/Spinner.tsx new file mode 100644 index 0000000..6ca2547 --- /dev/null +++ b/frontend/src/components/ui/Spinner.tsx @@ -0,0 +1,112 @@ +'use client' + +import React from 'react' +import { cn } from '@/lib/utils' +import { Loader2 } from 'lucide-react' + +/* ---------- types ---------- */ + +type SpinnerSize = 'sm' | 'md' | 'lg' | 'xl' + +interface SpinnerProps { + size?: SpinnerSize + className?: string + /** Tailwind color class — default: text-violet-400 */ + colorClass?: string +} + +/* ---------- size map ---------- */ + +const SIZE_MAP: Record = { + sm: 'h-4 w-4', + md: 'h-6 w-6', + lg: 'h-8 w-8', + xl: 'h-12 w-12', +} + +/* ---------- component ---------- */ + +export function Spinner({ + size = 'md', + className, + colorClass = 'text-violet-400', +}: SpinnerProps) { + return ( + + ) +} + +/* ---------- LoadingOverlay ---------- */ + +interface LoadingOverlayProps { + isOpen: boolean + /** Optional label text below spinner */ + label?: string + /** Custom spinner size */ + spinnerSize?: SpinnerSize + className?: string +} + +export function LoadingOverlay({ + isOpen, + label, + spinnerSize = 'lg', + className, +}: LoadingOverlayProps) { + if (!isOpen) return null + + return ( +
+ + {label && ( +

+ {label} +

+ )} +
+ ) +} + +/* ---------- PageSkeleton (full-page placeholder) ---------- */ + +interface PageSpinnerProps { + /** Full-screen center */ + fullscreen?: boolean + label?: string + spinnerSize?: SpinnerSize +} + +export function PageSpinner({ + fullscreen = false, + label, + spinnerSize = 'lg', +}: PageSpinnerProps) { + return ( +
+ + {label && ( +

{label}

+ )} +
+ ) +} + +export default Spinner diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts index 531166f..c3d40bc 100644 --- a/frontend/src/components/ui/index.ts +++ b/frontend/src/components/ui/index.ts @@ -14,3 +14,4 @@ export { DropdownMenuItem, } from "./DropdownMenu"; export { Pagination } from "./Pagination"; +export { Spinner, LoadingOverlay, PageSpinner } from "./Spinner";