返回 presentation-ai
PresentationSavingIndicator.tsx
根目录 / src / components / presentation / core / PresentationSavingIndicator.tsx
1 "use client";
2
3 import { CheckCircle, Loader2 } from "lucide-react";
4 import { useEffect, useState } from "react";
5
6 import { usePresentationState } from "@/states/presentation-state";
7
8 export function PresentationSavingIndicator() {
9 const savingStatus = usePresentationState((s) => s.savingStatus);
10 const isReadOnly = usePresentationState((s) => s.isReadOnly);
11
12 const [visibleStatus, setVisibleStatus] = useState<
13 "idle" | "saving" | "saved"
14 >("idle");
15
16 useEffect(() => {
17 let timeout: NodeJS.Timeout;
18
19 if (savingStatus === "saving") {
20 setVisibleStatus("saving");
21 } else if (savingStatus === "saved") {
22 setVisibleStatus("saved");
23 timeout = setTimeout(() => {
24 setVisibleStatus("idle");
25 }, 2000);
26 } else if (savingStatus === "idle") {
27 setVisibleStatus("idle");
28 }
29
30 return () => {
31 if (timeout) clearTimeout(timeout);
32 };
33 }, [savingStatus]);
34
35 if (isReadOnly || visibleStatus === "idle") return null;
36
37 return (
38 <div className="flex items-center gap-1.5 px-2 text-sm text-muted-foreground">
39 {visibleStatus === "saving" && (
40 <>
41 <Loader2 className="size-3.5" />
42 <span className="hidden sm:inline">
43 Saving…
44 </span>
45 </>
46 )}
47 {visibleStatus === "saved" && (
48 <>
49 <CheckCircle className="size-3.5 text-green-600" />
50 <span className="hidden text-green-600 sm:inline">
51 Saved
52 </span>
53 </>
54 )}
55 </div>
56 );
57 }
58
58 lines Plain Text