@space-man/react-theme-animation 2.3.0 → 3.0.1

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