feat: animation de confirmation sur le bouton save
Ajoute 3 états visuels au bouton save : repos (gris), saving (spinner), saved (fond vert + checkmark animé pendant 2 secondes). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,3 +25,14 @@ input:focus, select:focus, textarea:focus {
|
||||
outline: none;
|
||||
ring: 2px;
|
||||
}
|
||||
|
||||
@keyframes check {
|
||||
0% { stroke-dashoffset: 20; opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
100% { stroke-dashoffset: 0; opacity: 1; }
|
||||
}
|
||||
|
||||
.check-icon polyline {
|
||||
stroke-dasharray: 20;
|
||||
animation: check 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ export function EvaluationEditor({ id, initialEvaluation, templates, users }: Ev
|
||||
const router = useRouter();
|
||||
const [evaluation, setEvaluation] = useState<Evaluation>(initialEvaluation);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [saved, setSaved] = useState(false);
|
||||
const [exportOpen, setExportOpen] = useState(false);
|
||||
const [shareOpen, setShareOpen] = useState(false);
|
||||
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
||||
@@ -149,6 +150,8 @@ export function EvaluationEditor({ id, initialEvaluation, templates, users }: Ev
|
||||
});
|
||||
if (result.success) {
|
||||
if (!options?.skipRefresh) fetchEval();
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 2000);
|
||||
} else {
|
||||
alert(result.error);
|
||||
}
|
||||
@@ -208,9 +211,20 @@ export function EvaluationEditor({ id, initialEvaluation, templates, users }: Ev
|
||||
<button
|
||||
onClick={() => handleSave()}
|
||||
disabled={saving}
|
||||
className="rounded border border-zinc-300 dark:border-zinc-600 bg-zinc-100 dark:bg-zinc-700 px-3 py-1.5 font-mono text-xs text-zinc-700 dark:text-zinc-300 hover:bg-zinc-200 dark:hover:bg-zinc-700 disabled:opacity-50"
|
||||
className={`rounded border px-3 py-1.5 font-mono text-xs disabled:opacity-50 transition-all duration-300 flex items-center gap-1.5 ${
|
||||
saved
|
||||
? "border-emerald-500/50 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
|
||||
: "border-zinc-300 dark:border-zinc-600 bg-zinc-100 dark:bg-zinc-700 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-200 dark:hover:bg-zinc-600"
|
||||
}`}
|
||||
>
|
||||
{saving ? "..." : "save"}
|
||||
{saving ? (
|
||||
<span className="inline-block h-3 w-3 animate-spin rounded-full border border-zinc-400 border-t-transparent" />
|
||||
) : saved ? (
|
||||
<svg className="check-icon h-3 w-3" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="1.5,6 4.5,9 10.5,3" />
|
||||
</svg>
|
||||
) : null}
|
||||
{saving ? "saving" : saved ? "saved" : "save"}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
|
||||
Reference in New Issue
Block a user