39 lines
1.4 KiB
React
39 lines
1.4 KiB
React
|
|
import { AnimatePresence, motion } from 'framer-motion';
|
||
|
|
import { Check, CloudUpload, AlertCircle } from 'lucide-react';
|
||
|
|
import { cn } from '@/lib/utils';
|
||
|
|
|
||
|
|
const STATES = {
|
||
|
|
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 }) {
|
||
|
|
const state = STATES[status];
|
||
|
|
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>
|
||
|
|
);
|
||
|
|
}
|