返回 presentation-ai
ThemeBackground.tsx
根目录 / src / components / notebook / presentation / components / theme / ThemeBackground.tsx
1 import { useEffect, useRef, useState } from "react";
2
3 import { FontLoader } from "@/components/plate/utils/font-loader";
4 import { usePresentationTheme } from "@/components/presentation/providers/PresentationThemeProvider";
5 import { resolvePresentationThemeData } from "@/lib/presentation/theme-resolution";
6 import {
7 setThemeVariables,
8 type ThemeProperties,
9 type themes,
10 } from "@/lib/presentation/themes";
11 import { cn } from "@/lib/utils";
12 import { usePresentationState } from "@/states/presentation-state";
13
14 interface ThemeBackgroundProps {
15 className?: string;
16 children: React.ReactNode;
17 themeOverride?: keyof typeof themes;
18 themeModeOverride?: "light" | "dark";
19 themeDataOverride?: ThemeProperties;
20 suppressThemeUpdates?: boolean;
21 ignorePageBackgroundOverride?: boolean;
22 }
23
24 export function ThemeBackground({
25 className,
26 children,
27 themeOverride,
28 themeModeOverride,
29 themeDataOverride,
30 suppressThemeUpdates,
31 ignorePageBackgroundOverride = false,
32 }: ThemeBackgroundProps) {
33 const presentationTheme = usePresentationState((s) => s.theme);
34 const customThemeData = usePresentationState((s) => s.customThemeData);
35 // Use our custom presentation theme hook for isolated theme control
36 const { resolvedTheme, setTheme: setPresentationThemeMode } =
37 usePresentationTheme();
38 const pageBackground = usePresentationState((s) => s.pageBackground);
39 const themeBackgroundRef = useRef<HTMLDivElement>(null);
40
41 const theme = themeOverride ?? presentationTheme;
42 const themeData =
43 themeDataOverride ??
44 resolvePresentationThemeData({ customThemeData, theme });
45 const themeMode = themeModeOverride ?? themeData?.mode ?? resolvedTheme;
46
47 const isDark = themeMode === "dark";
48 const [mounted, setMounted] = useState(false);
49
50 // Handle hydration mismatch by only rendering the gradient after mount
51 useEffect(() => {
52 setMounted(true);
53 }, []);
54
55 // Apply theme variables whenever presentation theme or dark mode changes
56 useEffect(() => {
57 if (mounted && (theme || themeData)) {
58 // Check if we're using a custom theme or a predefined theme
59 if (themeData) {
60 setThemeVariables(themeData, themeBackgroundRef.current ?? undefined);
61 }
62 }
63 }, [theme, JSON.stringify(themeData), isDark, mounted]);
64
65 // Sync the theme mode with the presentation theme
66 // When inside /presentation route, this sets the isolated presentation theme
67 // When elsewhere, this would set the global theme (existing behavior for other use cases)
68 useEffect(() => {
69 if (!mounted || !theme || suppressThemeUpdates) return;
70
71 let currentThemeData: ThemeProperties | null = null;
72
73 // Get the current theme data
74 currentThemeData = themeData ?? null;
75
76 // Sync the theme mode to match presentation theme mode
77 if (currentThemeData) {
78 const presentationThemeMode = currentThemeData.mode;
79 if (resolvedTheme !== presentationThemeMode) {
80 setPresentationThemeMode(presentationThemeMode);
81 }
82 }
83 }, [
84 theme,
85 themeData,
86 mounted,
87 resolvedTheme,
88 setPresentationThemeMode,
89 suppressThemeUpdates,
90 ]);
91
92 // Get the current theme colors
93 const currentTheme: ThemeProperties | undefined = themeData ?? undefined;
94
95 if (!currentTheme || !mounted) {
96 return (
97 <div className={cn("h-max min-h-full w-full bg-background", className)}>
98 {children}
99 </div>
100 );
101 }
102
103 const colors = currentTheme.colors;
104
105 // Get theme-level background configuration
106 const themeBackground = currentTheme.background;
107
108 // Use theme background or fallback to neutral color
109 // Note: We don't use colors.background here because that's the slide color,
110 // and we want the theme background to be independent
111 const computedBackground =
112 (!ignorePageBackgroundOverride && pageBackground.backgroundOverride) ||
113 themeBackground?.override || // Theme background
114 (isDark ? "#0a0a0a" : "#ffffff"); // Neutral fallback (not tied to slide color)
115
116 const gradientStyle = {
117 background: computedBackground,
118 transition: currentTheme.transitions.default,
119 color: colors.text,
120 } as React.CSSProperties;
121
122 return (
123 <div
124 className={cn("theme-background h-max min-h-full w-full", className)}
125 style={gradientStyle}
126 ref={themeBackgroundRef}
127 >
128 <FontLoader
129 fontsToLoad={[currentTheme.fonts.heading, currentTheme.fonts.body]}
130 />
131 {children}
132 </div>
133 );
134 }
135
135 lines Plain Text