@space-man/react-theme-animation 1.1.1 → 2.1.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 +129 -380
- package/dist/cjs/components/NextThemeProvider.d.ts +81 -0
- package/dist/cjs/components/NextThemeProvider.js +223 -0
- package/dist/cjs/components/SpacemanThemeProvider.d.ts +8 -2
- package/dist/cjs/components/SpacemanThemeProvider.js +16 -4
- package/dist/cjs/components/TanStackThemeProvider.d.ts +68 -0
- package/dist/cjs/components/TanStackThemeProvider.js +183 -0
- package/dist/cjs/components/ViteThemeProvider.d.ts +49 -7
- package/dist/cjs/components/ViteThemeProvider.js +115 -60
- package/dist/cjs/hooks/use-theme-animation.js +51 -37
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.js +8 -1
- package/dist/cjs/types/index.d.ts +8 -2
- package/dist/esm/components/NextThemeProvider.d.ts +81 -0
- package/dist/esm/components/NextThemeProvider.js +185 -0
- package/dist/esm/components/SpacemanThemeProvider.d.ts +8 -2
- package/dist/esm/components/SpacemanThemeProvider.js +16 -4
- package/dist/esm/components/TanStackThemeProvider.d.ts +68 -0
- package/dist/esm/components/TanStackThemeProvider.js +178 -0
- package/dist/esm/components/ViteThemeProvider.d.ts +49 -7
- package/dist/esm/components/ViteThemeProvider.js +117 -62
- package/dist/esm/hooks/use-theme-animation.js +51 -37
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +3 -0
- package/dist/esm/types/index.d.ts +8 -2
- package/package.json +1 -1
|
@@ -1,75 +1,130 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createContext, useContext,
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useCallback } from 'react';
|
|
3
|
+
import { useThemeAnimation } from '../hooks/use-theme-animation';
|
|
4
|
+
import { ThemeAnimationType } from '../types';
|
|
3
5
|
const ViteThemeContext = createContext(undefined);
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Vite Theme Provider - Optimized for Vite SPA applications
|
|
8
|
+
*
|
|
9
|
+
* This provider is designed for Vite-based single-page applications with optional
|
|
10
|
+
* transition disabling to prevent flash during theme switches.
|
|
11
|
+
*
|
|
12
|
+
* Note: For flash prevention on initial load, add a script to your index.html
|
|
13
|
+
* or inject it via Vite plugin to apply theme before React loads.
|
|
14
|
+
*
|
|
15
|
+
* @param children - React children to wrap with theme context
|
|
16
|
+
* @param themes - Available theme options
|
|
17
|
+
* @param colorThemes - Available color theme options
|
|
18
|
+
* @param defaultTheme - Default theme to use
|
|
19
|
+
* @param defaultColorTheme - Default color theme to use
|
|
20
|
+
* @param animationType - Animation type for theme transitions
|
|
21
|
+
* @param duration - Animation duration in milliseconds
|
|
22
|
+
* @param attribute - HTML attribute to use for theme ('class' or 'data-theme')
|
|
23
|
+
* @param disableTransitionOnChange - Temporarily disable CSS transitions during theme switch
|
|
24
|
+
* @param storageKey - LocalStorage key for theme preference
|
|
25
|
+
* @param colorStorageKey - LocalStorage key for color theme preference
|
|
26
|
+
* @param globalClassName - Class name for dark theme (when using 'class' attribute)
|
|
27
|
+
* @param colorThemePrefix - Prefix for color theme classes
|
|
28
|
+
*/
|
|
29
|
+
export const ViteThemeProvider = ({ children, themes = ['light', 'dark', 'system'], colorThemes = ['default'], defaultTheme = 'system', defaultColorTheme = 'default', animationType = ThemeAnimationType.CIRCLE, duration = 750, attribute = 'class', disableTransitionOnChange = false, storageKey = 'vite-theme', colorStorageKey = 'vite-color-theme', globalClassName = 'dark', colorThemePrefix = 'theme-', }) => {
|
|
30
|
+
const themeState = useThemeAnimation({
|
|
31
|
+
themes,
|
|
32
|
+
colorThemes,
|
|
33
|
+
defaultTheme,
|
|
34
|
+
defaultColorTheme,
|
|
35
|
+
animationType,
|
|
36
|
+
duration,
|
|
37
|
+
storageKey,
|
|
38
|
+
colorStorageKey,
|
|
39
|
+
globalClassName: attribute === 'class' ? globalClassName : undefined,
|
|
40
|
+
colorThemePrefix,
|
|
19
41
|
});
|
|
20
|
-
|
|
21
|
-
if (!
|
|
22
|
-
return;
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
mediaQuery.addEventListener('change', updateSystemTheme);
|
|
29
|
-
return () => mediaQuery.removeEventListener('change', updateSystemTheme);
|
|
30
|
-
}, [enableSystem]);
|
|
31
|
-
const resolvedTheme = theme === 'system' ? systemTheme : theme;
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
if (!mounted)
|
|
34
|
-
return;
|
|
35
|
-
const root = document.documentElement;
|
|
36
|
-
if (disableTransitionOnChange) {
|
|
37
|
-
const css = document.createElement('style');
|
|
38
|
-
css.textContent = `*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}`;
|
|
39
|
-
document.head.appendChild(css);
|
|
40
|
-
window.getComputedStyle(document.body);
|
|
42
|
+
const applyTransitionDisable = useCallback(() => {
|
|
43
|
+
if (!disableTransitionOnChange)
|
|
44
|
+
return () => { };
|
|
45
|
+
const css = document.createElement('style');
|
|
46
|
+
css.textContent = `*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}`;
|
|
47
|
+
document.head.appendChild(css);
|
|
48
|
+
window.getComputedStyle(document.body);
|
|
49
|
+
return () => {
|
|
41
50
|
setTimeout(() => {
|
|
42
51
|
document.head.removeChild(css);
|
|
43
52
|
}, 1);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
};
|
|
54
|
+
}, [disableTransitionOnChange]);
|
|
55
|
+
const wrappedSetTheme = useCallback((theme) => {
|
|
56
|
+
const cleanup = applyTransitionDisable();
|
|
57
|
+
themeState.setTheme(theme);
|
|
58
|
+
cleanup();
|
|
59
|
+
}, [themeState.setTheme, applyTransitionDisable]);
|
|
60
|
+
const wrappedSwitchTheme = useCallback(async (theme, animationOff = false) => {
|
|
61
|
+
const cleanup = applyTransitionDisable();
|
|
62
|
+
await themeState.switchTheme(theme, animationOff);
|
|
63
|
+
cleanup();
|
|
64
|
+
}, [themeState.switchTheme, applyTransitionDisable]);
|
|
65
|
+
const wrappedToggleTheme = useCallback(async (animationOff = false) => {
|
|
66
|
+
const cleanup = applyTransitionDisable();
|
|
67
|
+
await themeState.toggleTheme(animationOff);
|
|
68
|
+
cleanup();
|
|
69
|
+
}, [themeState.toggleTheme, applyTransitionDisable]);
|
|
70
|
+
const wrappedToggleLightTheme = useCallback(async (animationOff = false) => {
|
|
71
|
+
const cleanup = applyTransitionDisable();
|
|
72
|
+
await themeState.toggleLightTheme(animationOff);
|
|
73
|
+
cleanup();
|
|
74
|
+
}, [themeState.toggleLightTheme, applyTransitionDisable]);
|
|
75
|
+
const wrappedToggleDarkTheme = useCallback(async (animationOff = false) => {
|
|
76
|
+
const cleanup = applyTransitionDisable();
|
|
77
|
+
await themeState.toggleDarkTheme(animationOff);
|
|
78
|
+
cleanup();
|
|
79
|
+
}, [themeState.toggleDarkTheme, applyTransitionDisable]);
|
|
80
|
+
const switchThemeFromElement = async (theme, element) => {
|
|
81
|
+
const cleanup = applyTransitionDisable();
|
|
82
|
+
if (themeState.ref.current) {
|
|
83
|
+
const originalRef = themeState.ref.current;
|
|
84
|
+
Object.defineProperty(themeState.ref, 'current', {
|
|
85
|
+
value: element,
|
|
86
|
+
writable: true,
|
|
87
|
+
configurable: true,
|
|
88
|
+
});
|
|
89
|
+
await themeState.switchTheme(theme);
|
|
90
|
+
Object.defineProperty(themeState.ref, 'current', {
|
|
91
|
+
value: originalRef,
|
|
92
|
+
writable: true,
|
|
93
|
+
configurable: true,
|
|
94
|
+
});
|
|
48
95
|
}
|
|
49
96
|
else {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
97
|
+
Object.defineProperty(themeState.ref, 'current', {
|
|
98
|
+
value: element,
|
|
99
|
+
writable: true,
|
|
100
|
+
configurable: true,
|
|
101
|
+
});
|
|
102
|
+
await themeState.switchTheme(theme);
|
|
54
103
|
}
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
const value = {
|
|
67
|
-
theme,
|
|
68
|
-
resolvedTheme,
|
|
69
|
-
setTheme,
|
|
104
|
+
cleanup();
|
|
105
|
+
};
|
|
106
|
+
const systemTheme = typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
107
|
+
? 'dark'
|
|
108
|
+
: 'light';
|
|
109
|
+
const contextValue = {
|
|
110
|
+
ref: themeState.ref,
|
|
111
|
+
theme: themeState.theme,
|
|
112
|
+
colorTheme: themeState.colorTheme,
|
|
113
|
+
resolvedTheme: themeState.resolvedTheme,
|
|
70
114
|
systemTheme,
|
|
115
|
+
setTheme: wrappedSetTheme,
|
|
116
|
+
setColorTheme: themeState.setColorTheme,
|
|
117
|
+
switchTheme: wrappedSwitchTheme,
|
|
118
|
+
switchThemeFromElement,
|
|
119
|
+
switchColorTheme: themeState.switchColorTheme,
|
|
120
|
+
toggleTheme: wrappedToggleTheme,
|
|
121
|
+
toggleLightTheme: wrappedToggleLightTheme,
|
|
122
|
+
toggleDarkTheme: wrappedToggleDarkTheme,
|
|
123
|
+
toggleColorTheme: themeState.toggleColorTheme,
|
|
124
|
+
createColorThemeToggle: themeState.createColorThemeToggle,
|
|
125
|
+
isColorThemeActive: themeState.isColorThemeActive,
|
|
71
126
|
};
|
|
72
|
-
return _jsx(ViteThemeContext.Provider, { value:
|
|
127
|
+
return _jsx(ViteThemeContext.Provider, { value: contextValue, children: children });
|
|
73
128
|
};
|
|
74
129
|
export const useViteTheme = () => {
|
|
75
130
|
const context = useContext(ViteThemeContext);
|
|
@@ -10,34 +10,23 @@ export const useThemeAnimation = (props = {}) => {
|
|
|
10
10
|
const isHighResolution = isBrowser && (window.innerWidth >= 3000 || window.innerHeight >= 2000);
|
|
11
11
|
const duration = isHighResolution ? Math.max(propsDuration * 0.8, 500) : propsDuration;
|
|
12
12
|
const [mounted, setMounted] = useState(false);
|
|
13
|
+
// Inject base styles once on mount
|
|
13
14
|
useEffect(() => {
|
|
14
15
|
injectBaseStyles();
|
|
16
|
+
setMounted(true);
|
|
15
17
|
}, []);
|
|
16
18
|
const [internalTheme, setInternalTheme] = useState(() => {
|
|
17
19
|
if (!isBrowser)
|
|
18
20
|
return defaultTheme;
|
|
19
|
-
|
|
21
|
+
const saved = localStorage.getItem(storageKey);
|
|
22
|
+
return saved && themes.indexOf(saved) !== -1 ? saved : defaultTheme;
|
|
20
23
|
});
|
|
21
24
|
const [internalColorTheme, setInternalColorTheme] = useState(() => {
|
|
22
25
|
if (!isBrowser)
|
|
23
26
|
return defaultColorTheme;
|
|
24
|
-
|
|
27
|
+
const saved = localStorage.getItem(colorStorageKey);
|
|
28
|
+
return saved && colorThemes.indexOf(saved) !== -1 ? saved : defaultColorTheme;
|
|
25
29
|
});
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
setMounted(true);
|
|
28
|
-
if (isBrowser && !externalTheme) {
|
|
29
|
-
const saved = localStorage.getItem(storageKey);
|
|
30
|
-
if (saved && themes.indexOf(saved) !== -1) {
|
|
31
|
-
setInternalTheme(saved);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
if (isBrowser && !externalColorTheme) {
|
|
35
|
-
const saved = localStorage.getItem(colorStorageKey);
|
|
36
|
-
if (saved && colorThemes.indexOf(saved) !== -1) {
|
|
37
|
-
setInternalColorTheme(saved);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}, []);
|
|
41
30
|
const currentTheme = externalTheme ?? internalTheme;
|
|
42
31
|
const currentColorTheme = externalColorTheme ?? internalColorTheme;
|
|
43
32
|
const [, setSystemTheme] = useState(() => getSystemTheme());
|
|
@@ -94,8 +83,8 @@ export const useThemeAnimation = (props = {}) => {
|
|
|
94
83
|
onColorThemeChange(newColorTheme);
|
|
95
84
|
}
|
|
96
85
|
}, [onColorThemeChange, externalColorTheme, colorStorageKey]);
|
|
97
|
-
const switchTheme = useCallback(async (newTheme) => {
|
|
98
|
-
if (!ref.current || !supportsViewTransitions() || prefersReducedMotion()) {
|
|
86
|
+
const switchTheme = useCallback(async (newTheme, animationOff = false) => {
|
|
87
|
+
if (!ref.current || !supportsViewTransitions() || prefersReducedMotion() || animationOff) {
|
|
99
88
|
setTheme(newTheme);
|
|
100
89
|
return;
|
|
101
90
|
}
|
|
@@ -183,26 +172,45 @@ export const useThemeAnimation = (props = {}) => {
|
|
|
183
172
|
slideToX,
|
|
184
173
|
slideToY,
|
|
185
174
|
]);
|
|
186
|
-
const
|
|
175
|
+
const switchColorTheme = useCallback((newColorTheme) => {
|
|
176
|
+
if (colorThemes.indexOf(newColorTheme) === -1) {
|
|
177
|
+
console.warn(`Color theme "${newColorTheme}" not found in available themes`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
setColorTheme(newColorTheme);
|
|
181
|
+
}, [colorThemes, setColorTheme]);
|
|
182
|
+
const toggleTheme = useCallback(async (animationOff = false) => {
|
|
187
183
|
const newTheme = resolvedTheme === 'dark' ? 'light' : 'dark';
|
|
188
|
-
await switchTheme(newTheme);
|
|
184
|
+
await switchTheme(newTheme, animationOff);
|
|
189
185
|
}, [resolvedTheme, switchTheme]);
|
|
190
|
-
|
|
191
|
-
if (
|
|
186
|
+
const toggleLightTheme = useCallback(async (animationOff = false) => {
|
|
187
|
+
if (resolvedTheme === 'light')
|
|
192
188
|
return;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}, [
|
|
189
|
+
await switchTheme('light', animationOff);
|
|
190
|
+
}, [resolvedTheme, switchTheme]);
|
|
191
|
+
const toggleDarkTheme = useCallback(async (animationOff = false) => {
|
|
192
|
+
if (resolvedTheme === 'dark')
|
|
193
|
+
return;
|
|
194
|
+
await switchTheme('dark', animationOff);
|
|
195
|
+
}, [resolvedTheme, switchTheme]);
|
|
196
|
+
const toggleColorTheme = useCallback(() => {
|
|
197
|
+
const currentIndex = colorThemes.indexOf(currentColorTheme);
|
|
198
|
+
const nextIndex = (currentIndex + 1) % colorThemes.length;
|
|
199
|
+
const nextColorTheme = colorThemes[nextIndex];
|
|
200
|
+
setColorTheme(nextColorTheme);
|
|
201
|
+
}, [currentColorTheme, colorThemes, setColorTheme]);
|
|
202
|
+
const createColorThemeToggle = useCallback((targetColorTheme) => {
|
|
203
|
+
return () => {
|
|
204
|
+
if (colorThemes.indexOf(targetColorTheme) === -1) {
|
|
205
|
+
console.warn(`Color theme "${targetColorTheme}" not found in available themes`);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
setColorTheme(targetColorTheme);
|
|
209
|
+
};
|
|
210
|
+
}, [colorThemes, setColorTheme]);
|
|
211
|
+
const isColorThemeActive = useCallback((targetColorTheme) => {
|
|
212
|
+
return currentColorTheme === targetColorTheme;
|
|
213
|
+
}, [currentColorTheme]);
|
|
206
214
|
return {
|
|
207
215
|
ref,
|
|
208
216
|
theme: currentTheme,
|
|
@@ -210,7 +218,13 @@ export const useThemeAnimation = (props = {}) => {
|
|
|
210
218
|
resolvedTheme,
|
|
211
219
|
setTheme,
|
|
212
220
|
setColorTheme,
|
|
213
|
-
toggleTheme,
|
|
214
221
|
switchTheme,
|
|
222
|
+
switchColorTheme,
|
|
223
|
+
toggleTheme,
|
|
224
|
+
toggleLightTheme,
|
|
225
|
+
toggleDarkTheme,
|
|
226
|
+
toggleColorTheme,
|
|
227
|
+
createColorThemeToggle,
|
|
228
|
+
isColorThemeActive,
|
|
215
229
|
};
|
|
216
230
|
};
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -2,8 +2,9 @@ export { useThemeAnimation } from './hooks/use-theme-animation';
|
|
|
2
2
|
export { ThemeSwitcher } from './components/ThemeSwitcher';
|
|
3
3
|
export { ThemeSelector } from './components/ThemeSelector';
|
|
4
4
|
export { SpacemanThemeProvider, useSpacemanTheme } from './components/SpacemanThemeProvider';
|
|
5
|
+
export { NextThemeProvider, useNextTheme } from './components/NextThemeProvider';
|
|
6
|
+
export { TanStackThemeProvider, useTanStackTheme } from './components/TanStackThemeProvider';
|
|
5
7
|
export { ViteThemeProvider, useViteTheme } from './components/ViteThemeProvider';
|
|
6
8
|
export type { UseThemeAnimationProps, UseThemeAnimationReturn, ThemeSwitcherProps, ThemeSelectorProps, Theme, ColorTheme, SlideDirection, } from './types';
|
|
7
|
-
export type { ViteThemeContextType } from './components/ViteThemeProvider';
|
|
8
9
|
export { ThemeAnimationType } from './types';
|
|
9
10
|
export { injectBaseStyles, getSystemTheme, resolveTheme, supportsViewTransitions, prefersReducedMotion, } from './utils/animations';
|
package/dist/esm/index.js
CHANGED
|
@@ -2,6 +2,9 @@ export { useThemeAnimation } from './hooks/use-theme-animation';
|
|
|
2
2
|
export { ThemeSwitcher } from './components/ThemeSwitcher';
|
|
3
3
|
export { ThemeSelector } from './components/ThemeSelector';
|
|
4
4
|
export { SpacemanThemeProvider, useSpacemanTheme } from './components/SpacemanThemeProvider';
|
|
5
|
+
// Framework-specific optimized providers
|
|
6
|
+
export { NextThemeProvider, useNextTheme } from './components/NextThemeProvider';
|
|
7
|
+
export { TanStackThemeProvider, useTanStackTheme } from './components/TanStackThemeProvider';
|
|
5
8
|
export { ViteThemeProvider, useViteTheme } from './components/ViteThemeProvider';
|
|
6
9
|
export { ThemeAnimationType } from './types';
|
|
7
10
|
export { injectBaseStyles, getSystemTheme, resolveTheme, supportsViewTransitions, prefersReducedMotion, } from './utils/animations';
|
|
@@ -38,8 +38,14 @@ export interface UseThemeAnimationReturn {
|
|
|
38
38
|
resolvedTheme: 'light' | 'dark';
|
|
39
39
|
setTheme: (theme: Theme) => void;
|
|
40
40
|
setColorTheme: (colorTheme: ColorTheme) => void;
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
switchTheme: (theme: Theme, animationOff?: boolean) => Promise<void>;
|
|
42
|
+
switchColorTheme: (colorTheme: string) => void;
|
|
43
|
+
toggleTheme: (animationOff?: boolean) => Promise<void>;
|
|
44
|
+
toggleLightTheme: (animationOff?: boolean) => Promise<void>;
|
|
45
|
+
toggleDarkTheme: (animationOff?: boolean) => Promise<void>;
|
|
46
|
+
toggleColorTheme: () => void;
|
|
47
|
+
createColorThemeToggle: (targetColorTheme: string) => () => void;
|
|
48
|
+
isColorThemeActive: (targetColorTheme: string) => boolean;
|
|
43
49
|
}
|
|
44
50
|
export interface ThemeSwitcherProps {
|
|
45
51
|
themes?: Theme[];
|