@space-man/react-theme-animation 2.1.0 → 3.0.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.
Files changed (60) hide show
  1. package/README.md +115 -164
  2. package/dist/animations-CaaRrn3-.d.ts +208 -0
  3. package/dist/chunk-FNL7ZHF4.js +44 -0
  4. package/dist/chunk-I3T2QXRT.js +18 -0
  5. package/dist/chunk-QYUMCBTQ.js +1 -0
  6. package/dist/chunk-X6UEG7WG.js +12 -0
  7. package/dist/core/index.d.ts +9 -0
  8. package/dist/core/index.js +1 -0
  9. package/dist/index.d.ts +5 -0
  10. package/dist/index.js +1 -0
  11. package/dist/logo-DtwtRv4y.d.ts +4 -0
  12. package/dist/react/index.d.ts +228 -0
  13. package/dist/react/index.js +2 -0
  14. package/dist/tanstack/index.d.ts +20 -0
  15. package/dist/tanstack/index.js +1 -0
  16. package/package.json +82 -63
  17. package/dist/cjs/components/NextThemeProvider.d.ts +0 -81
  18. package/dist/cjs/components/NextThemeProvider.js +0 -223
  19. package/dist/cjs/components/SpacemanThemeProvider.d.ts +0 -57
  20. package/dist/cjs/components/SpacemanThemeProvider.js +0 -113
  21. package/dist/cjs/components/TanStackThemeProvider.d.ts +0 -68
  22. package/dist/cjs/components/TanStackThemeProvider.js +0 -183
  23. package/dist/cjs/components/ThemeSelector.d.ts +0 -3
  24. package/dist/cjs/components/ThemeSelector.js +0 -39
  25. package/dist/cjs/components/ThemeSwitcher.d.ts +0 -3
  26. package/dist/cjs/components/ThemeSwitcher.js +0 -81
  27. package/dist/cjs/components/ViteThemeProvider.d.ts +0 -61
  28. package/dist/cjs/components/ViteThemeProvider.js +0 -140
  29. package/dist/cjs/components/ui/select.d.ts +0 -15
  30. package/dist/cjs/components/ui/select.js +0 -83
  31. package/dist/cjs/hooks/use-theme-animation.d.ts +0 -2
  32. package/dist/cjs/hooks/use-theme-animation.js +0 -234
  33. package/dist/cjs/index.d.ts +0 -10
  34. package/dist/cjs/index.js +0 -30
  35. package/dist/cjs/types/index.d.ts +0 -78
  36. package/dist/cjs/types/index.js +0 -9
  37. package/dist/cjs/utils/animations.d.ts +0 -21
  38. package/dist/cjs/utils/animations.js +0 -189
  39. package/dist/esm/components/NextThemeProvider.d.ts +0 -81
  40. package/dist/esm/components/NextThemeProvider.js +0 -185
  41. package/dist/esm/components/SpacemanThemeProvider.d.ts +0 -57
  42. package/dist/esm/components/SpacemanThemeProvider.js +0 -108
  43. package/dist/esm/components/TanStackThemeProvider.d.ts +0 -68
  44. package/dist/esm/components/TanStackThemeProvider.js +0 -178
  45. package/dist/esm/components/ThemeSelector.d.ts +0 -3
  46. package/dist/esm/components/ThemeSelector.js +0 -35
  47. package/dist/esm/components/ThemeSwitcher.d.ts +0 -3
  48. package/dist/esm/components/ThemeSwitcher.js +0 -77
  49. package/dist/esm/components/ViteThemeProvider.d.ts +0 -61
  50. package/dist/esm/components/ViteThemeProvider.js +0 -135
  51. package/dist/esm/components/ui/select.d.ts +0 -15
  52. package/dist/esm/components/ui/select.js +0 -39
  53. package/dist/esm/hooks/use-theme-animation.d.ts +0 -2
  54. package/dist/esm/hooks/use-theme-animation.js +0 -230
  55. package/dist/esm/index.d.ts +0 -10
  56. package/dist/esm/index.js +0 -10
  57. package/dist/esm/types/index.d.ts +0 -78
  58. package/dist/esm/types/index.js +0 -6
  59. package/dist/esm/utils/animations.d.ts +0 -21
  60. 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,3 +0,0 @@
1
- import React from 'react';
2
- import { ThemeSelectorProps } from '../types';
3
- export declare const ThemeSelector: React.FC<ThemeSelectorProps>;
@@ -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
- };
@@ -1,3 +0,0 @@
1
- import React from 'react';
2
- import { ThemeSwitcherProps } from '../types';
3
- export declare const ThemeSwitcher: React.FC<ThemeSwitcherProps>;