@space-man/react-theme-animation 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -142
- package/dist/core/index.cjs +74 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/{esm/types/index.d.ts → core/index.d.cts} +26 -8
- package/dist/{cjs/types → core}/index.d.ts +26 -8
- package/dist/core/index.js +66 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.cjs +1756 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1706 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.cjs +1742 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +224 -0
- package/dist/react/index.d.ts +224 -0
- package/dist/react/index.js +1697 -0
- package/dist/react/index.js.map +1 -0
- package/dist/tanstack/index.cjs +34 -0
- package/dist/tanstack/index.cjs.map +1 -0
- package/dist/tanstack/index.d.cts +20 -0
- package/dist/tanstack/index.d.ts +20 -0
- package/dist/tanstack/index.js +27 -0
- package/dist/tanstack/index.js.map +1 -0
- package/package.json +66 -37
- package/dist/cjs/components/NextThemeProvider.d.ts +0 -81
- package/dist/cjs/components/NextThemeProvider.js +0 -223
- package/dist/cjs/components/SpacemanThemeProvider.d.ts +0 -57
- package/dist/cjs/components/SpacemanThemeProvider.js +0 -113
- package/dist/cjs/components/TanStackThemeProvider.d.ts +0 -68
- package/dist/cjs/components/TanStackThemeProvider.js +0 -183
- package/dist/cjs/components/ThemeSelector.d.ts +0 -3
- package/dist/cjs/components/ThemeSelector.js +0 -39
- package/dist/cjs/components/ThemeSwitcher.d.ts +0 -3
- package/dist/cjs/components/ThemeSwitcher.js +0 -81
- package/dist/cjs/components/ViteThemeProvider.d.ts +0 -61
- package/dist/cjs/components/ViteThemeProvider.js +0 -140
- package/dist/cjs/components/ui/select.d.ts +0 -15
- package/dist/cjs/components/ui/select.js +0 -83
- package/dist/cjs/hooks/use-theme-animation.d.ts +0 -2
- package/dist/cjs/hooks/use-theme-animation.js +0 -234
- package/dist/cjs/index.d.ts +0 -10
- package/dist/cjs/index.js +0 -30
- package/dist/cjs/types/index.js +0 -9
- package/dist/cjs/utils/animations.d.ts +0 -21
- package/dist/cjs/utils/animations.js +0 -189
- package/dist/esm/components/NextThemeProvider.d.ts +0 -81
- package/dist/esm/components/NextThemeProvider.js +0 -185
- package/dist/esm/components/SpacemanThemeProvider.d.ts +0 -57
- package/dist/esm/components/SpacemanThemeProvider.js +0 -108
- package/dist/esm/components/TanStackThemeProvider.d.ts +0 -68
- package/dist/esm/components/TanStackThemeProvider.js +0 -178
- package/dist/esm/components/ThemeSelector.d.ts +0 -3
- package/dist/esm/components/ThemeSelector.js +0 -35
- package/dist/esm/components/ThemeSwitcher.d.ts +0 -3
- package/dist/esm/components/ThemeSwitcher.js +0 -77
- package/dist/esm/components/ViteThemeProvider.d.ts +0 -61
- package/dist/esm/components/ViteThemeProvider.js +0 -135
- package/dist/esm/components/ui/select.d.ts +0 -15
- package/dist/esm/components/ui/select.js +0 -39
- package/dist/esm/hooks/use-theme-animation.d.ts +0 -2
- package/dist/esm/hooks/use-theme-animation.js +0 -230
- package/dist/esm/index.d.ts +0 -10
- package/dist/esm/index.js +0 -10
- package/dist/esm/types/index.js +0 -6
- package/dist/esm/utils/animations.d.ts +0 -21
- package/dist/esm/utils/animations.js +0 -177
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React, { createContext, useContext, useState, useEffect, useCallback, } from 'react';
|
|
3
|
-
import { useThemeAnimation } from '../hooks/use-theme-animation';
|
|
4
|
-
import { ThemeAnimationType } from '../types';
|
|
5
|
-
const NextThemeContext = createContext(undefined);
|
|
6
|
-
/**
|
|
7
|
-
* Pre-hydration script content generator
|
|
8
|
-
* This script runs BEFORE React hydrates to prevent flash of wrong theme
|
|
9
|
-
*/
|
|
10
|
-
const generatePreHydrationScript = (storageKey, colorStorageKey, defaultTheme, defaultColorTheme, attribute, globalClassName, colorThemePrefix) => {
|
|
11
|
-
return `
|
|
12
|
-
(function() {
|
|
13
|
-
try {
|
|
14
|
-
var theme = localStorage.getItem('${storageKey}') || '${defaultTheme}';
|
|
15
|
-
var colorTheme = localStorage.getItem('${colorStorageKey}') || '${defaultColorTheme}';
|
|
16
|
-
var resolved = theme === 'system'
|
|
17
|
-
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
|
18
|
-
: theme;
|
|
19
|
-
|
|
20
|
-
if ('${attribute}' === 'class') {
|
|
21
|
-
if (resolved === 'dark') {
|
|
22
|
-
document.documentElement.classList.add('${globalClassName}');
|
|
23
|
-
} else {
|
|
24
|
-
document.documentElement.classList.remove('${globalClassName}');
|
|
25
|
-
}
|
|
26
|
-
} else {
|
|
27
|
-
document.documentElement.setAttribute('data-theme', resolved);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
document.documentElement.style.colorScheme = resolved;
|
|
31
|
-
document.documentElement.classList.add('${colorThemePrefix}' + colorTheme);
|
|
32
|
-
} catch (e) {
|
|
33
|
-
console.warn('Theme pre-hydration script failed:', e);
|
|
34
|
-
}
|
|
35
|
-
})();
|
|
36
|
-
`;
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* Pre-hydration script component
|
|
40
|
-
* Injects inline script before React hydration to prevent theme flash
|
|
41
|
-
*/
|
|
42
|
-
const ThemePreHydrationScript = React.memo(({ storageKey, colorStorageKey, defaultTheme, defaultColorTheme, attribute, globalClassName, colorThemePrefix, nonce, }) => {
|
|
43
|
-
const scriptContent = generatePreHydrationScript(storageKey, colorStorageKey, defaultTheme, defaultColorTheme, attribute, globalClassName, colorThemePrefix);
|
|
44
|
-
return (_jsx("script", { nonce: nonce, suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: scriptContent } }));
|
|
45
|
-
});
|
|
46
|
-
ThemePreHydrationScript.displayName = 'ThemePreHydrationScript';
|
|
47
|
-
/**
|
|
48
|
-
* Next Theme Provider - Next.js optimized theme management with animations
|
|
49
|
-
*
|
|
50
|
-
* This provider is specifically optimized for Next.js and SSR frameworks with
|
|
51
|
-
* pre-hydration script injection to prevent flash of wrong theme.
|
|
52
|
-
*
|
|
53
|
-
* Features:
|
|
54
|
-
* - Pre-hydration script for flash prevention (SSR apps)
|
|
55
|
-
* - Animated theme transitions via View Transitions API
|
|
56
|
-
* - Multi-dimensional theming (theme + colorTheme)
|
|
57
|
-
* - System theme detection
|
|
58
|
-
* - CSP support via nonce prop
|
|
59
|
-
*
|
|
60
|
-
* @param children - React children to wrap with theme context
|
|
61
|
-
* @param themes - Available theme options
|
|
62
|
-
* @param colorThemes - Available color theme options
|
|
63
|
-
* @param defaultTheme - Default theme to use
|
|
64
|
-
* @param defaultColorTheme - Default color theme to use
|
|
65
|
-
* @param animationType - Animation type for theme transitions
|
|
66
|
-
* @param duration - Animation duration in milliseconds
|
|
67
|
-
* @param storageKey - LocalStorage key for theme preference
|
|
68
|
-
* @param colorStorageKey - LocalStorage key for color theme preference
|
|
69
|
-
* @param attribute - HTML attribute to use for theme ('class' or 'data-theme')
|
|
70
|
-
* @param globalClassName - Class name for dark theme (when using 'class' attribute)
|
|
71
|
-
* @param colorThemePrefix - Prefix for color theme classes
|
|
72
|
-
* @param nonce - CSP nonce for inline script (SSR apps)
|
|
73
|
-
* @param disableAnimationOnInit - Skip animation during initial hydration
|
|
74
|
-
* @param disablePreHydrationScript - Disable flash prevention script (for SPAs without SSR)
|
|
75
|
-
*/
|
|
76
|
-
export const NextThemeProvider = ({ children, themes = ['light', 'dark', 'system'], colorThemes = ['default'], defaultTheme = 'system', defaultColorTheme = 'default', animationType = ThemeAnimationType.CIRCLE, duration = 750, storageKey = 'theme', colorStorageKey = 'color-theme', attribute = 'class', globalClassName = 'dark', colorThemePrefix = 'theme-', nonce, disableAnimationOnInit = true, disablePreHydrationScript = false, }) => {
|
|
77
|
-
const [mounted, setMounted] = useState(false);
|
|
78
|
-
const themeState = useThemeAnimation({
|
|
79
|
-
themes,
|
|
80
|
-
colorThemes,
|
|
81
|
-
defaultTheme,
|
|
82
|
-
defaultColorTheme,
|
|
83
|
-
animationType,
|
|
84
|
-
duration,
|
|
85
|
-
storageKey,
|
|
86
|
-
colorStorageKey,
|
|
87
|
-
globalClassName: attribute === 'class' ? globalClassName : undefined,
|
|
88
|
-
colorThemePrefix,
|
|
89
|
-
});
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
setMounted(true);
|
|
92
|
-
}, []);
|
|
93
|
-
const switchThemeWithHydrationAwareness = useCallback(async (theme, animationOff = false) => {
|
|
94
|
-
if (!mounted && disableAnimationOnInit) {
|
|
95
|
-
themeState.setTheme(theme);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
await themeState.switchTheme(theme, animationOff);
|
|
99
|
-
}
|
|
100
|
-
}, [mounted, disableAnimationOnInit, themeState]);
|
|
101
|
-
const switchThemeFromElement = useCallback(async (theme, element) => {
|
|
102
|
-
if (!mounted && disableAnimationOnInit) {
|
|
103
|
-
themeState.setTheme(theme);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
if (themeState.ref.current) {
|
|
107
|
-
const originalRef = themeState.ref.current;
|
|
108
|
-
Object.defineProperty(themeState.ref, 'current', {
|
|
109
|
-
value: element,
|
|
110
|
-
writable: true,
|
|
111
|
-
configurable: true,
|
|
112
|
-
});
|
|
113
|
-
await themeState.switchTheme(theme);
|
|
114
|
-
Object.defineProperty(themeState.ref, 'current', {
|
|
115
|
-
value: originalRef,
|
|
116
|
-
writable: true,
|
|
117
|
-
configurable: true,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
Object.defineProperty(themeState.ref, 'current', {
|
|
122
|
-
value: element,
|
|
123
|
-
writable: true,
|
|
124
|
-
configurable: true,
|
|
125
|
-
});
|
|
126
|
-
await themeState.switchTheme(theme);
|
|
127
|
-
}
|
|
128
|
-
}, [mounted, disableAnimationOnInit, themeState]);
|
|
129
|
-
const systemTheme = typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
130
|
-
? 'dark'
|
|
131
|
-
: 'light';
|
|
132
|
-
if (!mounted) {
|
|
133
|
-
const loadingContextValue = {
|
|
134
|
-
ref: { current: null },
|
|
135
|
-
theme: defaultTheme,
|
|
136
|
-
colorTheme: defaultColorTheme,
|
|
137
|
-
resolvedTheme: defaultTheme === 'dark' ? 'dark' : 'light',
|
|
138
|
-
systemTheme: 'light',
|
|
139
|
-
setTheme: () => { },
|
|
140
|
-
setColorTheme: () => { },
|
|
141
|
-
switchTheme: async () => { },
|
|
142
|
-
switchThemeFromElement: async () => { },
|
|
143
|
-
switchColorTheme: () => { },
|
|
144
|
-
toggleTheme: async () => { },
|
|
145
|
-
toggleLightTheme: async () => { },
|
|
146
|
-
toggleDarkTheme: async () => { },
|
|
147
|
-
toggleColorTheme: () => { },
|
|
148
|
-
createColorThemeToggle: () => () => { },
|
|
149
|
-
isColorThemeActive: () => false,
|
|
150
|
-
};
|
|
151
|
-
return (_jsxs(NextThemeContext.Provider, { value: loadingContextValue, children: [!disablePreHydrationScript && (_jsx(ThemePreHydrationScript, { storageKey: storageKey, colorStorageKey: colorStorageKey, defaultTheme: defaultTheme, defaultColorTheme: defaultColorTheme, attribute: attribute, globalClassName: globalClassName, colorThemePrefix: colorThemePrefix, nonce: nonce })), children] }));
|
|
152
|
-
}
|
|
153
|
-
const contextValue = {
|
|
154
|
-
ref: themeState.ref,
|
|
155
|
-
theme: themeState.theme,
|
|
156
|
-
colorTheme: themeState.colorTheme,
|
|
157
|
-
resolvedTheme: themeState.resolvedTheme,
|
|
158
|
-
systemTheme,
|
|
159
|
-
setTheme: themeState.setTheme,
|
|
160
|
-
setColorTheme: themeState.setColorTheme,
|
|
161
|
-
switchTheme: switchThemeWithHydrationAwareness,
|
|
162
|
-
switchThemeFromElement,
|
|
163
|
-
switchColorTheme: themeState.switchColorTheme,
|
|
164
|
-
toggleTheme: themeState.toggleTheme,
|
|
165
|
-
toggleLightTheme: themeState.toggleLightTheme,
|
|
166
|
-
toggleDarkTheme: themeState.toggleDarkTheme,
|
|
167
|
-
toggleColorTheme: themeState.toggleColorTheme,
|
|
168
|
-
createColorThemeToggle: themeState.createColorThemeToggle,
|
|
169
|
-
isColorThemeActive: themeState.isColorThemeActive,
|
|
170
|
-
};
|
|
171
|
-
return (_jsxs(NextThemeContext.Provider, { value: contextValue, children: [!disablePreHydrationScript && (_jsx(ThemePreHydrationScript, { storageKey: storageKey, colorStorageKey: colorStorageKey, defaultTheme: defaultTheme, defaultColorTheme: defaultColorTheme, attribute: attribute, globalClassName: globalClassName, colorThemePrefix: colorThemePrefix, nonce: nonce })), children] }));
|
|
172
|
-
};
|
|
173
|
-
/**
|
|
174
|
-
* Hook to consume the Next Theme context
|
|
175
|
-
*
|
|
176
|
-
* @returns Theme context value with all theme state and methods
|
|
177
|
-
* @throws Error if used outside of NextThemeProvider
|
|
178
|
-
*/
|
|
179
|
-
export const useNextTheme = () => {
|
|
180
|
-
const context = useContext(NextThemeContext);
|
|
181
|
-
if (context === undefined) {
|
|
182
|
-
throw new Error('useNextTheme must be used within a NextThemeProvider');
|
|
183
|
-
}
|
|
184
|
-
return context;
|
|
185
|
-
};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import React, { ReactNode } from 'react';
|
|
2
|
-
import { Theme, ColorTheme, ThemeAnimationType } from '../types';
|
|
3
|
-
/**
|
|
4
|
-
* Context type for the Spaceman Theme Provider
|
|
5
|
-
*/
|
|
6
|
-
interface SpacemanThemeContextType {
|
|
7
|
-
ref: React.RefObject<HTMLButtonElement | null>;
|
|
8
|
-
theme: Theme;
|
|
9
|
-
colorTheme: ColorTheme;
|
|
10
|
-
resolvedTheme: 'light' | 'dark';
|
|
11
|
-
setTheme: (theme: Theme) => void;
|
|
12
|
-
setColorTheme: (colorTheme: ColorTheme) => void;
|
|
13
|
-
switchTheme: (theme: Theme) => Promise<void>;
|
|
14
|
-
switchColorTheme: (colorTheme: string) => void;
|
|
15
|
-
toggleTheme: () => Promise<void>;
|
|
16
|
-
toggleLightTheme: () => Promise<void>;
|
|
17
|
-
toggleDarkTheme: () => Promise<void>;
|
|
18
|
-
toggleColorTheme: () => void;
|
|
19
|
-
createColorThemeToggle: (targetColorTheme: string) => () => void;
|
|
20
|
-
isColorThemeActive: (targetColorTheme: string) => boolean;
|
|
21
|
-
switchThemeFromElement: (theme: Theme, element: HTMLButtonElement) => Promise<void>;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Props for the Spaceman Theme Provider
|
|
25
|
-
*/
|
|
26
|
-
interface SpacemanThemeProviderProps {
|
|
27
|
-
children: ReactNode;
|
|
28
|
-
themes?: Theme[];
|
|
29
|
-
colorThemes?: ColorTheme[];
|
|
30
|
-
defaultTheme?: Theme;
|
|
31
|
-
defaultColorTheme?: ColorTheme;
|
|
32
|
-
animationType?: ThemeAnimationType;
|
|
33
|
-
duration?: number;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Spaceman Theme Provider - Provides centralized theme state management
|
|
37
|
-
*
|
|
38
|
-
* This provider creates a single source of truth for theme state using the useThemeAnimation hook.
|
|
39
|
-
* All theme-related components should consume from this context to ensure synchronization.
|
|
40
|
-
*
|
|
41
|
-
* @param children - React children to wrap with theme context
|
|
42
|
-
* @param themes - Available theme options (default: ['light', 'dark', 'system'])
|
|
43
|
-
* @param colorThemes - Available color theme options (default: ['default'])
|
|
44
|
-
* @param defaultTheme - Default theme to use (default: 'system')
|
|
45
|
-
* @param defaultColorTheme - Default color theme to use (default: 'default')
|
|
46
|
-
* @param animationType - Animation type for theme transitions (default: CIRCLE)
|
|
47
|
-
* @param duration - Animation duration in milliseconds (default: 750)
|
|
48
|
-
*/
|
|
49
|
-
export declare const SpacemanThemeProvider: React.FC<SpacemanThemeProviderProps>;
|
|
50
|
-
/**
|
|
51
|
-
* Hook to consume the Spaceman Theme context
|
|
52
|
-
*
|
|
53
|
-
* @returns Theme context value with all theme state and methods
|
|
54
|
-
* @throws Error if used outside of SpacemanThemeProvider
|
|
55
|
-
*/
|
|
56
|
-
export declare const useSpacemanTheme: () => SpacemanThemeContextType;
|
|
57
|
-
export {};
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useState, useEffect } from 'react';
|
|
3
|
-
import { useThemeAnimation } from '../hooks/use-theme-animation';
|
|
4
|
-
import { ThemeAnimationType } from '../types';
|
|
5
|
-
const SpacemanThemeContext = createContext(undefined);
|
|
6
|
-
/**
|
|
7
|
-
* Spaceman Theme Provider - Provides centralized theme state management
|
|
8
|
-
*
|
|
9
|
-
* This provider creates a single source of truth for theme state using the useThemeAnimation hook.
|
|
10
|
-
* All theme-related components should consume from this context to ensure synchronization.
|
|
11
|
-
*
|
|
12
|
-
* @param children - React children to wrap with theme context
|
|
13
|
-
* @param themes - Available theme options (default: ['light', 'dark', 'system'])
|
|
14
|
-
* @param colorThemes - Available color theme options (default: ['default'])
|
|
15
|
-
* @param defaultTheme - Default theme to use (default: 'system')
|
|
16
|
-
* @param defaultColorTheme - Default color theme to use (default: 'default')
|
|
17
|
-
* @param animationType - Animation type for theme transitions (default: CIRCLE)
|
|
18
|
-
* @param duration - Animation duration in milliseconds (default: 750)
|
|
19
|
-
*/
|
|
20
|
-
export const SpacemanThemeProvider = ({ children, themes = ['light', 'dark', 'system'], colorThemes = ['default'], defaultTheme = 'system', defaultColorTheme = 'default', animationType = ThemeAnimationType.CIRCLE, duration = 750, }) => {
|
|
21
|
-
const [mounted, setMounted] = useState(false);
|
|
22
|
-
const themeState = useThemeAnimation({
|
|
23
|
-
themes,
|
|
24
|
-
colorThemes,
|
|
25
|
-
defaultTheme,
|
|
26
|
-
defaultColorTheme,
|
|
27
|
-
animationType,
|
|
28
|
-
duration,
|
|
29
|
-
});
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
setMounted(true);
|
|
32
|
-
}, []);
|
|
33
|
-
const switchThemeFromElement = async (theme, element) => {
|
|
34
|
-
if (themeState.ref.current) {
|
|
35
|
-
const originalRef = themeState.ref.current;
|
|
36
|
-
Object.defineProperty(themeState.ref, 'current', {
|
|
37
|
-
value: element,
|
|
38
|
-
writable: true,
|
|
39
|
-
configurable: true,
|
|
40
|
-
});
|
|
41
|
-
await themeState.switchTheme(theme);
|
|
42
|
-
Object.defineProperty(themeState.ref, 'current', {
|
|
43
|
-
value: originalRef,
|
|
44
|
-
writable: true,
|
|
45
|
-
configurable: true,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
Object.defineProperty(themeState.ref, 'current', {
|
|
50
|
-
value: element,
|
|
51
|
-
writable: true,
|
|
52
|
-
configurable: true,
|
|
53
|
-
});
|
|
54
|
-
await themeState.switchTheme(theme);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
if (!mounted) {
|
|
58
|
-
const loadingContextValue = {
|
|
59
|
-
ref: { current: null },
|
|
60
|
-
theme: defaultTheme,
|
|
61
|
-
colorTheme: defaultColorTheme,
|
|
62
|
-
resolvedTheme: defaultTheme === 'dark' ? 'dark' : 'light',
|
|
63
|
-
setTheme: () => { },
|
|
64
|
-
setColorTheme: () => { },
|
|
65
|
-
switchTheme: async () => { },
|
|
66
|
-
switchThemeFromElement: async () => { },
|
|
67
|
-
switchColorTheme: () => { },
|
|
68
|
-
toggleTheme: async () => { },
|
|
69
|
-
toggleLightTheme: async () => { },
|
|
70
|
-
toggleDarkTheme: async () => { },
|
|
71
|
-
toggleColorTheme: () => { },
|
|
72
|
-
createColorThemeToggle: () => () => { },
|
|
73
|
-
isColorThemeActive: () => false,
|
|
74
|
-
};
|
|
75
|
-
return (_jsx(SpacemanThemeContext.Provider, { value: loadingContextValue, children: children }));
|
|
76
|
-
}
|
|
77
|
-
const contextValue = {
|
|
78
|
-
ref: themeState.ref,
|
|
79
|
-
theme: themeState.theme,
|
|
80
|
-
colorTheme: themeState.colorTheme,
|
|
81
|
-
resolvedTheme: themeState.resolvedTheme,
|
|
82
|
-
setTheme: themeState.setTheme,
|
|
83
|
-
setColorTheme: themeState.setColorTheme,
|
|
84
|
-
switchTheme: themeState.switchTheme,
|
|
85
|
-
switchThemeFromElement,
|
|
86
|
-
switchColorTheme: themeState.switchColorTheme,
|
|
87
|
-
toggleTheme: themeState.toggleTheme,
|
|
88
|
-
toggleLightTheme: themeState.toggleLightTheme,
|
|
89
|
-
toggleDarkTheme: themeState.toggleDarkTheme,
|
|
90
|
-
toggleColorTheme: themeState.toggleColorTheme,
|
|
91
|
-
createColorThemeToggle: themeState.createColorThemeToggle,
|
|
92
|
-
isColorThemeActive: themeState.isColorThemeActive,
|
|
93
|
-
};
|
|
94
|
-
return (_jsx(SpacemanThemeContext.Provider, { value: contextValue, children: children }));
|
|
95
|
-
};
|
|
96
|
-
/**
|
|
97
|
-
* Hook to consume the Spaceman Theme context
|
|
98
|
-
*
|
|
99
|
-
* @returns Theme context value with all theme state and methods
|
|
100
|
-
* @throws Error if used outside of SpacemanThemeProvider
|
|
101
|
-
*/
|
|
102
|
-
export const useSpacemanTheme = () => {
|
|
103
|
-
const context = useContext(SpacemanThemeContext);
|
|
104
|
-
if (context === undefined) {
|
|
105
|
-
throw new Error('useSpacemanTheme must be used within a SpacemanThemeProvider');
|
|
106
|
-
}
|
|
107
|
-
return context;
|
|
108
|
-
};
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import React, { ReactNode } from 'react';
|
|
2
|
-
import { Theme, ColorTheme, ThemeAnimationType } from '../types';
|
|
3
|
-
/**
|
|
4
|
-
* Context type for the TanStack Start Theme Provider
|
|
5
|
-
*/
|
|
6
|
-
interface TanStackThemeContextType {
|
|
7
|
-
ref: React.RefObject<HTMLButtonElement | null>;
|
|
8
|
-
theme: Theme;
|
|
9
|
-
colorTheme: ColorTheme;
|
|
10
|
-
resolvedTheme: 'light' | 'dark';
|
|
11
|
-
systemTheme: 'light' | 'dark';
|
|
12
|
-
isHydrated: boolean;
|
|
13
|
-
setTheme: (theme: Theme) => void;
|
|
14
|
-
setColorTheme: (colorTheme: ColorTheme) => void;
|
|
15
|
-
switchTheme: (theme: Theme, animationOff?: boolean) => Promise<void>;
|
|
16
|
-
switchColorTheme: (colorTheme: string) => void;
|
|
17
|
-
toggleTheme: (animationOff?: boolean) => Promise<void>;
|
|
18
|
-
toggleLightTheme: (animationOff?: boolean) => Promise<void>;
|
|
19
|
-
toggleDarkTheme: (animationOff?: boolean) => Promise<void>;
|
|
20
|
-
toggleColorTheme: () => void;
|
|
21
|
-
createColorThemeToggle: (targetColorTheme: string) => () => void;
|
|
22
|
-
isColorThemeActive: (targetColorTheme: string) => boolean;
|
|
23
|
-
switchThemeFromElement: (theme: Theme, element: HTMLButtonElement) => Promise<void>;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Props for the TanStack Start Theme Provider
|
|
27
|
-
*/
|
|
28
|
-
interface TanStackThemeProviderProps {
|
|
29
|
-
children: ReactNode;
|
|
30
|
-
themes?: Theme[];
|
|
31
|
-
colorThemes?: ColorTheme[];
|
|
32
|
-
defaultTheme?: Theme;
|
|
33
|
-
defaultColorTheme?: ColorTheme;
|
|
34
|
-
animationType?: ThemeAnimationType;
|
|
35
|
-
duration?: number;
|
|
36
|
-
/** Storage key for theme preference */
|
|
37
|
-
storageKey?: string;
|
|
38
|
-
/** Storage key for color theme preference */
|
|
39
|
-
colorStorageKey?: string;
|
|
40
|
-
/** Class name for dark theme */
|
|
41
|
-
globalClassName?: string;
|
|
42
|
-
/** Prefix for color theme classes */
|
|
43
|
-
colorThemePrefix?: string;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* TanStack Start Theme Provider - Optimized for TanStack Start Framework
|
|
47
|
-
*
|
|
48
|
-
* @param children - React children to wrap with theme context
|
|
49
|
-
* @param themes - Available theme options
|
|
50
|
-
* @param colorThemes - Available color theme options
|
|
51
|
-
* @param defaultTheme - Default theme to use
|
|
52
|
-
* @param defaultColorTheme - Default color theme to use
|
|
53
|
-
* @param animationType - Animation type for theme transitions
|
|
54
|
-
* @param duration - Animation duration in milliseconds
|
|
55
|
-
* @param storageKey - LocalStorage key for theme preference
|
|
56
|
-
* @param colorStorageKey - LocalStorage key for color theme preference
|
|
57
|
-
* @param globalClassName - Class name for dark theme
|
|
58
|
-
* @param colorThemePrefix - Prefix for color theme classes
|
|
59
|
-
*/
|
|
60
|
-
export declare const TanStackThemeProvider: React.FC<TanStackThemeProviderProps>;
|
|
61
|
-
/**
|
|
62
|
-
* Hook to consume the TanStack Theme context
|
|
63
|
-
*
|
|
64
|
-
* @returns Theme context value with all theme state and methods
|
|
65
|
-
* @throws Error if used outside of TanStackThemeProvider
|
|
66
|
-
*/
|
|
67
|
-
export declare const useTanStackTheme: () => TanStackThemeContextType;
|
|
68
|
-
export {};
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useCallback, useSyncExternalStore, } from 'react';
|
|
3
|
-
import { useThemeAnimation } from '../hooks/use-theme-animation';
|
|
4
|
-
import { ThemeAnimationType } from '../types';
|
|
5
|
-
const TanStackThemeContext = createContext(undefined);
|
|
6
|
-
/**
|
|
7
|
-
* Custom hook to detect hydration state
|
|
8
|
-
* This is a simplified version compatible with TanStack Start's isomorphic model
|
|
9
|
-
*
|
|
10
|
-
* In TanStack Start, you can also use the built-in `useHydrated()` hook
|
|
11
|
-
* from `@tanstack/start` if available
|
|
12
|
-
*/
|
|
13
|
-
const useHydrated = () => {
|
|
14
|
-
return useSyncExternalStore(
|
|
15
|
-
// Subscribe function (no-op for hydration state)
|
|
16
|
-
() => () => { },
|
|
17
|
-
// Client snapshot: always true after hydration
|
|
18
|
-
() => true,
|
|
19
|
-
// Server snapshot: always false during SSR
|
|
20
|
-
() => false);
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* TanStack Start Theme Provider - Optimized for TanStack Start Framework
|
|
24
|
-
*
|
|
25
|
-
* @param children - React children to wrap with theme context
|
|
26
|
-
* @param themes - Available theme options
|
|
27
|
-
* @param colorThemes - Available color theme options
|
|
28
|
-
* @param defaultTheme - Default theme to use
|
|
29
|
-
* @param defaultColorTheme - Default color theme to use
|
|
30
|
-
* @param animationType - Animation type for theme transitions
|
|
31
|
-
* @param duration - Animation duration in milliseconds
|
|
32
|
-
* @param storageKey - LocalStorage key for theme preference
|
|
33
|
-
* @param colorStorageKey - LocalStorage key for color theme preference
|
|
34
|
-
* @param globalClassName - Class name for dark theme
|
|
35
|
-
* @param colorThemePrefix - Prefix for color theme classes
|
|
36
|
-
*/
|
|
37
|
-
export const TanStackThemeProvider = ({ children, themes = ['light', 'dark', 'system'], colorThemes = ['default'], defaultTheme = 'system', defaultColorTheme = 'default', animationType = ThemeAnimationType.CIRCLE, duration = 750, storageKey = 'theme', colorStorageKey = 'color-theme', globalClassName = 'dark', colorThemePrefix = 'theme-', }) => {
|
|
38
|
-
const isHydrated = useHydrated();
|
|
39
|
-
const themeState = useThemeAnimation({
|
|
40
|
-
themes,
|
|
41
|
-
colorThemes,
|
|
42
|
-
defaultTheme,
|
|
43
|
-
defaultColorTheme,
|
|
44
|
-
animationType,
|
|
45
|
-
duration,
|
|
46
|
-
storageKey,
|
|
47
|
-
colorStorageKey,
|
|
48
|
-
globalClassName,
|
|
49
|
-
colorThemePrefix,
|
|
50
|
-
});
|
|
51
|
-
// Hydration-aware theme switch: instant during hydration, animated after
|
|
52
|
-
const switchThemeWithHydrationAwareness = useCallback(async (theme, animationOff = false) => {
|
|
53
|
-
if (!isHydrated) {
|
|
54
|
-
// During hydration: instant switch, no animation
|
|
55
|
-
themeState.setTheme(theme);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
// After hydration: animated switch
|
|
59
|
-
await themeState.switchTheme(theme, animationOff);
|
|
60
|
-
}
|
|
61
|
-
}, [isHydrated, themeState]);
|
|
62
|
-
const toggleThemeWithHydrationAwareness = useCallback(async (animationOff = false) => {
|
|
63
|
-
if (!isHydrated) {
|
|
64
|
-
const nextTheme = themeState.resolvedTheme === 'dark' ? 'light' : 'dark';
|
|
65
|
-
themeState.setTheme(nextTheme);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
await themeState.toggleTheme(animationOff);
|
|
69
|
-
}
|
|
70
|
-
}, [isHydrated, themeState]);
|
|
71
|
-
const toggleLightThemeWithHydrationAwareness = useCallback(async (animationOff = false) => {
|
|
72
|
-
if (!isHydrated) {
|
|
73
|
-
themeState.setTheme('light');
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
await themeState.toggleLightTheme(animationOff);
|
|
77
|
-
}
|
|
78
|
-
}, [isHydrated, themeState]);
|
|
79
|
-
const toggleDarkThemeWithHydrationAwareness = useCallback(async (animationOff = false) => {
|
|
80
|
-
if (!isHydrated) {
|
|
81
|
-
themeState.setTheme('dark');
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
await themeState.toggleDarkTheme(animationOff);
|
|
85
|
-
}
|
|
86
|
-
}, [isHydrated, themeState]);
|
|
87
|
-
const switchThemeFromElement = useCallback(async (theme, element) => {
|
|
88
|
-
if (!isHydrated) {
|
|
89
|
-
themeState.setTheme(theme);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (themeState.ref.current) {
|
|
93
|
-
const originalRef = themeState.ref.current;
|
|
94
|
-
Object.defineProperty(themeState.ref, 'current', {
|
|
95
|
-
value: element,
|
|
96
|
-
writable: true,
|
|
97
|
-
configurable: true,
|
|
98
|
-
});
|
|
99
|
-
await themeState.switchTheme(theme);
|
|
100
|
-
Object.defineProperty(themeState.ref, 'current', {
|
|
101
|
-
value: originalRef,
|
|
102
|
-
writable: true,
|
|
103
|
-
configurable: true,
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
Object.defineProperty(themeState.ref, 'current', {
|
|
108
|
-
value: element,
|
|
109
|
-
writable: true,
|
|
110
|
-
configurable: true,
|
|
111
|
-
});
|
|
112
|
-
await themeState.switchTheme(theme);
|
|
113
|
-
}
|
|
114
|
-
}, [isHydrated, themeState]);
|
|
115
|
-
const systemTheme = typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
116
|
-
? 'dark'
|
|
117
|
-
: 'light';
|
|
118
|
-
// During SSR/hydration: provide safe context without animations
|
|
119
|
-
if (!isHydrated) {
|
|
120
|
-
const loadingContextValue = {
|
|
121
|
-
ref: { current: null },
|
|
122
|
-
theme: defaultTheme,
|
|
123
|
-
colorTheme: defaultColorTheme,
|
|
124
|
-
resolvedTheme: defaultTheme === 'dark' ? 'dark' : 'light',
|
|
125
|
-
systemTheme: 'light',
|
|
126
|
-
isHydrated: false,
|
|
127
|
-
setTheme: themeState.setTheme,
|
|
128
|
-
setColorTheme: themeState.setColorTheme,
|
|
129
|
-
switchTheme: async (theme, animationOff = false) => themeState.setTheme(theme),
|
|
130
|
-
switchThemeFromElement: async (theme) => themeState.setTheme(theme),
|
|
131
|
-
switchColorTheme: themeState.switchColorTheme,
|
|
132
|
-
toggleTheme: async (animationOff = false) => {
|
|
133
|
-
const nextTheme = themeState.resolvedTheme === 'dark' ? 'light' : 'dark';
|
|
134
|
-
themeState.setTheme(nextTheme);
|
|
135
|
-
},
|
|
136
|
-
toggleLightTheme: async (animationOff = false) => themeState.setTheme('light'),
|
|
137
|
-
toggleDarkTheme: async (animationOff = false) => themeState.setTheme('dark'),
|
|
138
|
-
toggleColorTheme: themeState.toggleColorTheme,
|
|
139
|
-
createColorThemeToggle: themeState.createColorThemeToggle,
|
|
140
|
-
isColorThemeActive: themeState.isColorThemeActive,
|
|
141
|
-
};
|
|
142
|
-
return (_jsx(TanStackThemeContext.Provider, { value: loadingContextValue, children: children }));
|
|
143
|
-
}
|
|
144
|
-
// After hydration: full context with animations enabled
|
|
145
|
-
const contextValue = {
|
|
146
|
-
ref: themeState.ref,
|
|
147
|
-
theme: themeState.theme,
|
|
148
|
-
colorTheme: themeState.colorTheme,
|
|
149
|
-
resolvedTheme: themeState.resolvedTheme,
|
|
150
|
-
systemTheme,
|
|
151
|
-
isHydrated: true,
|
|
152
|
-
setTheme: themeState.setTheme,
|
|
153
|
-
setColorTheme: themeState.setColorTheme,
|
|
154
|
-
switchTheme: switchThemeWithHydrationAwareness,
|
|
155
|
-
switchThemeFromElement,
|
|
156
|
-
switchColorTheme: themeState.switchColorTheme,
|
|
157
|
-
toggleTheme: toggleThemeWithHydrationAwareness,
|
|
158
|
-
toggleLightTheme: toggleLightThemeWithHydrationAwareness,
|
|
159
|
-
toggleDarkTheme: toggleDarkThemeWithHydrationAwareness,
|
|
160
|
-
toggleColorTheme: themeState.toggleColorTheme,
|
|
161
|
-
createColorThemeToggle: themeState.createColorThemeToggle,
|
|
162
|
-
isColorThemeActive: themeState.isColorThemeActive,
|
|
163
|
-
};
|
|
164
|
-
return (_jsx(TanStackThemeContext.Provider, { value: contextValue, children: children }));
|
|
165
|
-
};
|
|
166
|
-
/**
|
|
167
|
-
* Hook to consume the TanStack Theme context
|
|
168
|
-
*
|
|
169
|
-
* @returns Theme context value with all theme state and methods
|
|
170
|
-
* @throws Error if used outside of TanStackThemeProvider
|
|
171
|
-
*/
|
|
172
|
-
export const useTanStackTheme = () => {
|
|
173
|
-
const context = useContext(TanStackThemeContext);
|
|
174
|
-
if (context === undefined) {
|
|
175
|
-
throw new Error('useTanStackTheme must be used within a TanStackThemeProvider');
|
|
176
|
-
}
|
|
177
|
-
return context;
|
|
178
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useThemeAnimation } from '../hooks/use-theme-animation';
|
|
3
|
-
import { useSpacemanTheme } from './SpacemanThemeProvider';
|
|
4
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
|
5
|
-
export const ThemeSelector = ({ themes = ['light', 'dark', 'system'], colorThemes = ['default'], currentColorTheme, onColorThemeChange, animationType, duration, }) => {
|
|
6
|
-
// Try to use SpacemanTheme context if available (controlled mode)
|
|
7
|
-
let contextTheme = null;
|
|
8
|
-
try {
|
|
9
|
-
contextTheme = useSpacemanTheme();
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
// Context not available, use standalone mode
|
|
13
|
-
}
|
|
14
|
-
// Use context if available, otherwise fall back to standalone hook
|
|
15
|
-
const standaloneHook = useThemeAnimation({
|
|
16
|
-
animationType,
|
|
17
|
-
duration,
|
|
18
|
-
themes,
|
|
19
|
-
colorThemes,
|
|
20
|
-
...(currentColorTheme !== undefined && { colorTheme: currentColorTheme }),
|
|
21
|
-
onColorThemeChange,
|
|
22
|
-
});
|
|
23
|
-
const isControlled = contextTheme !== null;
|
|
24
|
-
const { colorTheme, setColorTheme } = isControlled
|
|
25
|
-
? { colorTheme: contextTheme.colorTheme, setColorTheme: contextTheme.setColorTheme }
|
|
26
|
-
: standaloneHook;
|
|
27
|
-
const handleColorThemeChange = (newColorTheme) => {
|
|
28
|
-
setColorTheme(newColorTheme);
|
|
29
|
-
// Also call the callback if provided
|
|
30
|
-
if (onColorThemeChange) {
|
|
31
|
-
onColorThemeChange(newColorTheme);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
return (_jsx(_Fragment, { children: colorThemes.length > 1 && (_jsx("div", { className: "flex flex-col gap-2", children: _jsxs(Select, { value: colorTheme, onValueChange: (v) => handleColorThemeChange(v), children: [_jsx(SelectTrigger, { className: "capitalize", children: _jsx(SelectValue, { placeholder: "Choose a color theme" }) }), _jsx(SelectContent, { children: colorThemes.map(theme => (_jsx(SelectItem, { className: "capitalize", value: theme, children: theme }, theme))) })] }) })) }));
|
|
35
|
-
};
|