45 lines
1.6 KiB
TypeScript
45 lines
1.6 KiB
TypeScript
import { AnimatePresence, motion } from 'framer-motion';
|
|
import { Check, CloudUpload, AlertCircle, type LucideIcon } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface SaveState {
|
|
icon: LucideIcon;
|
|
text: string;
|
|
cls: string;
|
|
}
|
|
|
|
const STATES: Record<string, SaveState> = {
|
|
saving: { icon: CloudUpload, text: 'Saving…', cls: 'text-muted-foreground border-border/70 bg-muted/40' },
|
|
saved: { icon: Check, text: 'Saved', cls: 'text-emerald-600 dark:text-emerald-300 border-emerald-500/30 bg-emerald-500/10' },
|
|
error: { icon: AlertCircle, text: 'Save failed', cls: 'text-rose-500 dark:text-rose-300 border-rose-500/35 bg-rose-500/10' },
|
|
};
|
|
|
|
/**
|
|
* Tiny animated pill that reflects auto-save state. Renders nothing while idle —
|
|
* the page communicates "changes save automatically" once, statically.
|
|
*/
|
|
export function SaveStatus({ status, className }: { status?: string | null; className?: string }) {
|
|
const state = status ? STATES[status] : undefined;
|
|
return (
|
|
<AnimatePresence mode="wait">
|
|
{state && (
|
|
<motion.span
|
|
key={status}
|
|
initial={{ opacity: 0, y: -2 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: 2 }}
|
|
transition={{ duration: 0.15 }}
|
|
className={cn(
|
|
'inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-medium',
|
|
state.cls,
|
|
className,
|
|
)}
|
|
>
|
|
<state.icon className={cn('h-3 w-3', status === 'saving' && 'animate-pulse')} />
|
|
{state.text}
|
|
</motion.span>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|