返回 presentation-ai
PageBackgroundSection.tsx
根目录 / src / components / presentation / controls / global-settings / sections / PageBackgroundSection.tsx
1 "use client";
2
3 import { RotateCcw } from "lucide-react";
4 import { useCallback, useMemo } from "react";
5
6 import { CompactBackgroundSelector } from "@/components/notebook/presentation/components/theme/create-theme/CompactBackgroundSelector";
7 import { Button } from "@/components/ui/button";
8 import { useDebouncedSave } from "@/hooks/presentation/useDebouncedSave";
9 import { type ThemeBackground } from "@/lib/presentation/themes";
10 import { usePresentationState } from "@/states/presentation-state";
11
12 function isGradientString(input?: string | null) {
13 if (!input) return false;
14 const v = input.toLowerCase().trim();
15 return v.startsWith("linear-gradient") || v.startsWith("radial-gradient");
16 }
17
18 function isImageString(input?: string | null) {
19 if (!input) return false;
20 return input.trim().startsWith("url(");
21 }
22
23 export function PageBackgroundSection() {
24 const pageBackground = usePresentationState((s) => s.pageBackground);
25 const setPageBackground = usePresentationState((s) => s.setPageBackground);
26 const { save } = useDebouncedSave({ delay: 800 });
27
28 const backgroundValue = useMemo<ThemeBackground | null>(() => {
29 const override = pageBackground?.backgroundOverride as string | undefined;
30 const type = pageBackground?.backgroundType as
31 | ThemeBackground["type"]
32 | undefined;
33 const gradient =
34 pageBackground?.backgroundGradient as ThemeBackground["gradient"];
35 const imageUrl = pageBackground?.backgroundImageUrl as string | undefined;
36
37 if (!override && !type && !gradient && !imageUrl) return null;
38
39 if (type === "image" || isImageString(override)) {
40 return { type: "image", override, imageUrl };
41 }
42
43 if (type === "linear" || type === "radial") {
44 return { type, override, gradient };
45 }
46
47 if (type === "solid") {
48 return { type: "solid", override };
49 }
50
51 if (isGradientString(override)) {
52 const derivedType = override?.startsWith("radial-gradient")
53 ? "radial"
54 : "linear";
55 return { type: derivedType, override, gradient };
56 }
57
58 if (override) {
59 return { type: "solid", override };
60 }
61
62 return null;
63 }, [pageBackground]);
64
65 const applyBackground = useCallback(
66 (value: ThemeBackground | undefined | null) => {
67 const prev = (pageBackground ?? {}) as Record<string, unknown>;
68 const next = { ...prev };
69
70 delete next.backgroundOverride;
71 delete next.backgroundType;
72 delete next.backgroundGradient;
73 delete next.backgroundImageUrl;
74
75 if (value) {
76 if (value.type) next.backgroundType = value.type;
77 if (value.override) next.backgroundOverride = value.override;
78 if (value.type === "linear" || value.type === "radial") {
79 if (value.gradient) next.backgroundGradient = value.gradient;
80 }
81 if (value.type === "image") {
82 if (value.imageUrl) next.backgroundImageUrl = value.imageUrl;
83 }
84 }
85
86 setPageBackground(next);
87 save({ includeMetadata: true });
88 },
89 [pageBackground, save, setPageBackground],
90 );
91
92 return (
93 <div className="space-y-3">
94 <div className="flex items-center justify-between">
95 <h3 className="text-sm font-semibold text-foreground">
96 Page background
97 </h3>
98 <Button
99 type="button"
100 variant="ghost"
101 size="sm"
102 className="h-8 px-2 text-xs"
103 onClick={() => applyBackground(null)}
104 >
105 <RotateCcw className="mr-1 h-3.5 w-3.5" />
106 Reset
107 </Button>
108 </div>
109 <CompactBackgroundSelector
110 value={backgroundValue}
111 onChange={applyBackground}
112 />
113 </div>
114 );
115 }
116
116 lines Plain Text