@space-man/react-theme-animation 2.3.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.
package/dist/index.js CHANGED
@@ -1,1706 +1 @@
1
- import React4, { createContext, useState, useEffect, useRef, useCallback, useContext, useMemo, useSyncExternalStore } from 'react';
2
- import { flushSync } from 'react-dom';
3
- import { motion } from 'motion/react';
4
- import { clsx } from 'clsx';
5
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
- import * as SelectPrimitive from '@radix-ui/react-select';
7
-
8
- // src/react/hooks/use-theme-animation.ts
9
-
10
- // src/core/types/index.ts
11
- var ThemeAnimationType = /* @__PURE__ */ ((ThemeAnimationType2) => {
12
- ThemeAnimationType2["CIRCLE"] = "circle";
13
- ThemeAnimationType2["BLUR_CIRCLE"] = "blur-circle";
14
- ThemeAnimationType2["SLIDE"] = "slide";
15
- return ThemeAnimationType2;
16
- })(ThemeAnimationType || {});
17
-
18
- // src/core/utils/animations.ts
19
- var isBrowser = typeof window !== "undefined";
20
- var injectBaseStyles = () => {
21
- if (isBrowser) {
22
- const styleId = "spaceman-theme-base-style";
23
- if (!document.getElementById(styleId)) {
24
- const style = document.createElement("style");
25
- style.id = styleId;
26
- const isHighResolution = window.innerWidth >= 3e3 || window.innerHeight >= 2e3;
27
- style.textContent = `
28
- ::view-transition-old(root),
29
- ::view-transition-new(root) {
30
- animation: none;
31
- mix-blend-mode: normal;
32
- ${isHighResolution ? "transform: translateZ(0);" : ""}
33
- }
34
-
35
- ${isHighResolution ? `
36
- ::view-transition-group(root),
37
- ::view-transition-image-pair(root),
38
- ::view-transition-old(root),
39
- ::view-transition-new(root) {
40
- backface-visibility: hidden;
41
- perspective: 1000px;
42
- transform: translate3d(0, 0, 0);
43
- }
44
- ` : ""}
45
- `;
46
- document.head.appendChild(style);
47
- }
48
- }
49
- };
50
- var createBlurCircleMask = (blur) => {
51
- const isHighResolution = isBrowser && (window.innerWidth >= 3e3 || window.innerHeight >= 2e3);
52
- const blurFilter = `<filter id="blur"><feGaussianBlur stdDeviation="${blur}" /></filter>`;
53
- const circleRadius = isHighResolution ? 20 : 25;
54
- return `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-50 -50 100 100"><defs>${blurFilter}</defs><circle cx="0" cy="0" r="${circleRadius}" fill="white" filter="url(%23blur)"/></svg>')`;
55
- };
56
- var getSystemTheme = () => {
57
- if (!isBrowser) return "light";
58
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
59
- };
60
- var resolveTheme = (theme) => {
61
- if (theme === "system") {
62
- return getSystemTheme();
63
- }
64
- return theme === "dark" ? "dark" : "light";
65
- };
66
- var resolveThemeForServer = (theme) => {
67
- if (theme === "system") {
68
- return "system";
69
- }
70
- return theme === "dark" ? "dark" : "light";
71
- };
72
- var supportsViewTransitions = () => {
73
- return isBrowser && "startViewTransition" in document;
74
- };
75
- var prefersReducedMotion = () => {
76
- return isBrowser && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
77
- };
78
- var createCircleAnimation = (config) => {
79
- const { x, y, duration, easing } = config;
80
- const topLeft = Math.hypot(x, y);
81
- const topRight = Math.hypot(window.innerWidth - x, y);
82
- const bottomLeft = Math.hypot(x, window.innerHeight - y);
83
- const bottomRight = Math.hypot(window.innerWidth - x, window.innerHeight - y);
84
- const maxRadius = Math.max(topLeft, topRight, bottomLeft, bottomRight);
85
- document.documentElement.animate(
86
- {
87
- clipPath: [
88
- `circle(0px at ${x}px ${y}px)`,
89
- `circle(${maxRadius}px at ${x}px ${y}px)`
90
- ]
91
- },
92
- {
93
- duration,
94
- easing,
95
- pseudoElement: "::view-transition-new(root)"
96
- }
97
- );
98
- };
99
- var createSlideAnimation = (config) => {
100
- const { a = -100, b = 0, x = 0, y = 0, duration, styleId } = config;
101
- const styleElement = document.createElement("style");
102
- styleElement.id = styleId;
103
- styleElement.textContent = `
104
- ::view-transition-old(root) {
105
- animation-delay: ${duration}ms;
106
- }
107
-
108
- ::view-transition-new(root) {
109
- animation: move-in ${duration}ms;
110
- animation-timing-function: cubic-bezier(0.66, 0, 0.34, 1);
111
- }
112
-
113
- @keyframes move-in {
114
- from {
115
- translate: ${a}% ${b}%;
116
- }
117
- to {
118
- translate: ${x}% ${y}%;
119
- }
120
- }
121
- `;
122
- document.head.appendChild(styleElement);
123
- setTimeout(() => {
124
- const styleElement2 = document.getElementById(styleId);
125
- if (styleElement2) {
126
- styleElement2.remove();
127
- }
128
- }, duration);
129
- };
130
- var createBlurCircleAnimation = (config) => {
131
- const { x, y, duration, easing, blurAmount, styleId } = config;
132
- const existingStyle = document.getElementById(styleId);
133
- if (existingStyle) {
134
- existingStyle.remove();
135
- }
136
- const viewportSize = Math.max(window.innerWidth, window.innerHeight) + 200;
137
- const isHighResolution = window.innerWidth >= 3e3 || window.innerHeight >= 2e3;
138
- const scaleFactor = isHighResolution ? 2.5 : 4;
139
- const optimalMaskSize = isHighResolution ? Math.min(viewportSize * scaleFactor, 5e3) : viewportSize * scaleFactor;
140
- const topLeft = Math.hypot(x, y);
141
- const topRight = Math.hypot(window.innerWidth - x, y);
142
- const bottomLeft = Math.hypot(x, window.innerHeight - y);
143
- const bottomRight = Math.hypot(window.innerWidth - x, window.innerHeight - y);
144
- const maxRadius = Math.max(topLeft, topRight, bottomLeft, bottomRight);
145
- const styleElement = document.createElement("style");
146
- styleElement.id = styleId;
147
- const blurFactor = isHighResolution ? 1.5 : 1.2;
148
- const finalMaskSize = Math.max(optimalMaskSize, maxRadius * 2.5);
149
- styleElement.textContent = `
150
- ::view-transition-group(root) {
151
- animation-duration: ${duration}ms;
152
- animation-timing-function: ${isHighResolution ? "cubic-bezier(0.2, 0, 0.2, 1)" : "linear(0 0%, 0.2342 12.49%, 0.4374 24.99%,0.6093 37.49%, 0.6835 43.74%,0.7499 49.99%, 0.8086 56.25%,0.8593 62.5%, 0.9023 68.75%, 0.9375 75%,0.9648 81.25%, 0.9844 87.5%,0.9961 93.75%, 1 100%)"};
153
- will-change: transform;
154
- }
155
-
156
- ::view-transition-new(root) {
157
- mask: ${createBlurCircleMask(blurAmount * blurFactor)} 0 0 / 100% 100% no-repeat;
158
- mask-position: ${x}px ${y}px;
159
- animation: spaceman-mask-scale ${duration}ms ${easing};
160
- transform-origin: ${x}px ${y}px;
161
- will-change: mask-size, mask-position;
162
- }
163
-
164
- ::view-transition-old(root),
165
- .dark::view-transition-old(root) {
166
- animation: spaceman-mask-scale ${duration}ms ${easing};
167
- transform-origin: ${x}px ${y}px;
168
- z-index: -1;
169
- will-change: mask-size, mask-position;
170
- }
171
-
172
- @keyframes spaceman-mask-scale {
173
- 0% {
174
- mask-size: 0px;
175
- mask-position: ${x}px ${y}px;
176
- }
177
- 100% {
178
- mask-size: ${finalMaskSize}px;
179
- mask-position: ${x - finalMaskSize / 2}px ${y - finalMaskSize / 2}px;
180
- }
181
- }
182
- `;
183
- document.head.appendChild(styleElement);
184
- setTimeout(() => {
185
- const styleElement2 = document.getElementById(styleId);
186
- if (styleElement2) {
187
- styleElement2.remove();
188
- }
189
- }, duration);
190
- };
191
-
192
- // src/react/hooks/use-theme-animation.ts
193
- var isBrowser2 = typeof window !== "undefined";
194
- var useThemeAnimation = (props = {}) => {
195
- const {
196
- duration: propsDuration = 750,
197
- easing = "ease-in-out",
198
- animationType = "circle" /* CIRCLE */,
199
- blurAmount = 2,
200
- styleId = "spaceman-theme-style",
201
- themes = ["light", "dark", "system"],
202
- colorThemes = ["default"],
203
- defaultTheme = "system",
204
- defaultColorTheme = "default",
205
- globalClassName = "dark",
206
- colorThemePrefix = "theme-",
207
- attribute = "class",
208
- value,
209
- enableColorScheme = true,
210
- storageKey = "theme",
211
- colorStorageKey = "color-theme",
212
- theme: externalTheme,
213
- colorTheme: externalColorTheme,
214
- onThemeChange,
215
- onColorThemeChange,
216
- initialTheme,
217
- initialColorTheme,
218
- systemThemeMode = "js",
219
- // Slide animation options
220
- slideDirection = "left",
221
- slideFromX,
222
- slideFromY,
223
- slideToX = 0,
224
- slideToY = 0
225
- } = props;
226
- const isHighResolution = isBrowser2 && (window.innerWidth >= 3e3 || window.innerHeight >= 2e3);
227
- const duration = isHighResolution ? Math.max(propsDuration * 0.8, 500) : propsDuration;
228
- const [mounted, setMounted] = useState(false);
229
- useEffect(() => {
230
- injectBaseStyles();
231
- setMounted(true);
232
- }, []);
233
- const [internalTheme, setInternalTheme] = useState(() => {
234
- if (initialTheme !== void 0) return initialTheme;
235
- if (!isBrowser2) return defaultTheme;
236
- const saved = localStorage.getItem(storageKey);
237
- return saved && themes.indexOf(saved) !== -1 ? saved : defaultTheme;
238
- });
239
- const [internalColorTheme, setInternalColorTheme] = useState(() => {
240
- if (initialColorTheme !== void 0) return initialColorTheme;
241
- if (!isBrowser2) return defaultColorTheme;
242
- const saved = localStorage.getItem(colorStorageKey);
243
- return saved && colorThemes.indexOf(saved) !== -1 ? saved : defaultColorTheme;
244
- });
245
- const currentTheme = externalTheme ?? internalTheme;
246
- const currentColorTheme = externalColorTheme ?? internalColorTheme;
247
- const [, setSystemTheme] = useState(() => getSystemTheme());
248
- const systemTheme = getSystemTheme();
249
- const resolvedTheme = resolveTheme(currentTheme);
250
- useEffect(() => {
251
- if (!isBrowser2) return;
252
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
253
- const handleChange = () => setSystemTheme(getSystemTheme());
254
- mediaQuery.addEventListener("change", handleChange);
255
- return () => mediaQuery.removeEventListener("change", handleChange);
256
- }, []);
257
- useEffect(() => {
258
- if (!isBrowser2 || !mounted) return;
259
- const element = document.documentElement;
260
- const lightValue = value?.light;
261
- const darkValue = value?.dark ?? globalClassName;
262
- const systemValue = value?.system ?? "system";
263
- if (attribute === "data-theme") {
264
- if (systemThemeMode === "css" && currentTheme === "system") {
265
- element.setAttribute("data-theme", systemValue);
266
- } else {
267
- element.setAttribute(
268
- "data-theme",
269
- resolvedTheme === "dark" ? darkValue : lightValue ?? "light"
270
- );
271
- }
272
- } else {
273
- if (lightValue) {
274
- element.classList.remove(lightValue);
275
- }
276
- element.classList.remove(darkValue, systemValue, "auto");
277
- if (systemThemeMode === "css" && currentTheme === "system") {
278
- element.classList.add(systemValue);
279
- } else if (resolvedTheme === "dark") {
280
- element.classList.add(darkValue);
281
- } else if (lightValue) {
282
- element.classList.add(lightValue);
283
- } else {
284
- element.classList.remove(darkValue);
285
- }
286
- }
287
- if (enableColorScheme) {
288
- element.style.colorScheme = systemThemeMode === "css" && currentTheme === "system" ? "" : resolvedTheme;
289
- } else {
290
- element.style.removeProperty("color-scheme");
291
- }
292
- colorThemes.forEach((theme) => {
293
- element.classList.remove(`${colorThemePrefix}${theme}`);
294
- });
295
- element.classList.add(`${colorThemePrefix}${currentColorTheme}`);
296
- }, [
297
- resolvedTheme,
298
- currentTheme,
299
- currentColorTheme,
300
- globalClassName,
301
- colorThemePrefix,
302
- colorThemes,
303
- attribute,
304
- value,
305
- enableColorScheme,
306
- mounted,
307
- systemThemeMode
308
- ]);
309
- const ref = useRef(null);
310
- const setTheme = useCallback(
311
- (newTheme) => {
312
- if (isBrowser2) {
313
- localStorage.setItem(storageKey, newTheme);
314
- }
315
- if (externalTheme === void 0) {
316
- setInternalTheme(newTheme);
317
- }
318
- if (onThemeChange) {
319
- onThemeChange(newTheme);
320
- }
321
- },
322
- [onThemeChange, externalTheme, storageKey]
323
- );
324
- const setColorTheme = useCallback(
325
- (newColorTheme) => {
326
- if (isBrowser2) {
327
- localStorage.setItem(colorStorageKey, newColorTheme);
328
- }
329
- if (externalColorTheme === void 0) {
330
- setInternalColorTheme(newColorTheme);
331
- }
332
- if (onColorThemeChange) {
333
- onColorThemeChange(newColorTheme);
334
- }
335
- },
336
- [onColorThemeChange, externalColorTheme, colorStorageKey]
337
- );
338
- const switchTheme = useCallback(
339
- async (newTheme, animationOff = false) => {
340
- if (!ref.current || !supportsViewTransitions() || prefersReducedMotion() || animationOff) {
341
- setTheme(newTheme);
342
- return;
343
- }
344
- const getSlideFromCoords = (direction) => {
345
- switch (direction) {
346
- case "left":
347
- return { a: -100, b: 0 };
348
- case "right":
349
- return { a: 100, b: 0 };
350
- case "top":
351
- return { a: 0, b: -100 };
352
- case "bottom":
353
- return { a: 0, b: 100 };
354
- case "top-left":
355
- return { a: -100, b: -100 };
356
- case "top-right":
357
- return { a: 100, b: -100 };
358
- case "bottom-left":
359
- return { a: -100, b: 100 };
360
- case "bottom-right":
361
- return { a: 100, b: 100 };
362
- default:
363
- return { a: -100, b: 0 };
364
- }
365
- };
366
- let animationConfig;
367
- if (animationType === "slide" /* SLIDE */) {
368
- const fromCoords = slideFromX !== void 0 && slideFromY !== void 0 ? { a: slideFromX, b: slideFromY } : getSlideFromCoords(slideDirection);
369
- animationConfig = {
370
- a: fromCoords.a,
371
- b: fromCoords.b,
372
- x: slideToX,
373
- y: slideToY,
374
- duration,
375
- easing,
376
- animationType,
377
- blurAmount,
378
- styleId
379
- };
380
- } else {
381
- const { top, left, width, height } = ref.current.getBoundingClientRect();
382
- const x = left + width / 2;
383
- const y = top + height / 2;
384
- animationConfig = {
385
- x,
386
- y,
387
- duration,
388
- easing,
389
- animationType,
390
- blurAmount,
391
- styleId
392
- };
393
- }
394
- if (animationType === "blur-circle" /* BLUR_CIRCLE */) {
395
- createBlurCircleAnimation(animationConfig);
396
- }
397
- if (animationType === "slide" /* SLIDE */) {
398
- createSlideAnimation(animationConfig);
399
- }
400
- await document.startViewTransition(() => {
401
- flushSync(() => {
402
- setTheme(newTheme);
403
- });
404
- }).ready;
405
- if (animationType === "circle" /* CIRCLE */) {
406
- createCircleAnimation(animationConfig);
407
- }
408
- },
409
- [
410
- setTheme,
411
- duration,
412
- easing,
413
- animationType,
414
- blurAmount,
415
- styleId,
416
- slideDirection,
417
- slideFromX,
418
- slideFromY,
419
- slideToX,
420
- slideToY
421
- ]
422
- );
423
- const switchColorTheme = useCallback(
424
- (newColorTheme) => {
425
- if (colorThemes.indexOf(newColorTheme) === -1) {
426
- console.warn(
427
- `Color theme "${newColorTheme}" not found in available themes`
428
- );
429
- return;
430
- }
431
- setColorTheme(newColorTheme);
432
- },
433
- [colorThemes, setColorTheme]
434
- );
435
- const toggleTheme = useCallback(
436
- async (animationOff = false) => {
437
- const newTheme = resolvedTheme === "dark" ? "light" : "dark";
438
- await switchTheme(newTheme, animationOff);
439
- },
440
- [resolvedTheme, switchTheme]
441
- );
442
- const toggleLightTheme = useCallback(
443
- async (animationOff = false) => {
444
- if (resolvedTheme === "light") return;
445
- await switchTheme("light", animationOff);
446
- },
447
- [resolvedTheme, switchTheme]
448
- );
449
- const toggleDarkTheme = useCallback(
450
- async (animationOff = false) => {
451
- if (resolvedTheme === "dark") return;
452
- await switchTheme("dark", animationOff);
453
- },
454
- [resolvedTheme, switchTheme]
455
- );
456
- const toggleColorTheme = useCallback(() => {
457
- const currentIndex = colorThemes.indexOf(currentColorTheme);
458
- const nextIndex = (currentIndex + 1) % colorThemes.length;
459
- const nextColorTheme = colorThemes[nextIndex];
460
- setColorTheme(nextColorTheme);
461
- }, [currentColorTheme, colorThemes, setColorTheme]);
462
- const createColorThemeToggle = useCallback(
463
- (targetColorTheme) => {
464
- return () => {
465
- if (colorThemes.indexOf(targetColorTheme) === -1) {
466
- console.warn(
467
- `Color theme "${targetColorTheme}" not found in available themes`
468
- );
469
- return;
470
- }
471
- setColorTheme(targetColorTheme);
472
- };
473
- },
474
- [colorThemes, setColorTheme]
475
- );
476
- const isColorThemeActive = useCallback(
477
- (targetColorTheme) => {
478
- return currentColorTheme === targetColorTheme;
479
- },
480
- [currentColorTheme]
481
- );
482
- return {
483
- ref,
484
- theme: currentTheme,
485
- colorTheme: currentColorTheme,
486
- resolvedTheme,
487
- systemTheme,
488
- setTheme,
489
- setColorTheme,
490
- switchTheme,
491
- switchColorTheme,
492
- toggleTheme,
493
- toggleLightTheme,
494
- toggleDarkTheme,
495
- toggleColorTheme,
496
- createColorThemeToggle,
497
- isColorThemeActive
498
- };
499
- };
500
- var SharedThemeContext = createContext(
501
- null
502
- );
503
- var useSharedThemeContext = () => {
504
- return useContext(SharedThemeContext);
505
- };
506
- var SunIcon = () => /* @__PURE__ */ jsxs(
507
- "svg",
508
- {
509
- width: "16",
510
- height: "16",
511
- viewBox: "0 0 24 24",
512
- fill: "none",
513
- stroke: "currentColor",
514
- strokeWidth: "2",
515
- strokeLinecap: "round",
516
- strokeLinejoin: "round",
517
- children: [
518
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "5" }),
519
- /* @__PURE__ */ jsx("path", { d: "M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" })
520
- ]
521
- }
522
- );
523
- var MoonIcon = () => /* @__PURE__ */ jsx(
524
- "svg",
525
- {
526
- width: "16",
527
- height: "16",
528
- viewBox: "0 0 24 24",
529
- fill: "none",
530
- stroke: "currentColor",
531
- strokeWidth: "2",
532
- strokeLinecap: "round",
533
- strokeLinejoin: "round",
534
- children: /* @__PURE__ */ jsx("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" })
535
- }
536
- );
537
- var MonitorIcon = () => /* @__PURE__ */ jsxs(
538
- "svg",
539
- {
540
- width: "16",
541
- height: "16",
542
- viewBox: "0 0 24 24",
543
- fill: "none",
544
- stroke: "currentColor",
545
- strokeWidth: "2",
546
- strokeLinecap: "round",
547
- strokeLinejoin: "round",
548
- children: [
549
- /* @__PURE__ */ jsx("rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }),
550
- /* @__PURE__ */ jsx("line", { x1: "8", x2: "16", y1: "21", y2: "21" }),
551
- /* @__PURE__ */ jsx("line", { x1: "12", x2: "12", y1: "17", y2: "21" })
552
- ]
553
- }
554
- );
555
- var ThemeOption = ({
556
- icon,
557
- value,
558
- isActive,
559
- isHovered,
560
- onClick,
561
- onMouseEnter,
562
- onMouseLeave,
563
- buttonRef
564
- }) => {
565
- return /* @__PURE__ */ jsxs(
566
- "button",
567
- {
568
- ref: isActive ? buttonRef : void 0,
569
- className: clsx(
570
- "relative flex h-9 w-12 cursor-pointer items-center justify-center",
571
- "text-muted-foreground hover:text-foreground",
572
- isActive && "text-foreground font-medium"
573
- ),
574
- role: "radio",
575
- "aria-checked": isActive,
576
- "aria-label": `Switch to ${value} theme`,
577
- onClick: (event) => onClick(value, event),
578
- onMouseEnter,
579
- onMouseLeave,
580
- style: {
581
- borderRadius: "var(--radius)"
582
- },
583
- children: [
584
- isHovered && /* @__PURE__ */ jsx(
585
- motion.div,
586
- {
587
- layoutId: "theme-hover",
588
- className: "absolute inset-0 bg-muted",
589
- style: {
590
- borderRadius: "var(--radius)"
591
- },
592
- transition: {
593
- type: "spring",
594
- bounce: 0,
595
- stiffness: 100,
596
- damping: 10,
597
- duration: 0.3
598
- }
599
- }
600
- ),
601
- /* @__PURE__ */ jsx("div", { className: "relative z-10 flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "[&_svg]:size-4", children: icon }) }),
602
- isActive && /* @__PURE__ */ jsx(
603
- "div",
604
- {
605
- className: "absolute bottom-0 left-1/2 h-0.5 w-6 -translate-x-1/2 rounded-full",
606
- style: {
607
- backgroundColor: "hsl(var(--primary))"
608
- }
609
- }
610
- )
611
- ]
612
- }
613
- );
614
- };
615
- var THEME_OPTIONS = [
616
- {
617
- icon: /* @__PURE__ */ jsx(MonitorIcon, {}),
618
- value: "system"
619
- },
620
- {
621
- icon: /* @__PURE__ */ jsx(SunIcon, {}),
622
- value: "light"
623
- },
624
- {
625
- icon: /* @__PURE__ */ jsx(MoonIcon, {}),
626
- value: "dark"
627
- }
628
- ];
629
- var ThemeSwitcherView = ({
630
- className,
631
- onSwitchTheme,
632
- theme,
633
- themes,
634
- themeRef
635
- }) => {
636
- const [isMounted, setIsMounted] = useState(false);
637
- const [hoveredTheme, setHoveredTheme] = useState(null);
638
- useEffect(() => {
639
- setIsMounted(true);
640
- }, []);
641
- const handleThemeChange = async (newTheme, event) => {
642
- await onSwitchTheme(newTheme, event);
643
- };
644
- const filteredOptions = THEME_OPTIONS.filter(
645
- (option) => themes.includes(option.value)
646
- );
647
- return /* @__PURE__ */ jsx(
648
- "div",
649
- {
650
- className: clsx(
651
- "inline-flex h-9 items-center overflow-hidden bg-background shadow-sm border border-border",
652
- isMounted ? "opacity-100" : "opacity-0",
653
- className
654
- ),
655
- style: {
656
- borderRadius: "var(--radius)",
657
- backgroundColor: "hsl(var(--background))"
658
- },
659
- role: "radiogroup",
660
- onMouseLeave: () => setHoveredTheme(null),
661
- children: filteredOptions.map((option) => /* @__PURE__ */ jsx(
662
- ThemeOption,
663
- {
664
- icon: option.icon,
665
- value: option.value,
666
- isActive: theme === option.value,
667
- isHovered: hoveredTheme === option.value,
668
- onClick: handleThemeChange,
669
- onMouseEnter: () => setHoveredTheme(option.value),
670
- onMouseLeave: () => {
671
- },
672
- buttonRef: theme === option.value ? themeRef : void 0
673
- },
674
- option.value
675
- ))
676
- }
677
- );
678
- };
679
- var ThemeSwitcherWithContext = ({ className, contextTheme, themes = ["light", "dark", "system"] }) => {
680
- const handleSwitchTheme = async (theme, event) => {
681
- if (contextTheme.switchThemeFromElement && event) {
682
- await contextTheme.switchThemeFromElement(theme, event.currentTarget);
683
- return;
684
- }
685
- await contextTheme.switchTheme(theme);
686
- };
687
- return /* @__PURE__ */ jsx(
688
- ThemeSwitcherView,
689
- {
690
- className,
691
- onSwitchTheme: handleSwitchTheme,
692
- theme: contextTheme.theme,
693
- themes,
694
- themeRef: contextTheme.ref
695
- }
696
- );
697
- };
698
- var ThemeSwitcherStandalone = ({
699
- themes = ["light", "dark", "system"],
700
- currentTheme,
701
- onThemeChange,
702
- animationType,
703
- duration,
704
- className
705
- }) => {
706
- const standaloneHook = useThemeAnimation({
707
- animationType,
708
- duration,
709
- themes,
710
- ...currentTheme !== void 0 && { theme: currentTheme },
711
- onThemeChange
712
- });
713
- const handleSwitchTheme = async (theme) => {
714
- await standaloneHook.switchTheme(theme);
715
- };
716
- return /* @__PURE__ */ jsx(
717
- ThemeSwitcherView,
718
- {
719
- className,
720
- onSwitchTheme: handleSwitchTheme,
721
- theme: standaloneHook.theme,
722
- themes,
723
- themeRef: standaloneHook.ref
724
- }
725
- );
726
- };
727
- var ThemeSwitcher = (props) => {
728
- const contextTheme = useSharedThemeContext();
729
- if (contextTheme) {
730
- return /* @__PURE__ */ jsx(ThemeSwitcherWithContext, { ...props, contextTheme });
731
- }
732
- return /* @__PURE__ */ jsx(ThemeSwitcherStandalone, { ...props });
733
- };
734
- function Select({
735
- ...props
736
- }) {
737
- return /* @__PURE__ */ jsx(SelectPrimitive.Root, { "data-slot": "select", ...props });
738
- }
739
- function SelectValue({
740
- ...props
741
- }) {
742
- return /* @__PURE__ */ jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
743
- }
744
- var ChevronDownIcon = () => /* @__PURE__ */ jsx(
745
- "svg",
746
- {
747
- width: "16",
748
- height: "16",
749
- viewBox: "0 0 24 24",
750
- fill: "none",
751
- stroke: "currentColor",
752
- strokeWidth: "2",
753
- strokeLinecap: "round",
754
- strokeLinejoin: "round",
755
- children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
756
- }
757
- );
758
- var CheckIcon = () => /* @__PURE__ */ jsx(
759
- "svg",
760
- {
761
- width: "16",
762
- height: "16",
763
- viewBox: "0 0 24 24",
764
- fill: "none",
765
- stroke: "currentColor",
766
- strokeWidth: "2",
767
- strokeLinecap: "round",
768
- strokeLinejoin: "round",
769
- children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" })
770
- }
771
- );
772
- function SelectTrigger({
773
- className,
774
- size = "default",
775
- children,
776
- ...props
777
- }) {
778
- return /* @__PURE__ */ jsxs(
779
- SelectPrimitive.Trigger,
780
- {
781
- "data-slot": "select-trigger",
782
- "data-size": size,
783
- className: clsx(
784
- "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
785
- className
786
- ),
787
- ...props,
788
- children: [
789
- children,
790
- /* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(ChevronDownIcon, {}) })
791
- ]
792
- }
793
- );
794
- }
795
- function SelectContent({
796
- className,
797
- children,
798
- position = "popper",
799
- ...props
800
- }) {
801
- return /* @__PURE__ */ jsx(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsx(
802
- SelectPrimitive.Content,
803
- {
804
- "data-slot": "select-content",
805
- className: clsx(
806
- "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
807
- position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
808
- className
809
- ),
810
- position,
811
- ...props,
812
- children: /* @__PURE__ */ jsx(
813
- SelectPrimitive.Viewport,
814
- {
815
- className: clsx(
816
- "p-1",
817
- position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
818
- ),
819
- children
820
- }
821
- )
822
- }
823
- ) });
824
- }
825
- function SelectItem({
826
- className,
827
- children,
828
- ...props
829
- }) {
830
- return /* @__PURE__ */ jsxs(
831
- SelectPrimitive.Item,
832
- {
833
- "data-slot": "select-item",
834
- className: clsx(
835
- "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
836
- className
837
- ),
838
- ...props,
839
- children: [
840
- /* @__PURE__ */ jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, {}) }) }),
841
- /* @__PURE__ */ jsx(SelectPrimitive.ItemText, { children })
842
- ]
843
- }
844
- );
845
- }
846
- var ThemeSelectorView = ({
847
- colorTheme,
848
- colorThemes,
849
- onSelectColorTheme
850
- }) => {
851
- return /* @__PURE__ */ jsx(Fragment, { children: colorThemes.length > 1 ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: /* @__PURE__ */ jsxs(
852
- Select,
853
- {
854
- value: colorTheme,
855
- onValueChange: (value) => onSelectColorTheme(value),
856
- children: [
857
- /* @__PURE__ */ jsx(SelectTrigger, { className: "capitalize", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Choose a color theme" }) }),
858
- /* @__PURE__ */ jsx(SelectContent, { children: colorThemes.map((theme) => /* @__PURE__ */ jsx(SelectItem, { className: "capitalize", value: theme, children: theme }, theme)) })
859
- ]
860
- }
861
- ) }) : null });
862
- };
863
- var ThemeSelectorWithContext = ({ colorThemes = ["default"], contextTheme }) => {
864
- return /* @__PURE__ */ jsx(
865
- ThemeSelectorView,
866
- {
867
- colorTheme: contextTheme.colorTheme,
868
- colorThemes,
869
- onSelectColorTheme: contextTheme.setColorTheme
870
- }
871
- );
872
- };
873
- var ThemeSelectorStandalone = ({
874
- themes = ["light", "dark", "system"],
875
- colorThemes = ["default"],
876
- currentColorTheme,
877
- onColorThemeChange,
878
- animationType,
879
- duration
880
- }) => {
881
- const standaloneHook = useThemeAnimation({
882
- animationType,
883
- duration,
884
- themes,
885
- colorThemes,
886
- ...currentColorTheme !== void 0 && { colorTheme: currentColorTheme },
887
- onColorThemeChange
888
- });
889
- return /* @__PURE__ */ jsx(
890
- ThemeSelectorView,
891
- {
892
- colorTheme: standaloneHook.colorTheme,
893
- colorThemes,
894
- onSelectColorTheme: standaloneHook.setColorTheme
895
- }
896
- );
897
- };
898
- var ThemeSelector = (props) => {
899
- const contextTheme = useSharedThemeContext();
900
- if (contextTheme) {
901
- return /* @__PURE__ */ jsx(ThemeSelectorWithContext, { ...props, contextTheme });
902
- }
903
- return /* @__PURE__ */ jsx(ThemeSelectorStandalone, { ...props });
904
- };
905
-
906
- // src/react/components/provider-helpers.ts
907
- var withElementAsRef = async (ref, element, run) => {
908
- if (ref.current) {
909
- const originalRef = ref.current;
910
- Object.defineProperty(ref, "current", {
911
- value: element,
912
- writable: true,
913
- configurable: true
914
- });
915
- await run();
916
- Object.defineProperty(ref, "current", {
917
- value: originalRef,
918
- writable: true,
919
- configurable: true
920
- });
921
- return;
922
- }
923
- Object.defineProperty(ref, "current", {
924
- value: element,
925
- writable: true,
926
- configurable: true
927
- });
928
- await run();
929
- };
930
- var getBrowserSystemTheme = () => {
931
- return typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
932
- };
933
- var getNextResolvedTheme = (resolvedTheme) => {
934
- return resolvedTheme === "dark" ? "light" : "dark";
935
- };
936
- var SpacemanThemeContext = createContext(void 0);
937
- var SpacemanThemeProvider = ({
938
- children,
939
- themes = ["light", "dark", "system"],
940
- colorThemes = ["default"],
941
- defaultTheme = "system",
942
- defaultColorTheme = "default",
943
- animationType = "circle" /* CIRCLE */,
944
- duration = 750
945
- }) => {
946
- const [mounted, setMounted] = useState(false);
947
- const themeState = useThemeAnimation({
948
- themes,
949
- colorThemes,
950
- defaultTheme,
951
- defaultColorTheme,
952
- animationType,
953
- duration
954
- });
955
- useEffect(() => {
956
- setMounted(true);
957
- }, []);
958
- const switchThemeFromElement = async (theme, element) => {
959
- await withElementAsRef(themeState.ref, element, async () => {
960
- await themeState.switchTheme(theme);
961
- });
962
- };
963
- if (!mounted) {
964
- const loadingContextValue = {
965
- ref: { current: null },
966
- theme: defaultTheme,
967
- colorTheme: defaultColorTheme,
968
- resolvedTheme: defaultTheme === "dark" ? "dark" : "light",
969
- setTheme: () => {
970
- },
971
- setColorTheme: () => {
972
- },
973
- switchTheme: async () => {
974
- },
975
- switchThemeFromElement: async () => {
976
- },
977
- switchColorTheme: () => {
978
- },
979
- toggleTheme: async () => {
980
- },
981
- toggleLightTheme: async () => {
982
- },
983
- toggleDarkTheme: async () => {
984
- },
985
- toggleColorTheme: () => {
986
- },
987
- createColorThemeToggle: () => () => {
988
- },
989
- isColorThemeActive: () => false
990
- };
991
- return /* @__PURE__ */ jsx(SpacemanThemeContext.Provider, { value: loadingContextValue, children: /* @__PURE__ */ jsx(SharedThemeContext.Provider, { value: loadingContextValue, children }) });
992
- }
993
- const contextValue = {
994
- ref: themeState.ref,
995
- theme: themeState.theme,
996
- colorTheme: themeState.colorTheme,
997
- resolvedTheme: themeState.resolvedTheme,
998
- setTheme: themeState.setTheme,
999
- setColorTheme: themeState.setColorTheme,
1000
- switchTheme: themeState.switchTheme,
1001
- switchThemeFromElement,
1002
- switchColorTheme: themeState.switchColorTheme,
1003
- toggleTheme: themeState.toggleTheme,
1004
- toggleLightTheme: themeState.toggleLightTheme,
1005
- toggleDarkTheme: themeState.toggleDarkTheme,
1006
- toggleColorTheme: themeState.toggleColorTheme,
1007
- createColorThemeToggle: themeState.createColorThemeToggle,
1008
- isColorThemeActive: themeState.isColorThemeActive
1009
- };
1010
- return /* @__PURE__ */ jsx(SpacemanThemeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(SharedThemeContext.Provider, { value: contextValue, children }) });
1011
- };
1012
- var useSpacemanTheme = () => {
1013
- const context = useContext(SpacemanThemeContext);
1014
- if (context === void 0) {
1015
- throw new Error(
1016
- "useSpacemanTheme must be used within a SpacemanThemeProvider"
1017
- );
1018
- }
1019
- return context;
1020
- };
1021
- var NextThemeContext = createContext(
1022
- void 0
1023
- );
1024
- var createDisableTransitions = () => {
1025
- const css = document.createElement("style");
1026
- css.textContent = "*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}";
1027
- document.head.appendChild(css);
1028
- window.getComputedStyle(document.body);
1029
- return () => {
1030
- setTimeout(() => {
1031
- if (css.parentNode) {
1032
- css.parentNode.removeChild(css);
1033
- }
1034
- }, 1);
1035
- };
1036
- };
1037
- var generatePreHydrationScript = ({
1038
- attribute,
1039
- colorStorageKey,
1040
- colorThemePrefix,
1041
- defaultColorTheme,
1042
- defaultTheme,
1043
- enableColorScheme,
1044
- enableSystem,
1045
- globalClassName,
1046
- storageKey,
1047
- value
1048
- }) => {
1049
- const lightValue = value?.light ?? "light";
1050
- const darkValue = value?.dark ?? globalClassName;
1051
- const systemValue = value?.system ?? "system";
1052
- return `(function(){try{var d=document.documentElement;var theme=localStorage.getItem('${storageKey}')||'${defaultTheme}';var colorTheme=localStorage.getItem('${colorStorageKey}')||'${defaultColorTheme}';if(!${JSON.stringify(
1053
- enableSystem
1054
- )}&&theme==='system'){theme='${defaultTheme === "system" ? "light" : defaultTheme}';}var resolved=theme==='system'?(window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'):theme;var attr='${attribute}';var lightValue='${lightValue}';var darkValue='${darkValue}';var systemValue='${systemValue}';if(attr==='data-theme'){d.setAttribute('data-theme',theme==='system'?systemValue:(resolved==='dark'?darkValue:lightValue));}else{d.classList.remove(lightValue,darkValue,systemValue,'auto');d.classList.add(theme==='system'?systemValue:(resolved==='dark'?darkValue:lightValue));}if(${JSON.stringify(
1055
- enableColorScheme
1056
- )}){d.style.colorScheme=theme==='system'?'':resolved;}else{d.style.removeProperty('color-scheme');}d.classList.add('${colorThemePrefix}'+colorTheme);}catch(e){console.warn('Theme pre-hydration script failed:',e);}})();`;
1057
- };
1058
- var ThemePreHydrationScript = React4.memo(
1059
- ({
1060
- attribute,
1061
- colorStorageKey,
1062
- colorThemePrefix,
1063
- defaultColorTheme,
1064
- defaultTheme,
1065
- enableColorScheme,
1066
- enableSystem,
1067
- globalClassName,
1068
- nonce,
1069
- scriptProps,
1070
- storageKey,
1071
- value
1072
- }) => /* @__PURE__ */ jsx(
1073
- "script",
1074
- {
1075
- ...scriptProps,
1076
- nonce,
1077
- suppressHydrationWarning: true,
1078
- dangerouslySetInnerHTML: {
1079
- __html: generatePreHydrationScript({
1080
- attribute,
1081
- colorStorageKey,
1082
- colorThemePrefix,
1083
- defaultColorTheme,
1084
- defaultTheme,
1085
- enableColorScheme,
1086
- enableSystem,
1087
- globalClassName,
1088
- storageKey,
1089
- value
1090
- })
1091
- }
1092
- }
1093
- )
1094
- );
1095
- ThemePreHydrationScript.displayName = "ThemePreHydrationScript";
1096
- var NextThemeProvider = ({
1097
- attribute = "class",
1098
- children,
1099
- colorStorageKey = "color-theme",
1100
- colorThemePrefix = "theme-",
1101
- colorThemes = ["default"],
1102
- defaultColorTheme = "default",
1103
- defaultTheme = "system",
1104
- disableAnimationOnInit = true,
1105
- disablePreHydrationScript = false,
1106
- disableTransitionOnChange = false,
1107
- duration = 750,
1108
- enableColorScheme = true,
1109
- enableSystem = true,
1110
- forcedTheme,
1111
- globalClassName = "dark",
1112
- nonce,
1113
- onColorThemeChange,
1114
- onThemeChange,
1115
- scriptProps,
1116
- storageKey = "theme",
1117
- themes,
1118
- value,
1119
- animationType = "circle" /* CIRCLE */
1120
- }) => {
1121
- const allowedThemes = useMemo(() => {
1122
- if (themes && themes.length > 0) {
1123
- return themes;
1124
- }
1125
- return enableSystem ? ["light", "dark", "system"] : ["light", "dark"];
1126
- }, [enableSystem, themes]);
1127
- const effectiveDefaultTheme = useMemo(() => {
1128
- if (defaultTheme === "system" && !enableSystem) {
1129
- return "light";
1130
- }
1131
- return defaultTheme;
1132
- }, [defaultTheme, enableSystem]);
1133
- const [mounted, setMounted] = useState(false);
1134
- const themeState = useThemeAnimation({
1135
- animationType,
1136
- attribute,
1137
- colorStorageKey,
1138
- colorTheme: forcedTheme ? void 0 : void 0,
1139
- colorThemePrefix,
1140
- colorThemes,
1141
- defaultColorTheme,
1142
- defaultTheme: effectiveDefaultTheme,
1143
- duration,
1144
- enableColorScheme,
1145
- globalClassName,
1146
- onColorThemeChange,
1147
- onThemeChange,
1148
- storageKey,
1149
- theme: forcedTheme,
1150
- themes: allowedThemes,
1151
- value
1152
- });
1153
- useEffect(() => {
1154
- setMounted(true);
1155
- }, []);
1156
- const withTransitionsDisabled = useCallback(
1157
- async (fn) => {
1158
- const cleanup = disableTransitionOnChange ? createDisableTransitions() : void 0;
1159
- try {
1160
- return await fn();
1161
- } finally {
1162
- cleanup?.();
1163
- }
1164
- },
1165
- [disableTransitionOnChange]
1166
- );
1167
- const setTheme = useCallback(
1168
- (theme) => {
1169
- void withTransitionsDisabled(async () => {
1170
- themeState.setTheme(theme);
1171
- });
1172
- },
1173
- [themeState, withTransitionsDisabled]
1174
- );
1175
- const switchTheme = useCallback(
1176
- async (theme, animationOff = false) => {
1177
- await withTransitionsDisabled(async () => {
1178
- if (!mounted && disableAnimationOnInit) {
1179
- themeState.setTheme(theme);
1180
- return;
1181
- }
1182
- await themeState.switchTheme(theme, animationOff);
1183
- });
1184
- },
1185
- [disableAnimationOnInit, mounted, themeState, withTransitionsDisabled]
1186
- );
1187
- const switchThemeFromElement = useCallback(
1188
- async (theme, element) => {
1189
- await withTransitionsDisabled(async () => {
1190
- if (!mounted && disableAnimationOnInit) {
1191
- themeState.setTheme(theme);
1192
- return;
1193
- }
1194
- await withElementAsRef(themeState.ref, element, async () => {
1195
- await themeState.switchTheme(theme);
1196
- });
1197
- });
1198
- },
1199
- [disableAnimationOnInit, mounted, themeState, withTransitionsDisabled]
1200
- );
1201
- const contextValue = useMemo(
1202
- () => ({
1203
- ref: themeState.ref,
1204
- theme: themeState.theme,
1205
- colorTheme: themeState.colorTheme,
1206
- resolvedTheme: themeState.resolvedTheme,
1207
- systemTheme: themeState.systemTheme,
1208
- setTheme,
1209
- setColorTheme: themeState.setColorTheme,
1210
- switchTheme,
1211
- switchColorTheme: themeState.switchColorTheme,
1212
- toggleTheme: async (animationOff = false) => switchTheme(
1213
- getNextResolvedTheme(themeState.resolvedTheme),
1214
- animationOff
1215
- ),
1216
- toggleLightTheme: async (animationOff = false) => {
1217
- if (themeState.resolvedTheme === "light") return;
1218
- await switchTheme("light", animationOff);
1219
- },
1220
- toggleDarkTheme: async (animationOff = false) => {
1221
- if (themeState.resolvedTheme === "dark") return;
1222
- await switchTheme("dark", animationOff);
1223
- },
1224
- toggleColorTheme: themeState.toggleColorTheme,
1225
- createColorThemeToggle: themeState.createColorThemeToggle,
1226
- isColorThemeActive: themeState.isColorThemeActive,
1227
- switchThemeFromElement
1228
- }),
1229
- [setTheme, switchTheme, switchThemeFromElement, themeState]
1230
- );
1231
- return /* @__PURE__ */ jsx(NextThemeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(SharedThemeContext.Provider, { value: contextValue, children: [
1232
- !disablePreHydrationScript && !forcedTheme ? /* @__PURE__ */ jsx(
1233
- ThemePreHydrationScript,
1234
- {
1235
- attribute,
1236
- colorStorageKey,
1237
- colorThemePrefix,
1238
- defaultColorTheme,
1239
- defaultTheme: effectiveDefaultTheme,
1240
- enableColorScheme,
1241
- enableSystem,
1242
- globalClassName,
1243
- nonce,
1244
- scriptProps,
1245
- storageKey,
1246
- value
1247
- }
1248
- ) : null,
1249
- children
1250
- ] }) });
1251
- };
1252
- var ThemeProvider = NextThemeProvider;
1253
- var useNextTheme = () => {
1254
- const context = useContext(NextThemeContext);
1255
- if (context === void 0) {
1256
- throw new Error("useNextTheme must be used within a NextThemeProvider");
1257
- }
1258
- return context;
1259
- };
1260
- var useTheme = useNextTheme;
1261
- var isBrowser3 = typeof window !== "undefined";
1262
- var useSyncServerThemeStorage = ({
1263
- enabled,
1264
- theme,
1265
- colorTheme,
1266
- storageKey,
1267
- colorStorageKey
1268
- }) => {
1269
- useEffect(() => {
1270
- if (!enabled || !isBrowser3) return;
1271
- if (theme) {
1272
- localStorage.setItem(storageKey, theme);
1273
- }
1274
- if (colorTheme) {
1275
- localStorage.setItem(colorStorageKey, colorTheme);
1276
- }
1277
- }, [enabled, theme, colorTheme, storageKey, colorStorageKey]);
1278
- };
1279
-
1280
- // src/tanstack/helpers.ts
1281
- var STORAGE_KEY = "theme";
1282
- var COLOR_STORAGE_KEY = "color-theme";
1283
- var GLOBAL_CLASS_NAME = "dark";
1284
- var COLOR_THEME_PREFIX = "theme-";
1285
- function buildServerThemeData(themeCookie, colorThemeCookie, options = {}) {
1286
- const { defaultTheme = "system", defaultColorTheme = "default" } = options;
1287
- const themePreference = themeCookie ?? defaultTheme;
1288
- const colorTheme = colorThemeCookie ?? defaultColorTheme;
1289
- return {
1290
- theme: resolveThemeForServer(themePreference),
1291
- themePreference,
1292
- colorTheme
1293
- };
1294
- }
1295
- var TanStackThemeContext = createContext(void 0);
1296
- var generateTanStackPreHydrationScript = (storageKey, colorStorageKey, defaultTheme, defaultColorTheme, globalClassName, colorThemePrefix, systemThemeMode) => {
1297
- return `
1298
- (function() {
1299
- try {
1300
- var theme = localStorage.getItem('${storageKey}') || '${defaultTheme}';
1301
- var colorTheme = localStorage.getItem('${colorStorageKey}') || '${defaultColorTheme}';
1302
- var el = document.documentElement;
1303
- var resolved;
1304
- if (theme === 'system') {
1305
- if ('${systemThemeMode}' === 'css') {
1306
- el.classList.remove('${globalClassName}');
1307
- el.classList.remove('auto');
1308
- el.classList.remove('system');
1309
- el.classList.add('system');
1310
- el.style.colorScheme = '';
1311
- } else {
1312
- resolved = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
1313
- if (resolved === 'dark') {
1314
- el.classList.add('${globalClassName}');
1315
- } else {
1316
- el.classList.remove('${globalClassName}');
1317
- }
1318
- el.classList.remove('system');
1319
- el.classList.remove('auto');
1320
- el.style.colorScheme = resolved;
1321
- }
1322
- } else {
1323
- resolved = theme;
1324
- el.classList.remove('system');
1325
- el.classList.remove('auto');
1326
- if (resolved === 'dark') {
1327
- el.classList.add('${globalClassName}');
1328
- } else {
1329
- el.classList.remove('${globalClassName}');
1330
- }
1331
- el.style.colorScheme = resolved;
1332
- }
1333
- if (colorTheme && colorTheme !== 'default') {
1334
- el.classList.add('${colorThemePrefix}' + colorTheme);
1335
- }
1336
- } catch (e) {
1337
- console.warn('Theme pre-hydration script failed:', e);
1338
- }
1339
- })();
1340
- `;
1341
- };
1342
- var TanStackStartThemeScript = React4.memo(
1343
- ({
1344
- storageKey = STORAGE_KEY,
1345
- colorStorageKey = COLOR_STORAGE_KEY,
1346
- defaultTheme = "system",
1347
- defaultColorTheme = "default",
1348
- globalClassName = GLOBAL_CLASS_NAME,
1349
- colorThemePrefix = COLOR_THEME_PREFIX,
1350
- nonce,
1351
- systemThemeMode = "js"
1352
- }) => {
1353
- const scriptContent = generateTanStackPreHydrationScript(
1354
- storageKey,
1355
- colorStorageKey,
1356
- defaultTheme,
1357
- defaultColorTheme,
1358
- globalClassName,
1359
- colorThemePrefix,
1360
- systemThemeMode
1361
- );
1362
- return /* @__PURE__ */ jsx(
1363
- "script",
1364
- {
1365
- nonce,
1366
- suppressHydrationWarning: true,
1367
- dangerouslySetInnerHTML: { __html: scriptContent }
1368
- }
1369
- );
1370
- }
1371
- );
1372
- TanStackStartThemeScript.displayName = "TanStackStartThemeScript";
1373
- var useHydrated = () => {
1374
- return useSyncExternalStore(
1375
- () => () => {
1376
- },
1377
- () => true,
1378
- () => false
1379
- );
1380
- };
1381
- var getNextColorTheme = (colorThemes, currentColorTheme) => {
1382
- if (colorThemes.length === 0) return currentColorTheme;
1383
- const currentIndex = colorThemes.indexOf(currentColorTheme);
1384
- const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % colorThemes.length;
1385
- return colorThemes[nextIndex];
1386
- };
1387
- var TanStackThemeProvider = ({
1388
- children,
1389
- themes = ["light", "dark", "system"],
1390
- colorThemes = ["default"],
1391
- defaultTheme = "system",
1392
- defaultColorTheme = "default",
1393
- animationType = "circle" /* CIRCLE */,
1394
- duration = 750,
1395
- storageKey = STORAGE_KEY,
1396
- colorStorageKey = COLOR_STORAGE_KEY,
1397
- globalClassName = GLOBAL_CLASS_NAME,
1398
- colorThemePrefix = COLOR_THEME_PREFIX,
1399
- serverTheme,
1400
- serverColorTheme,
1401
- systemThemeMode = "css",
1402
- onServerThemeChange,
1403
- onServerColorThemeChange
1404
- }) => {
1405
- const isHydrated = useHydrated();
1406
- const hasServerTheme = serverTheme !== void 0;
1407
- const initialTheme = hasServerTheme ? serverTheme : void 0;
1408
- const themeState = useThemeAnimation({
1409
- themes,
1410
- colorThemes,
1411
- defaultTheme,
1412
- defaultColorTheme,
1413
- animationType,
1414
- duration,
1415
- storageKey,
1416
- colorStorageKey,
1417
- globalClassName,
1418
- colorThemePrefix,
1419
- initialTheme,
1420
- initialColorTheme: serverColorTheme,
1421
- systemThemeMode
1422
- });
1423
- useSyncServerThemeStorage({
1424
- enabled: hasServerTheme,
1425
- theme: initialTheme,
1426
- colorTheme: serverColorTheme,
1427
- storageKey,
1428
- colorStorageKey
1429
- });
1430
- const setThemeWithServer = useCallback(
1431
- (newTheme) => {
1432
- themeState.setTheme(newTheme);
1433
- onServerThemeChange?.(newTheme);
1434
- },
1435
- [themeState, onServerThemeChange]
1436
- );
1437
- const setColorThemeWithServer = useCallback(
1438
- (newColorTheme) => {
1439
- themeState.setColorTheme(newColorTheme);
1440
- onServerColorThemeChange?.(newColorTheme);
1441
- },
1442
- [themeState, onServerColorThemeChange]
1443
- );
1444
- const switchThemeWithHydrationAwareness = useCallback(
1445
- async (theme, animationOff = false) => {
1446
- if (!isHydrated) {
1447
- setThemeWithServer(theme);
1448
- return;
1449
- }
1450
- await themeState.switchTheme(theme, animationOff);
1451
- onServerThemeChange?.(theme);
1452
- },
1453
- [isHydrated, themeState, setThemeWithServer, onServerThemeChange]
1454
- );
1455
- const toggleThemeWithHydrationAwareness = useCallback(
1456
- async (animationOff = false) => {
1457
- const nextTheme = getNextResolvedTheme(themeState.resolvedTheme);
1458
- if (!isHydrated) {
1459
- setThemeWithServer(nextTheme);
1460
- return;
1461
- }
1462
- await themeState.toggleTheme(animationOff);
1463
- onServerThemeChange?.(nextTheme);
1464
- },
1465
- [isHydrated, themeState, setThemeWithServer, onServerThemeChange]
1466
- );
1467
- const toggleLightThemeWithHydrationAwareness = useCallback(
1468
- async (animationOff = false) => {
1469
- if (!isHydrated) {
1470
- setThemeWithServer("light");
1471
- return;
1472
- }
1473
- await themeState.toggleLightTheme(animationOff);
1474
- onServerThemeChange?.("light");
1475
- },
1476
- [isHydrated, themeState, setThemeWithServer, onServerThemeChange]
1477
- );
1478
- const toggleDarkThemeWithHydrationAwareness = useCallback(
1479
- async (animationOff = false) => {
1480
- if (!isHydrated) {
1481
- setThemeWithServer("dark");
1482
- return;
1483
- }
1484
- await themeState.toggleDarkTheme(animationOff);
1485
- onServerThemeChange?.("dark");
1486
- },
1487
- [isHydrated, themeState, setThemeWithServer, onServerThemeChange]
1488
- );
1489
- const switchColorThemeWithServer = useCallback(
1490
- (newColorTheme) => {
1491
- themeState.switchColorTheme(newColorTheme);
1492
- onServerColorThemeChange?.(newColorTheme);
1493
- },
1494
- [themeState, onServerColorThemeChange]
1495
- );
1496
- const toggleColorThemeWithServer = useCallback(() => {
1497
- const nextColorTheme = getNextColorTheme(colorThemes, themeState.colorTheme);
1498
- themeState.toggleColorTheme();
1499
- onServerColorThemeChange?.(nextColorTheme);
1500
- }, [colorThemes, themeState, onServerColorThemeChange]);
1501
- const createColorThemeToggleWithServer = useCallback(
1502
- (targetColorTheme) => () => {
1503
- themeState.createColorThemeToggle(targetColorTheme)();
1504
- onServerColorThemeChange?.(targetColorTheme);
1505
- },
1506
- [themeState, onServerColorThemeChange]
1507
- );
1508
- const switchThemeFromElement = useCallback(
1509
- async (theme, element) => {
1510
- if (!isHydrated) {
1511
- setThemeWithServer(theme);
1512
- return;
1513
- }
1514
- await withElementAsRef(themeState.ref, element, async () => {
1515
- await themeState.switchTheme(theme);
1516
- });
1517
- onServerThemeChange?.(theme);
1518
- },
1519
- [isHydrated, themeState, setThemeWithServer, onServerThemeChange]
1520
- );
1521
- const systemTheme = getBrowserSystemTheme();
1522
- const serverResolvedTheme = (() => {
1523
- if (hasServerTheme) {
1524
- if (serverTheme === "dark") return "dark";
1525
- if (serverTheme === "system") return systemTheme;
1526
- return "light";
1527
- }
1528
- return defaultTheme === "dark" ? "dark" : "light";
1529
- })();
1530
- if (!isHydrated) {
1531
- const loadingContextValue = {
1532
- ref: { current: null },
1533
- theme: initialTheme ?? defaultTheme,
1534
- colorTheme: serverColorTheme ?? defaultColorTheme,
1535
- resolvedTheme: serverResolvedTheme,
1536
- systemTheme: "light",
1537
- isHydrated: false,
1538
- setTheme: setThemeWithServer,
1539
- setColorTheme: setColorThemeWithServer,
1540
- switchTheme: async (theme) => setThemeWithServer(theme),
1541
- switchThemeFromElement: async (theme) => setThemeWithServer(theme),
1542
- switchColorTheme: switchColorThemeWithServer,
1543
- toggleTheme: async () => {
1544
- const nextTheme = serverResolvedTheme === "dark" ? "light" : "dark";
1545
- setThemeWithServer(nextTheme);
1546
- },
1547
- toggleLightTheme: async () => setThemeWithServer("light"),
1548
- toggleDarkTheme: async () => setThemeWithServer("dark"),
1549
- toggleColorTheme: toggleColorThemeWithServer,
1550
- createColorThemeToggle: createColorThemeToggleWithServer,
1551
- isColorThemeActive: themeState.isColorThemeActive
1552
- };
1553
- return /* @__PURE__ */ jsx(TanStackThemeContext.Provider, { value: loadingContextValue, children: /* @__PURE__ */ jsx(SharedThemeContext.Provider, { value: loadingContextValue, children }) });
1554
- }
1555
- const contextValue = {
1556
- ref: themeState.ref,
1557
- theme: themeState.theme,
1558
- colorTheme: themeState.colorTheme,
1559
- resolvedTheme: themeState.resolvedTheme,
1560
- systemTheme,
1561
- isHydrated: true,
1562
- setTheme: setThemeWithServer,
1563
- setColorTheme: setColorThemeWithServer,
1564
- switchTheme: switchThemeWithHydrationAwareness,
1565
- switchThemeFromElement,
1566
- switchColorTheme: switchColorThemeWithServer,
1567
- toggleTheme: toggleThemeWithHydrationAwareness,
1568
- toggleLightTheme: toggleLightThemeWithHydrationAwareness,
1569
- toggleDarkTheme: toggleDarkThemeWithHydrationAwareness,
1570
- toggleColorTheme: toggleColorThemeWithServer,
1571
- createColorThemeToggle: createColorThemeToggleWithServer,
1572
- isColorThemeActive: themeState.isColorThemeActive
1573
- };
1574
- return /* @__PURE__ */ jsx(TanStackThemeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(SharedThemeContext.Provider, { value: contextValue, children }) });
1575
- };
1576
- var useTanStackTheme = () => {
1577
- const context = useContext(TanStackThemeContext);
1578
- if (context === void 0) {
1579
- throw new Error(
1580
- "useTanStackTheme must be used within a TanStackThemeProvider"
1581
- );
1582
- }
1583
- return context;
1584
- };
1585
- var ViteThemeContext = createContext(
1586
- void 0
1587
- );
1588
- var ViteThemeProvider = ({
1589
- children,
1590
- themes = ["light", "dark", "system"],
1591
- colorThemes = ["default"],
1592
- defaultTheme = "system",
1593
- defaultColorTheme = "default",
1594
- animationType = "circle" /* CIRCLE */,
1595
- duration = 750,
1596
- attribute = "class",
1597
- disableTransitionOnChange = false,
1598
- storageKey = "vite-theme",
1599
- colorStorageKey = "vite-color-theme",
1600
- globalClassName = "dark",
1601
- colorThemePrefix = "theme-"
1602
- }) => {
1603
- const themeState = useThemeAnimation({
1604
- themes,
1605
- colorThemes,
1606
- defaultTheme,
1607
- defaultColorTheme,
1608
- animationType,
1609
- duration,
1610
- storageKey,
1611
- colorStorageKey,
1612
- globalClassName: attribute === "class" ? globalClassName : void 0,
1613
- colorThemePrefix
1614
- });
1615
- const applyTransitionDisable = useCallback(() => {
1616
- if (!disableTransitionOnChange) return () => {
1617
- };
1618
- const css = document.createElement("style");
1619
- css.textContent = `*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}`;
1620
- document.head.appendChild(css);
1621
- window.getComputedStyle(document.body);
1622
- return () => {
1623
- setTimeout(() => {
1624
- document.head.removeChild(css);
1625
- }, 1);
1626
- };
1627
- }, [disableTransitionOnChange]);
1628
- const wrappedSetTheme = useCallback(
1629
- (theme) => {
1630
- const cleanup = applyTransitionDisable();
1631
- themeState.setTheme(theme);
1632
- cleanup();
1633
- },
1634
- [themeState.setTheme, applyTransitionDisable]
1635
- );
1636
- const wrappedSwitchTheme = useCallback(
1637
- async (theme, animationOff = false) => {
1638
- const cleanup = applyTransitionDisable();
1639
- await themeState.switchTheme(theme, animationOff);
1640
- cleanup();
1641
- },
1642
- [themeState.switchTheme, applyTransitionDisable]
1643
- );
1644
- const wrappedToggleTheme = useCallback(
1645
- async (animationOff = false) => {
1646
- const cleanup = applyTransitionDisable();
1647
- await themeState.toggleTheme(animationOff);
1648
- cleanup();
1649
- },
1650
- [themeState.toggleTheme, applyTransitionDisable]
1651
- );
1652
- const wrappedToggleLightTheme = useCallback(
1653
- async (animationOff = false) => {
1654
- const cleanup = applyTransitionDisable();
1655
- await themeState.toggleLightTheme(animationOff);
1656
- cleanup();
1657
- },
1658
- [themeState.toggleLightTheme, applyTransitionDisable]
1659
- );
1660
- const wrappedToggleDarkTheme = useCallback(
1661
- async (animationOff = false) => {
1662
- const cleanup = applyTransitionDisable();
1663
- await themeState.toggleDarkTheme(animationOff);
1664
- cleanup();
1665
- },
1666
- [themeState.toggleDarkTheme, applyTransitionDisable]
1667
- );
1668
- const switchThemeFromElement = async (theme, element) => {
1669
- const cleanup = applyTransitionDisable();
1670
- await withElementAsRef(themeState.ref, element, async () => {
1671
- await themeState.switchTheme(theme);
1672
- });
1673
- cleanup();
1674
- };
1675
- const systemTheme = getBrowserSystemTheme();
1676
- const contextValue = {
1677
- ref: themeState.ref,
1678
- theme: themeState.theme,
1679
- colorTheme: themeState.colorTheme,
1680
- resolvedTheme: themeState.resolvedTheme,
1681
- systemTheme,
1682
- setTheme: wrappedSetTheme,
1683
- setColorTheme: themeState.setColorTheme,
1684
- switchTheme: wrappedSwitchTheme,
1685
- switchThemeFromElement,
1686
- switchColorTheme: themeState.switchColorTheme,
1687
- toggleTheme: wrappedToggleTheme,
1688
- toggleLightTheme: wrappedToggleLightTheme,
1689
- toggleDarkTheme: wrappedToggleDarkTheme,
1690
- toggleColorTheme: themeState.toggleColorTheme,
1691
- createColorThemeToggle: themeState.createColorThemeToggle,
1692
- isColorThemeActive: themeState.isColorThemeActive
1693
- };
1694
- return /* @__PURE__ */ jsx(ViteThemeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(SharedThemeContext.Provider, { value: contextValue, children }) });
1695
- };
1696
- var useViteTheme = () => {
1697
- const context = useContext(ViteThemeContext);
1698
- if (context === void 0) {
1699
- throw new Error("useViteTheme must be used within a ViteThemeProvider");
1700
- }
1701
- return context;
1702
- };
1703
-
1704
- export { COLOR_STORAGE_KEY, COLOR_THEME_PREFIX, GLOBAL_CLASS_NAME, NextThemeProvider, STORAGE_KEY, SpacemanThemeProvider, TanStackStartThemeScript, TanStackThemeProvider, ThemeAnimationType, ThemeProvider, ThemeSelector, ThemeSwitcher, ViteThemeProvider, buildServerThemeData, getSystemTheme, injectBaseStyles, prefersReducedMotion, resolveTheme, resolveThemeForServer, supportsViewTransitions, useNextTheme, useSpacemanTheme, useTanStackTheme, useTheme, useThemeAnimation, useViteTheme };
1705
- //# sourceMappingURL=index.js.map
1706
- //# sourceMappingURL=index.js.map
1
+ export{g as NextThemeProvider,e as SpacemanThemeProvider,k as TanStackStartThemeScript,l as TanStackThemeProvider,h as ThemeProvider,d as ThemeSelector,c as ThemeSwitcher,n as ViteThemeProvider,a as useHydrated,i as useNextTheme,f as useSpacemanTheme,m as useTanStackTheme,j as useTheme,b as useThemeAnimation,o as useViteTheme}from'./chunk-FNL7ZHF4.js';export{d as TRANSITION_DIRECTIONS,c as ThemeAnimationType,b as preloadThemeLogo}from'./chunk-X6UEG7WG.js';export{b as COLOR_STORAGE_KEY,d as COLOR_THEME_PREFIX,c as GLOBAL_CLASS_NAME,a as STORAGE_KEY,e as buildServerThemeData}from'./chunk-QYUMCBTQ.js';export{b as getSystemTheme,a as injectBaseStyles,f as prefersReducedMotion,c as resolveTheme,d as resolveThemeForServer,e as supportsViewTransitions}from'./chunk-I3T2QXRT.js';