shru-design-system 0.1.1 → 0.1.3
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.d.mts +111 -1
- package/dist/index.d.ts +111 -1
- package/dist/index.js +1095 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1083 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -11
- package/scripts/apply-theme-sync.js +230 -0
- package/scripts/init.js +445 -10
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
2
3
|
import { Slot } from '@radix-ui/react-slot';
|
|
3
4
|
import { cva } from 'class-variance-authority';
|
|
4
5
|
import { clsx } from 'clsx';
|
|
@@ -7,6 +8,7 @@ import { jsx, jsxs } from 'react/jsx-runtime';
|
|
|
7
8
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
8
9
|
import { XIcon, ChevronDownIcon, CheckIcon, ChevronUpIcon } from 'lucide-react';
|
|
9
10
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
11
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
10
12
|
|
|
11
13
|
// src/atoms/Button.tsx
|
|
12
14
|
function cn(...inputs) {
|
|
@@ -358,7 +360,1087 @@ function SelectScrollDownButton({
|
|
|
358
360
|
}
|
|
359
361
|
);
|
|
360
362
|
}
|
|
363
|
+
function TooltipProvider({
|
|
364
|
+
delayDuration = 0,
|
|
365
|
+
...props
|
|
366
|
+
}) {
|
|
367
|
+
return /* @__PURE__ */ jsx(
|
|
368
|
+
TooltipPrimitive.Provider,
|
|
369
|
+
{
|
|
370
|
+
"data-slot": "tooltip-provider",
|
|
371
|
+
delayDuration,
|
|
372
|
+
...props
|
|
373
|
+
}
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
function Tooltip({
|
|
377
|
+
...props
|
|
378
|
+
}) {
|
|
379
|
+
return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx(TooltipPrimitive.Root, { "data-slot": "tooltip", ...props }) });
|
|
380
|
+
}
|
|
381
|
+
function TooltipTrigger({
|
|
382
|
+
...props
|
|
383
|
+
}) {
|
|
384
|
+
return /* @__PURE__ */ jsx(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
|
|
385
|
+
}
|
|
386
|
+
function TooltipContent({
|
|
387
|
+
className,
|
|
388
|
+
sideOffset = 0,
|
|
389
|
+
children,
|
|
390
|
+
side,
|
|
391
|
+
...props
|
|
392
|
+
}) {
|
|
393
|
+
return /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
|
|
394
|
+
TooltipPrimitive.Content,
|
|
395
|
+
{
|
|
396
|
+
"data-slot": "tooltip-content",
|
|
397
|
+
sideOffset,
|
|
398
|
+
side,
|
|
399
|
+
className: cn(
|
|
400
|
+
"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
|
401
|
+
className
|
|
402
|
+
),
|
|
403
|
+
...props,
|
|
404
|
+
children: [
|
|
405
|
+
children,
|
|
406
|
+
/* @__PURE__ */ jsx(TooltipPrimitive.Arrow, { className: "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" })
|
|
407
|
+
]
|
|
408
|
+
}
|
|
409
|
+
) });
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/themes/themeConfig.ts
|
|
413
|
+
var baseThemeCategories = {
|
|
414
|
+
color: {
|
|
415
|
+
name: "Color",
|
|
416
|
+
order: 1,
|
|
417
|
+
themes: {
|
|
418
|
+
white: {
|
|
419
|
+
name: "White",
|
|
420
|
+
file: "color/white.json",
|
|
421
|
+
icon: "\u{1F3A8}",
|
|
422
|
+
description: "Light theme with white background"
|
|
423
|
+
},
|
|
424
|
+
dark: {
|
|
425
|
+
name: "Dark",
|
|
426
|
+
file: "color/dark.json",
|
|
427
|
+
icon: "\u{1F319}",
|
|
428
|
+
description: "Dark theme with dark background"
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
typography: {
|
|
433
|
+
name: "Typography",
|
|
434
|
+
order: 2,
|
|
435
|
+
themes: {
|
|
436
|
+
sans: {
|
|
437
|
+
name: "Sans",
|
|
438
|
+
file: "typography/sans.json",
|
|
439
|
+
icon: "\u{1F4DD}",
|
|
440
|
+
description: "Sans-serif font family"
|
|
441
|
+
},
|
|
442
|
+
serif: {
|
|
443
|
+
name: "Serif",
|
|
444
|
+
file: "typography/serif.json",
|
|
445
|
+
icon: "\u{1F4D6}",
|
|
446
|
+
description: "Serif font family"
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
shape: {
|
|
451
|
+
name: "Shape",
|
|
452
|
+
order: 3,
|
|
453
|
+
themes: {
|
|
454
|
+
smooth: {
|
|
455
|
+
name: "Smooth",
|
|
456
|
+
file: "shape/smooth.json",
|
|
457
|
+
icon: "\u{1F532}",
|
|
458
|
+
description: "Smooth rounded corners"
|
|
459
|
+
},
|
|
460
|
+
sharp: {
|
|
461
|
+
name: "Sharp",
|
|
462
|
+
file: "shape/sharp.json",
|
|
463
|
+
icon: "\u2B1C",
|
|
464
|
+
description: "Sharp square corners"
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
density: {
|
|
469
|
+
name: "Density",
|
|
470
|
+
order: 4,
|
|
471
|
+
themes: {
|
|
472
|
+
comfortable: {
|
|
473
|
+
name: "Comfortable",
|
|
474
|
+
file: "density/comfortable.json",
|
|
475
|
+
icon: "\u{1F4CF}",
|
|
476
|
+
description: "Comfortable spacing"
|
|
477
|
+
},
|
|
478
|
+
compact: {
|
|
479
|
+
name: "Compact",
|
|
480
|
+
file: "density/compact.json",
|
|
481
|
+
icon: "\u{1F4D0}",
|
|
482
|
+
description: "Compact spacing"
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
animation: {
|
|
487
|
+
name: "Animation",
|
|
488
|
+
order: 5,
|
|
489
|
+
themes: {
|
|
490
|
+
gentle: {
|
|
491
|
+
name: "Gentle",
|
|
492
|
+
file: "animation/gentle.json",
|
|
493
|
+
icon: "\u2728",
|
|
494
|
+
description: "Gentle animations"
|
|
495
|
+
},
|
|
496
|
+
brisk: {
|
|
497
|
+
name: "Brisk",
|
|
498
|
+
file: "animation/brisk.json",
|
|
499
|
+
icon: "\u26A1",
|
|
500
|
+
description: "Fast, brisk animations"
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
custom: {
|
|
505
|
+
name: "Custom",
|
|
506
|
+
order: 10,
|
|
507
|
+
// Highest priority
|
|
508
|
+
themes: {
|
|
509
|
+
brand: {
|
|
510
|
+
name: "Brand",
|
|
511
|
+
file: "custom/brand.json",
|
|
512
|
+
icon: "\u{1F3AF}",
|
|
513
|
+
description: "Brand-specific theme"
|
|
514
|
+
},
|
|
515
|
+
minimal: {
|
|
516
|
+
name: "Minimal",
|
|
517
|
+
file: "custom/minimal.json",
|
|
518
|
+
icon: "\u{1F3AA}",
|
|
519
|
+
description: "Minimal theme"
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
var discoveredThemesCache = null;
|
|
525
|
+
async function discoverThemes() {
|
|
526
|
+
if (discoveredThemesCache) {
|
|
527
|
+
return discoveredThemesCache;
|
|
528
|
+
}
|
|
529
|
+
const discovered = JSON.parse(JSON.stringify(baseThemeCategories));
|
|
530
|
+
try {
|
|
531
|
+
const tokensBase = typeof window !== "undefined" && window.__THEME_TOKENS_BASE__ ? window.__THEME_TOKENS_BASE__ : "/tokens";
|
|
532
|
+
const knownCategories = Object.keys(baseThemeCategories);
|
|
533
|
+
for (const category of knownCategories) {
|
|
534
|
+
const categoryPath = `${tokensBase}/themes/${category}`;
|
|
535
|
+
}
|
|
536
|
+
discoveredThemesCache = discovered;
|
|
537
|
+
return discovered;
|
|
538
|
+
} catch (error) {
|
|
539
|
+
console.warn("Error discovering themes:", error);
|
|
540
|
+
return baseThemeCategories;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function registerTheme(category, themeId, metadata) {
|
|
544
|
+
if (!discoveredThemesCache) {
|
|
545
|
+
discoveredThemesCache = JSON.parse(JSON.stringify(baseThemeCategories));
|
|
546
|
+
}
|
|
547
|
+
const cache = discoveredThemesCache;
|
|
548
|
+
if (!cache[category]) {
|
|
549
|
+
cache[category] = {
|
|
550
|
+
name: category.charAt(0).toUpperCase() + category.slice(1),
|
|
551
|
+
order: 99,
|
|
552
|
+
// Custom categories get high order
|
|
553
|
+
themes: {}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
cache[category].themes[themeId] = {
|
|
557
|
+
name: metadata.name,
|
|
558
|
+
file: metadata.file,
|
|
559
|
+
icon: metadata.icon || "\u{1F3A8}",
|
|
560
|
+
description: metadata.description || ""
|
|
561
|
+
};
|
|
562
|
+
return cache;
|
|
563
|
+
}
|
|
564
|
+
async function getThemeCategories() {
|
|
565
|
+
return await discoverThemes();
|
|
566
|
+
}
|
|
567
|
+
function getThemeFilePath(category, themeId) {
|
|
568
|
+
const categories = discoveredThemesCache || baseThemeCategories;
|
|
569
|
+
const theme = categories[category]?.themes[themeId];
|
|
570
|
+
if (!theme) return null;
|
|
571
|
+
return `/tokens/themes/${theme.file}`;
|
|
572
|
+
}
|
|
573
|
+
async function getThemesForCategory(category) {
|
|
574
|
+
const categories = await getThemeCategories();
|
|
575
|
+
return categories[category]?.themes || {};
|
|
576
|
+
}
|
|
577
|
+
async function getTheme(category, themeId) {
|
|
578
|
+
const categories = await getThemeCategories();
|
|
579
|
+
return categories[category]?.themes[themeId] || null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// src/themes/themeUtils.ts
|
|
583
|
+
function getThemeName(selectedThemes) {
|
|
584
|
+
const parts = [];
|
|
585
|
+
if (selectedThemes.color) parts.push(selectedThemes.color);
|
|
586
|
+
if (selectedThemes.typography) parts.push(selectedThemes.typography);
|
|
587
|
+
if (selectedThemes.shape) parts.push(selectedThemes.shape);
|
|
588
|
+
if (selectedThemes.density) parts.push(selectedThemes.density);
|
|
589
|
+
if (selectedThemes.animation) parts.push(selectedThemes.animation);
|
|
590
|
+
return parts.length > 0 ? parts.join("-") : "default";
|
|
591
|
+
}
|
|
592
|
+
function validateThemeSelection(selectedThemes, themeCategories) {
|
|
593
|
+
const errors = [];
|
|
594
|
+
for (const [category, themeId] of Object.entries(selectedThemes)) {
|
|
595
|
+
if (!themeId) continue;
|
|
596
|
+
const categoryConfig = themeCategories[category];
|
|
597
|
+
if (!categoryConfig) {
|
|
598
|
+
errors.push(`Unknown category: ${category}`);
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
const theme = categoryConfig.themes[themeId];
|
|
602
|
+
if (!theme) {
|
|
603
|
+
errors.push(`Theme ${themeId} not found in category ${category}`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
valid: errors.length === 0,
|
|
608
|
+
errors
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function getDefaultThemes() {
|
|
612
|
+
return {
|
|
613
|
+
color: "white",
|
|
614
|
+
typography: "sans",
|
|
615
|
+
shape: "smooth",
|
|
616
|
+
density: "comfortable",
|
|
617
|
+
animation: "gentle"
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
function deepClone(obj) {
|
|
621
|
+
return JSON.parse(JSON.stringify(obj));
|
|
622
|
+
}
|
|
623
|
+
function isObject(item) {
|
|
624
|
+
return item && typeof item === "object" && !Array.isArray(item);
|
|
625
|
+
}
|
|
626
|
+
function deepMerge(target, source) {
|
|
627
|
+
const output = { ...target };
|
|
628
|
+
if (isObject(target) && isObject(source)) {
|
|
629
|
+
Object.keys(source).forEach((key) => {
|
|
630
|
+
if (isObject(source[key])) {
|
|
631
|
+
if (!(key in target)) {
|
|
632
|
+
Object.assign(output, { [key]: source[key] });
|
|
633
|
+
} else {
|
|
634
|
+
output[key] = deepMerge(target[key], source[key]);
|
|
635
|
+
}
|
|
636
|
+
} else {
|
|
637
|
+
Object.assign(output, { [key]: source[key] });
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
return output;
|
|
642
|
+
}
|
|
643
|
+
var tokenCache = /* @__PURE__ */ new Map();
|
|
644
|
+
async function loadTokenFile(path) {
|
|
645
|
+
if (tokenCache.has(path)) {
|
|
646
|
+
return deepClone(tokenCache.get(path));
|
|
647
|
+
}
|
|
648
|
+
try {
|
|
649
|
+
const response = await fetch(path);
|
|
650
|
+
if (!response.ok) {
|
|
651
|
+
throw new Error(`Failed to load ${path}: ${response.statusText}`);
|
|
652
|
+
}
|
|
653
|
+
const data = await response.json();
|
|
654
|
+
tokenCache.set(path, data);
|
|
655
|
+
return deepClone(data);
|
|
656
|
+
} catch (error) {
|
|
657
|
+
console.error(`Error loading token file ${path}:`, error);
|
|
658
|
+
throw error;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function resolveReferences(tokens, palette) {
|
|
662
|
+
const resolved = JSON.parse(JSON.stringify(tokens));
|
|
663
|
+
function resolveValue(value) {
|
|
664
|
+
if (typeof value !== "string") return value;
|
|
665
|
+
const match = value.match(/^\{([^}]+)\}$/);
|
|
666
|
+
if (!match) return value;
|
|
667
|
+
const path = match[1].split(".");
|
|
668
|
+
let current = { palette, ...resolved };
|
|
669
|
+
for (const key of path) {
|
|
670
|
+
if (current && typeof current === "object" && key in current) {
|
|
671
|
+
current = current[key];
|
|
672
|
+
} else {
|
|
673
|
+
console.warn(`Token reference not found: {${match[1]}}`);
|
|
674
|
+
return value;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return typeof current === "string" ? current : value;
|
|
678
|
+
}
|
|
679
|
+
function traverse(obj) {
|
|
680
|
+
for (const key in obj) {
|
|
681
|
+
if (typeof obj[key] === "object" && obj[key] !== null) {
|
|
682
|
+
traverse(obj[key]);
|
|
683
|
+
} else {
|
|
684
|
+
obj[key] = resolveValue(obj[key]);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
traverse(resolved);
|
|
689
|
+
return resolved;
|
|
690
|
+
}
|
|
691
|
+
function hexToHSL(hex) {
|
|
692
|
+
hex = hex.replace("#", "");
|
|
693
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
694
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
695
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
696
|
+
const max = Math.max(r, g, b);
|
|
697
|
+
const min = Math.min(r, g, b);
|
|
698
|
+
let h = 0;
|
|
699
|
+
let s = 0;
|
|
700
|
+
const l = (max + min) / 2;
|
|
701
|
+
if (max !== min) {
|
|
702
|
+
const d = max - min;
|
|
703
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
704
|
+
switch (max) {
|
|
705
|
+
case r:
|
|
706
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
707
|
+
break;
|
|
708
|
+
case g:
|
|
709
|
+
h = ((b - r) / d + 2) / 6;
|
|
710
|
+
break;
|
|
711
|
+
case b:
|
|
712
|
+
h = ((r - g) / d + 4) / 6;
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
h = Math.round(h * 360);
|
|
717
|
+
s = Math.round(s * 100);
|
|
718
|
+
const lPercent = Math.round(l * 100);
|
|
719
|
+
return `${h} ${s}% ${lPercent}%`;
|
|
720
|
+
}
|
|
721
|
+
function isHexColor(value) {
|
|
722
|
+
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value);
|
|
723
|
+
}
|
|
724
|
+
function flattenToCSS(tokens, prefix = "", result = {}, isColorContext = false) {
|
|
725
|
+
for (const key in tokens) {
|
|
726
|
+
const value = tokens[key];
|
|
727
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
728
|
+
const enteringColor = key === "color" && prefix === "";
|
|
729
|
+
const inColorContext = isColorContext || enteringColor;
|
|
730
|
+
if (enteringColor) {
|
|
731
|
+
flattenToCSS(value, "", result, true);
|
|
732
|
+
} else if (inColorContext) {
|
|
733
|
+
flattenToCSS(value, "", result, true);
|
|
734
|
+
} else {
|
|
735
|
+
const newPrefix = prefix ? `${prefix}-${key}` : key;
|
|
736
|
+
flattenToCSS(value, newPrefix, result, false);
|
|
737
|
+
}
|
|
738
|
+
} else {
|
|
739
|
+
let cssKey;
|
|
740
|
+
if (isColorContext || prefix === "" && key === "color") {
|
|
741
|
+
cssKey = `--${key}`;
|
|
742
|
+
} else if (prefix === "") {
|
|
743
|
+
cssKey = `--${key}`;
|
|
744
|
+
} else {
|
|
745
|
+
cssKey = `--${prefix}-${key}`;
|
|
746
|
+
}
|
|
747
|
+
let finalValue = value;
|
|
748
|
+
if (isColorContext && typeof value === "string" && isHexColor(value)) {
|
|
749
|
+
finalValue = hexToHSL(value);
|
|
750
|
+
}
|
|
751
|
+
result[cssKey] = finalValue;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return result;
|
|
755
|
+
}
|
|
756
|
+
function mapToTailwindVars(cssVars) {
|
|
757
|
+
const mapped = { ...cssVars };
|
|
758
|
+
if (cssVars["--radius-button"]) {
|
|
759
|
+
mapped["--radius"] = cssVars["--radius-button"];
|
|
760
|
+
}
|
|
761
|
+
if (cssVars["--radius-card"]) {
|
|
762
|
+
mapped["--radius-lg"] = cssVars["--radius-card"];
|
|
763
|
+
}
|
|
764
|
+
if (cssVars["--font-body"]) {
|
|
765
|
+
mapped["--font-sans"] = cssVars["--font-body"];
|
|
766
|
+
}
|
|
767
|
+
if (cssVars["--spacing-base"]) {
|
|
768
|
+
mapped["--spacing"] = cssVars["--spacing-base"];
|
|
769
|
+
} else if (cssVars["--spacing-component-md"]) {
|
|
770
|
+
mapped["--spacing"] = cssVars["--spacing-component-md"];
|
|
771
|
+
}
|
|
772
|
+
return mapped;
|
|
773
|
+
}
|
|
774
|
+
function generateCSSString(cssVars) {
|
|
775
|
+
const mappedVars = mapToTailwindVars(cssVars);
|
|
776
|
+
const vars = Object.entries(mappedVars).map(([key, value]) => ` ${key}: ${value};`).join("\n");
|
|
777
|
+
return `:root {
|
|
778
|
+
${vars}
|
|
779
|
+
}`;
|
|
780
|
+
}
|
|
781
|
+
function applyThemeCSS(css) {
|
|
782
|
+
let styleTag = document.getElementById("dynamic-theme");
|
|
783
|
+
if (!styleTag) {
|
|
784
|
+
styleTag = document.createElement("style");
|
|
785
|
+
styleTag.id = "dynamic-theme";
|
|
786
|
+
document.head.appendChild(styleTag);
|
|
787
|
+
}
|
|
788
|
+
styleTag.textContent = css;
|
|
789
|
+
}
|
|
790
|
+
async function generateAndApplyTheme(selectedThemes = {}) {
|
|
791
|
+
try {
|
|
792
|
+
const themeCategories = await getThemeCategories();
|
|
793
|
+
const validation = validateThemeSelection(selectedThemes, themeCategories);
|
|
794
|
+
if (!validation.valid) {
|
|
795
|
+
console.error("Invalid theme selection:", validation.errors);
|
|
796
|
+
throw new Error(`Invalid theme selection: ${validation.errors.join(", ")}`);
|
|
797
|
+
}
|
|
798
|
+
const base = await loadTokenFile("/tokens/base.json");
|
|
799
|
+
const palettes = await loadTokenFile("/tokens/palettes.json");
|
|
800
|
+
const palette = palettes.palette;
|
|
801
|
+
let merged = deepMerge(base, { palette });
|
|
802
|
+
const categoryOrder = Object.values(themeCategories).sort((a, b) => a.order - b.order).map((cat) => cat.name.toLowerCase());
|
|
803
|
+
for (const category of categoryOrder) {
|
|
804
|
+
if (category === "custom") continue;
|
|
805
|
+
const themeId = selectedThemes[category];
|
|
806
|
+
if (!themeId) continue;
|
|
807
|
+
const themePath = `/tokens/themes/${category}/${themeId}.json`;
|
|
808
|
+
const themeData = await loadTokenFile(themePath);
|
|
809
|
+
merged = deepMerge(merged, themeData);
|
|
810
|
+
}
|
|
811
|
+
if (selectedThemes.custom) {
|
|
812
|
+
const customPath = `/tokens/themes/custom/${selectedThemes.custom}.json`;
|
|
813
|
+
const customData = await loadTokenFile(customPath);
|
|
814
|
+
merged = deepMerge(merged, customData);
|
|
815
|
+
}
|
|
816
|
+
const resolved = resolveReferences(merged, palette);
|
|
817
|
+
const cssVars = flattenToCSS(resolved);
|
|
818
|
+
const css = generateCSSString(cssVars);
|
|
819
|
+
if (typeof document !== "undefined") {
|
|
820
|
+
applyThemeCSS(css);
|
|
821
|
+
}
|
|
822
|
+
return {
|
|
823
|
+
success: true,
|
|
824
|
+
themeName: getThemeName(selectedThemes),
|
|
825
|
+
cssVars
|
|
826
|
+
};
|
|
827
|
+
} catch (error) {
|
|
828
|
+
console.error("Error generating theme:", error);
|
|
829
|
+
throw error;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// src/themes/useTheme.tsx
|
|
834
|
+
var STORAGE_KEY = "design-system-theme";
|
|
835
|
+
function useTheme() {
|
|
836
|
+
const [selectedThemes, setSelectedThemes] = useState(getDefaultThemes());
|
|
837
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
838
|
+
const [error, setError] = useState(null);
|
|
839
|
+
const applyTheme = useCallback(async (themes) => {
|
|
840
|
+
setIsLoading(true);
|
|
841
|
+
setError(null);
|
|
842
|
+
try {
|
|
843
|
+
await generateAndApplyTheme(themes);
|
|
844
|
+
if (typeof window !== "undefined") {
|
|
845
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(themes));
|
|
846
|
+
}
|
|
847
|
+
} catch (err) {
|
|
848
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to apply theme";
|
|
849
|
+
setError(errorMessage);
|
|
850
|
+
console.error("Theme application error:", err);
|
|
851
|
+
} finally {
|
|
852
|
+
setIsLoading(false);
|
|
853
|
+
}
|
|
854
|
+
}, []);
|
|
855
|
+
useEffect(() => {
|
|
856
|
+
if (typeof window !== "undefined") {
|
|
857
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
858
|
+
if (stored) {
|
|
859
|
+
try {
|
|
860
|
+
const parsed = JSON.parse(stored);
|
|
861
|
+
setSelectedThemes(parsed);
|
|
862
|
+
applyTheme(parsed);
|
|
863
|
+
} catch {
|
|
864
|
+
const defaults = getDefaultThemes();
|
|
865
|
+
setSelectedThemes(defaults);
|
|
866
|
+
applyTheme(defaults);
|
|
867
|
+
}
|
|
868
|
+
} else {
|
|
869
|
+
const defaults = getDefaultThemes();
|
|
870
|
+
applyTheme(defaults);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}, [applyTheme]);
|
|
874
|
+
const updateTheme = useCallback(async (category, themeId) => {
|
|
875
|
+
const newThemes = {
|
|
876
|
+
...selectedThemes,
|
|
877
|
+
[category]: themeId || void 0
|
|
878
|
+
};
|
|
879
|
+
setSelectedThemes(newThemes);
|
|
880
|
+
await applyTheme(newThemes);
|
|
881
|
+
}, [selectedThemes, applyTheme]);
|
|
882
|
+
const resetToDefaults = useCallback(async () => {
|
|
883
|
+
const defaults = getDefaultThemes();
|
|
884
|
+
setSelectedThemes(defaults);
|
|
885
|
+
await applyTheme(defaults);
|
|
886
|
+
}, [applyTheme]);
|
|
887
|
+
const getAvailableThemes = useCallback(async (category) => {
|
|
888
|
+
const categories = await getThemeCategories();
|
|
889
|
+
return categories[category]?.themes || {};
|
|
890
|
+
}, []);
|
|
891
|
+
return {
|
|
892
|
+
selectedThemes,
|
|
893
|
+
updateTheme,
|
|
894
|
+
resetToDefaults,
|
|
895
|
+
isLoading,
|
|
896
|
+
error,
|
|
897
|
+
getAvailableThemes
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// src/themes/ui/ThemeToggle/useThemeToggle.ts
|
|
902
|
+
function useThemeToggle() {
|
|
903
|
+
const { selectedThemes, updateTheme, isLoading, getAvailableThemes } = useTheme();
|
|
904
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
905
|
+
const [selectedCategory, setSelectedCategory] = useState(null);
|
|
906
|
+
const [themeCategories, setThemeCategories] = useState(null);
|
|
907
|
+
const menuRef = useRef(null);
|
|
908
|
+
useEffect(() => {
|
|
909
|
+
getThemeCategories().then(setThemeCategories);
|
|
910
|
+
}, []);
|
|
911
|
+
useEffect(() => {
|
|
912
|
+
function handleClickOutside(event) {
|
|
913
|
+
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
|
914
|
+
setIsOpen(false);
|
|
915
|
+
setSelectedCategory(null);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (isOpen) {
|
|
919
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
920
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
921
|
+
}
|
|
922
|
+
}, [isOpen]);
|
|
923
|
+
const handleCategoryClick = useCallback((categoryKey) => {
|
|
924
|
+
setSelectedCategory(categoryKey);
|
|
925
|
+
}, []);
|
|
926
|
+
const handleThemeSelect = useCallback(async (category, themeId) => {
|
|
927
|
+
const currentTheme = selectedThemes[category];
|
|
928
|
+
const newTheme = currentTheme === themeId ? void 0 : themeId;
|
|
929
|
+
await updateTheme(category, newTheme);
|
|
930
|
+
}, [selectedThemes, updateTheme]);
|
|
931
|
+
const handleBack = useCallback(() => {
|
|
932
|
+
setSelectedCategory(null);
|
|
933
|
+
}, []);
|
|
934
|
+
const toggleMenu = useCallback(() => {
|
|
935
|
+
setIsOpen((prev) => {
|
|
936
|
+
if (!prev) {
|
|
937
|
+
setSelectedCategory(null);
|
|
938
|
+
}
|
|
939
|
+
return !prev;
|
|
940
|
+
});
|
|
941
|
+
}, []);
|
|
942
|
+
const categories = themeCategories ? Object.entries(themeCategories).sort(([, a], [, b]) => (a.order || 0) - (b.order || 0)) : [];
|
|
943
|
+
return {
|
|
944
|
+
selectedThemes,
|
|
945
|
+
isLoading,
|
|
946
|
+
getAvailableThemes,
|
|
947
|
+
isOpen,
|
|
948
|
+
selectedCategory,
|
|
949
|
+
themeCategories,
|
|
950
|
+
categories,
|
|
951
|
+
menuRef,
|
|
952
|
+
handleCategoryClick,
|
|
953
|
+
handleThemeSelect,
|
|
954
|
+
handleBack,
|
|
955
|
+
toggleMenu
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/themes/ui/ThemeToggle/themeToggleConfig.ts
|
|
960
|
+
var categoryIcons = {
|
|
961
|
+
color: "\u{1F3A8}",
|
|
962
|
+
typography: "\u{1F4DD}",
|
|
963
|
+
shape: "\u{1F532}",
|
|
964
|
+
density: "\u{1F4CF}",
|
|
965
|
+
animation: "\u2728"
|
|
966
|
+
};
|
|
967
|
+
var positionClasses = {
|
|
968
|
+
"bottom-right": "bottom-6 right-6",
|
|
969
|
+
"bottom-left": "bottom-6 left-6",
|
|
970
|
+
"top-right": "top-6 right-6",
|
|
971
|
+
"top-left": "top-6 left-6"
|
|
972
|
+
};
|
|
973
|
+
function getArcConfig(position) {
|
|
974
|
+
switch (position) {
|
|
975
|
+
case "bottom-right":
|
|
976
|
+
return { startAngle: -60, endAngle: -180, sweep: -150 };
|
|
977
|
+
case "bottom-left":
|
|
978
|
+
return { startAngle: -120, endAngle: 0, sweep: 150 };
|
|
979
|
+
case "top-right":
|
|
980
|
+
return { startAngle: 60, endAngle: 0, sweep: 150 };
|
|
981
|
+
case "top-left":
|
|
982
|
+
return { startAngle: 120, endAngle: 0, sweep: -150 };
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// src/themes/ui/ThemeToggle/themeToggleUtils.ts
|
|
987
|
+
function getPositionOnArc(angleDeg, radius) {
|
|
988
|
+
const rad = angleDeg * Math.PI / 180;
|
|
989
|
+
return {
|
|
990
|
+
x: Math.cos(rad) * radius,
|
|
991
|
+
y: Math.sin(rad) * radius
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
function ThemeToggle({
|
|
995
|
+
className,
|
|
996
|
+
position = "bottom-right"
|
|
997
|
+
}) {
|
|
998
|
+
const {
|
|
999
|
+
selectedThemes,
|
|
1000
|
+
isLoading,
|
|
1001
|
+
getAvailableThemes,
|
|
1002
|
+
isOpen,
|
|
1003
|
+
selectedCategory,
|
|
1004
|
+
categories,
|
|
1005
|
+
menuRef,
|
|
1006
|
+
handleCategoryClick,
|
|
1007
|
+
handleThemeSelect,
|
|
1008
|
+
handleBack,
|
|
1009
|
+
toggleMenu
|
|
1010
|
+
} = useThemeToggle();
|
|
1011
|
+
return /* @__PURE__ */ jsxs(
|
|
1012
|
+
"div",
|
|
1013
|
+
{
|
|
1014
|
+
ref: menuRef,
|
|
1015
|
+
id: "theme-toggle",
|
|
1016
|
+
className: cn("fixed z-50", positionClasses[position], className),
|
|
1017
|
+
children: [
|
|
1018
|
+
/* @__PURE__ */ jsx(
|
|
1019
|
+
"button",
|
|
1020
|
+
{
|
|
1021
|
+
onClick: toggleMenu,
|
|
1022
|
+
className: cn(
|
|
1023
|
+
"w-14 h-14 rounded-full bg-primary text-primary-foreground",
|
|
1024
|
+
"shadow-lg hover:shadow-xl",
|
|
1025
|
+
"flex items-center justify-center",
|
|
1026
|
+
"relative z-10",
|
|
1027
|
+
"transition-all duration-200",
|
|
1028
|
+
"hover:scale-110 active:scale-95",
|
|
1029
|
+
isOpen && "rotate-90"
|
|
1030
|
+
),
|
|
1031
|
+
"aria-label": "Theme settings",
|
|
1032
|
+
title: "Theme settings",
|
|
1033
|
+
children: /* @__PURE__ */ jsxs(
|
|
1034
|
+
"svg",
|
|
1035
|
+
{
|
|
1036
|
+
width: "24",
|
|
1037
|
+
height: "24",
|
|
1038
|
+
viewBox: "0 0 24 24",
|
|
1039
|
+
fill: "none",
|
|
1040
|
+
stroke: "currentColor",
|
|
1041
|
+
strokeWidth: "2",
|
|
1042
|
+
strokeLinecap: "round",
|
|
1043
|
+
strokeLinejoin: "round",
|
|
1044
|
+
children: [
|
|
1045
|
+
/* @__PURE__ */ jsx("path", { d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73 2.15l.22.38a2 2 0 0 1 0 2.73l-.22.38a2 2 0 0 0 2.15 2.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-2.15l-.22-.38a2 2 0 0 1 0-2.73l.22-.38a2 2 0 0 0-2.15-2.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" }),
|
|
1046
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" })
|
|
1047
|
+
]
|
|
1048
|
+
}
|
|
1049
|
+
)
|
|
1050
|
+
}
|
|
1051
|
+
),
|
|
1052
|
+
isOpen && /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsx("div", { className: "absolute inset-0 pointer-events-none", children: !selectedCategory ? /* @__PURE__ */ jsx(
|
|
1053
|
+
CategoryRing,
|
|
1054
|
+
{
|
|
1055
|
+
categories,
|
|
1056
|
+
onCategoryClick: handleCategoryClick,
|
|
1057
|
+
selectedThemes,
|
|
1058
|
+
position
|
|
1059
|
+
}
|
|
1060
|
+
) : /* @__PURE__ */ jsx(
|
|
1061
|
+
ThemeRingAsync,
|
|
1062
|
+
{
|
|
1063
|
+
category: selectedCategory,
|
|
1064
|
+
getAvailableThemes,
|
|
1065
|
+
selectedTheme: selectedThemes[selectedCategory],
|
|
1066
|
+
onThemeSelect: (themeId) => handleThemeSelect(selectedCategory, themeId),
|
|
1067
|
+
onBack: handleBack,
|
|
1068
|
+
isLoading,
|
|
1069
|
+
position
|
|
1070
|
+
}
|
|
1071
|
+
) }) })
|
|
1072
|
+
]
|
|
1073
|
+
}
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
function RadialWheel({
|
|
1077
|
+
items,
|
|
1078
|
+
position,
|
|
1079
|
+
radius,
|
|
1080
|
+
buttonSize,
|
|
1081
|
+
startOffset = 0.75
|
|
1082
|
+
}) {
|
|
1083
|
+
const arcConfig = getArcConfig(position);
|
|
1084
|
+
const totalItems = items.length;
|
|
1085
|
+
const angleStep = Math.abs(arcConfig.sweep) / totalItems;
|
|
1086
|
+
return /* @__PURE__ */ jsx("div", { className: "absolute inset-0", children: items.map((item, index) => {
|
|
1087
|
+
const angle = arcConfig.startAngle + angleStep * (index + startOffset) * Math.sign(arcConfig.sweep);
|
|
1088
|
+
const pos = getPositionOnArc(angle, radius);
|
|
1089
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
1090
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
1091
|
+
"button",
|
|
1092
|
+
{
|
|
1093
|
+
onClick: item.onClick,
|
|
1094
|
+
disabled: item.disabled,
|
|
1095
|
+
className: cn(
|
|
1096
|
+
"absolute rounded-full",
|
|
1097
|
+
"bg-background border-2 shadow-lg",
|
|
1098
|
+
"flex items-center justify-center text-lg",
|
|
1099
|
+
"pointer-events-auto",
|
|
1100
|
+
"transition-all duration-200",
|
|
1101
|
+
"hover:scale-110 active:scale-95",
|
|
1102
|
+
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
1103
|
+
item.isSelected ? "border-primary bg-primary text-primary-foreground" : "border-border hover:border-primary/50",
|
|
1104
|
+
item.className
|
|
1105
|
+
),
|
|
1106
|
+
style: {
|
|
1107
|
+
width: `${buttonSize}px`,
|
|
1108
|
+
height: `${buttonSize}px`,
|
|
1109
|
+
left: `${pos.x}px`,
|
|
1110
|
+
top: `${pos.y}px`
|
|
1111
|
+
},
|
|
1112
|
+
"aria-label": item.label,
|
|
1113
|
+
children: item.content
|
|
1114
|
+
}
|
|
1115
|
+
) }),
|
|
1116
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: "right", sideOffset: 8, children: item.label })
|
|
1117
|
+
] }, item.id);
|
|
1118
|
+
}) });
|
|
1119
|
+
}
|
|
1120
|
+
function CategoryRing({
|
|
1121
|
+
categories,
|
|
1122
|
+
onCategoryClick,
|
|
1123
|
+
selectedThemes,
|
|
1124
|
+
position
|
|
1125
|
+
}) {
|
|
1126
|
+
const radius = 60;
|
|
1127
|
+
const buttonSize = 40;
|
|
1128
|
+
const items = categories.map(([categoryKey, category]) => {
|
|
1129
|
+
const hasSelection = !!selectedThemes[categoryKey];
|
|
1130
|
+
return {
|
|
1131
|
+
id: categoryKey,
|
|
1132
|
+
content: categoryIcons[categoryKey] || "\u2699\uFE0F",
|
|
1133
|
+
label: category.name,
|
|
1134
|
+
onClick: () => onCategoryClick(categoryKey),
|
|
1135
|
+
isSelected: hasSelection,
|
|
1136
|
+
className: hasSelection ? "bg-primary/10" : void 0
|
|
1137
|
+
};
|
|
1138
|
+
});
|
|
1139
|
+
return /* @__PURE__ */ jsx(
|
|
1140
|
+
RadialWheel,
|
|
1141
|
+
{
|
|
1142
|
+
items,
|
|
1143
|
+
position,
|
|
1144
|
+
radius,
|
|
1145
|
+
buttonSize,
|
|
1146
|
+
startOffset: 0.5
|
|
1147
|
+
}
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
function ThemeRingAsync({
|
|
1151
|
+
category,
|
|
1152
|
+
getAvailableThemes,
|
|
1153
|
+
selectedTheme,
|
|
1154
|
+
onThemeSelect,
|
|
1155
|
+
onBack,
|
|
1156
|
+
isLoading,
|
|
1157
|
+
position
|
|
1158
|
+
}) {
|
|
1159
|
+
const [themes, setThemes] = useState({});
|
|
1160
|
+
useEffect(() => {
|
|
1161
|
+
getAvailableThemes(category).then(setThemes);
|
|
1162
|
+
}, [category, getAvailableThemes]);
|
|
1163
|
+
const themeEntries = Object.entries(themes);
|
|
1164
|
+
const radius = 65;
|
|
1165
|
+
const buttonSize = 40;
|
|
1166
|
+
const backButtonSize = 36;
|
|
1167
|
+
const arcConfig = getArcConfig(position);
|
|
1168
|
+
const backButtonPos = getPositionOnArc(arcConfig.startAngle, radius);
|
|
1169
|
+
const items = themeEntries.map(([themeId, theme]) => ({
|
|
1170
|
+
id: themeId,
|
|
1171
|
+
content: theme.icon,
|
|
1172
|
+
label: theme.name,
|
|
1173
|
+
onClick: () => !isLoading && onThemeSelect(themeId),
|
|
1174
|
+
isSelected: selectedTheme === themeId,
|
|
1175
|
+
disabled: isLoading,
|
|
1176
|
+
className: "text-base"
|
|
1177
|
+
// Smaller text for theme icons
|
|
1178
|
+
}));
|
|
1179
|
+
return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0", children: [
|
|
1180
|
+
/* @__PURE__ */ jsx(
|
|
1181
|
+
"button",
|
|
1182
|
+
{
|
|
1183
|
+
onClick: onBack,
|
|
1184
|
+
className: cn(
|
|
1185
|
+
"absolute rounded-full",
|
|
1186
|
+
"bg-muted border border-border shadow-md",
|
|
1187
|
+
"flex items-center justify-center",
|
|
1188
|
+
"pointer-events-auto",
|
|
1189
|
+
"transition-all duration-200",
|
|
1190
|
+
"hover:scale-110 active:scale-95"
|
|
1191
|
+
),
|
|
1192
|
+
style: {
|
|
1193
|
+
width: `${backButtonSize}px`,
|
|
1194
|
+
height: `${backButtonSize}px`,
|
|
1195
|
+
left: `${backButtonPos.x}px`,
|
|
1196
|
+
top: `${backButtonPos.y}px`
|
|
1197
|
+
},
|
|
1198
|
+
"aria-label": "Back",
|
|
1199
|
+
children: /* @__PURE__ */ jsxs(
|
|
1200
|
+
"svg",
|
|
1201
|
+
{
|
|
1202
|
+
width: "16",
|
|
1203
|
+
height: "16",
|
|
1204
|
+
viewBox: "0 0 24 24",
|
|
1205
|
+
fill: "none",
|
|
1206
|
+
stroke: "currentColor",
|
|
1207
|
+
strokeWidth: "2",
|
|
1208
|
+
strokeLinecap: "round",
|
|
1209
|
+
strokeLinejoin: "round",
|
|
1210
|
+
children: [
|
|
1211
|
+
/* @__PURE__ */ jsx("path", { d: "m12 19-7-7 7-7" }),
|
|
1212
|
+
/* @__PURE__ */ jsx("path", { d: "M19 12H5" })
|
|
1213
|
+
]
|
|
1214
|
+
}
|
|
1215
|
+
)
|
|
1216
|
+
}
|
|
1217
|
+
),
|
|
1218
|
+
/* @__PURE__ */ jsx(
|
|
1219
|
+
RadialWheel,
|
|
1220
|
+
{
|
|
1221
|
+
items,
|
|
1222
|
+
position,
|
|
1223
|
+
radius,
|
|
1224
|
+
buttonSize,
|
|
1225
|
+
startOffset: 1
|
|
1226
|
+
}
|
|
1227
|
+
),
|
|
1228
|
+
isLoading && /* @__PURE__ */ jsx(
|
|
1229
|
+
"div",
|
|
1230
|
+
{
|
|
1231
|
+
className: "absolute w-12 h-12 rounded-full bg-primary/20 border-2 border-primary animate-pulse pointer-events-none",
|
|
1232
|
+
style: {
|
|
1233
|
+
left: "50%",
|
|
1234
|
+
top: "50%",
|
|
1235
|
+
transform: "translate(-50%, -50%)"
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
)
|
|
1239
|
+
] });
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// src/themes/applyThemeSync.ts
|
|
1243
|
+
var STORAGE_KEY2 = "design-system-theme";
|
|
1244
|
+
function applyThemeSync() {
|
|
1245
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
let selectedThemes = getDefaultThemes();
|
|
1249
|
+
try {
|
|
1250
|
+
const stored = localStorage.getItem(STORAGE_KEY2);
|
|
1251
|
+
if (stored) {
|
|
1252
|
+
selectedThemes = JSON.parse(stored);
|
|
1253
|
+
}
|
|
1254
|
+
} catch {
|
|
1255
|
+
}
|
|
1256
|
+
try {
|
|
1257
|
+
const base = loadJSONSync("/tokens/base.json");
|
|
1258
|
+
const palettes = loadJSONSync("/tokens/palettes.json");
|
|
1259
|
+
const palette = palettes?.palette || {};
|
|
1260
|
+
let merged = deepMergeSync(base || {}, { palette });
|
|
1261
|
+
const categoryOrder = ["color", "typography", "shape", "density", "animation"];
|
|
1262
|
+
for (const category of categoryOrder) {
|
|
1263
|
+
const themeId = selectedThemes[category];
|
|
1264
|
+
if (!themeId) continue;
|
|
1265
|
+
try {
|
|
1266
|
+
const themeData = loadJSONSync(`/tokens/themes/${category}/${themeId}.json`);
|
|
1267
|
+
if (themeData) {
|
|
1268
|
+
merged = deepMergeSync(merged, themeData);
|
|
1269
|
+
}
|
|
1270
|
+
} catch {
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
if (selectedThemes.custom) {
|
|
1274
|
+
try {
|
|
1275
|
+
const customData = loadJSONSync(`/tokens/themes/custom/${selectedThemes.custom}.json`);
|
|
1276
|
+
if (customData) {
|
|
1277
|
+
merged = deepMergeSync(merged, customData);
|
|
1278
|
+
}
|
|
1279
|
+
} catch {
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
const resolved = resolveReferencesSync(merged, palette);
|
|
1283
|
+
const cssVars = flattenToCSSSync(resolved);
|
|
1284
|
+
const mappedVars = mapToTailwindVarsSync(cssVars);
|
|
1285
|
+
const css = `:root {
|
|
1286
|
+
${Object.entries(mappedVars).map(([key, value]) => ` ${key}: ${value};`).join("\n")}
|
|
1287
|
+
}`;
|
|
1288
|
+
let styleTag = document.getElementById("dynamic-theme");
|
|
1289
|
+
if (!styleTag) {
|
|
1290
|
+
styleTag = document.createElement("style");
|
|
1291
|
+
styleTag.id = "dynamic-theme";
|
|
1292
|
+
document.head.insertBefore(styleTag, document.head.firstChild);
|
|
1293
|
+
}
|
|
1294
|
+
styleTag.textContent = css;
|
|
1295
|
+
} catch (error) {
|
|
1296
|
+
console.warn("Sync theme application failed, will apply via React:", error);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
function loadJSONSync(path) {
|
|
1300
|
+
try {
|
|
1301
|
+
const xhr = new XMLHttpRequest();
|
|
1302
|
+
xhr.open("GET", path, false);
|
|
1303
|
+
xhr.send(null);
|
|
1304
|
+
if (xhr.status === 200 || xhr.status === 0) {
|
|
1305
|
+
return JSON.parse(xhr.responseText);
|
|
1306
|
+
}
|
|
1307
|
+
} catch {
|
|
1308
|
+
}
|
|
1309
|
+
return null;
|
|
1310
|
+
}
|
|
1311
|
+
function deepMergeSync(target, source) {
|
|
1312
|
+
const output = { ...target };
|
|
1313
|
+
if (isObjectSync(target) && isObjectSync(source)) {
|
|
1314
|
+
Object.keys(source).forEach((key) => {
|
|
1315
|
+
if (isObjectSync(source[key])) {
|
|
1316
|
+
if (!(key in target)) {
|
|
1317
|
+
Object.assign(output, { [key]: source[key] });
|
|
1318
|
+
} else {
|
|
1319
|
+
output[key] = deepMergeSync(target[key], source[key]);
|
|
1320
|
+
}
|
|
1321
|
+
} else {
|
|
1322
|
+
Object.assign(output, { [key]: source[key] });
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
return output;
|
|
1327
|
+
}
|
|
1328
|
+
function isObjectSync(item) {
|
|
1329
|
+
return item && typeof item === "object" && !Array.isArray(item);
|
|
1330
|
+
}
|
|
1331
|
+
function resolveReferencesSync(tokens, palette) {
|
|
1332
|
+
const resolved = JSON.parse(JSON.stringify(tokens));
|
|
1333
|
+
function resolveValue(value) {
|
|
1334
|
+
if (typeof value !== "string") return value;
|
|
1335
|
+
const match = value.match(/^\{([^}]+)\}$/);
|
|
1336
|
+
if (!match) return value;
|
|
1337
|
+
const path = match[1].split(".");
|
|
1338
|
+
let current = { palette, ...resolved };
|
|
1339
|
+
for (const key of path) {
|
|
1340
|
+
if (current && typeof current === "object" && key in current) {
|
|
1341
|
+
current = current[key];
|
|
1342
|
+
} else {
|
|
1343
|
+
return value;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
return typeof current === "string" ? current : value;
|
|
1347
|
+
}
|
|
1348
|
+
function traverse(obj) {
|
|
1349
|
+
for (const key in obj) {
|
|
1350
|
+
if (typeof obj[key] === "object" && obj[key] !== null) {
|
|
1351
|
+
traverse(obj[key]);
|
|
1352
|
+
} else {
|
|
1353
|
+
obj[key] = resolveValue(obj[key]);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
traverse(resolved);
|
|
1358
|
+
return resolved;
|
|
1359
|
+
}
|
|
1360
|
+
function hexToHSL2(hex) {
|
|
1361
|
+
hex = hex.replace("#", "");
|
|
1362
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
1363
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
1364
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
1365
|
+
const max = Math.max(r, g, b);
|
|
1366
|
+
const min = Math.min(r, g, b);
|
|
1367
|
+
let h = 0;
|
|
1368
|
+
let s = 0;
|
|
1369
|
+
const l = (max + min) / 2;
|
|
1370
|
+
if (max !== min) {
|
|
1371
|
+
const d = max - min;
|
|
1372
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
1373
|
+
switch (max) {
|
|
1374
|
+
case r:
|
|
1375
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
1376
|
+
break;
|
|
1377
|
+
case g:
|
|
1378
|
+
h = ((b - r) / d + 2) / 6;
|
|
1379
|
+
break;
|
|
1380
|
+
case b:
|
|
1381
|
+
h = ((r - g) / d + 4) / 6;
|
|
1382
|
+
break;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
h = Math.round(h * 360);
|
|
1386
|
+
s = Math.round(s * 100);
|
|
1387
|
+
const lPercent = Math.round(l * 100);
|
|
1388
|
+
return `${h} ${s}% ${lPercent}%`;
|
|
1389
|
+
}
|
|
1390
|
+
function isHexColor2(value) {
|
|
1391
|
+
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value);
|
|
1392
|
+
}
|
|
1393
|
+
function flattenToCSSSync(tokens, prefix = "", result = {}, isColorContext = false) {
|
|
1394
|
+
for (const key in tokens) {
|
|
1395
|
+
const value = tokens[key];
|
|
1396
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1397
|
+
const enteringColor = key === "color" && prefix === "";
|
|
1398
|
+
const inColorContext = isColorContext || enteringColor;
|
|
1399
|
+
if (enteringColor) {
|
|
1400
|
+
flattenToCSSSync(value, "", result, true);
|
|
1401
|
+
} else if (inColorContext) {
|
|
1402
|
+
flattenToCSSSync(value, "", result, true);
|
|
1403
|
+
} else {
|
|
1404
|
+
const newPrefix = prefix ? `${prefix}-${key}` : key;
|
|
1405
|
+
flattenToCSSSync(value, newPrefix, result, false);
|
|
1406
|
+
}
|
|
1407
|
+
} else {
|
|
1408
|
+
let cssKey;
|
|
1409
|
+
if (isColorContext || prefix === "" && key === "color") {
|
|
1410
|
+
cssKey = `--${key}`;
|
|
1411
|
+
} else if (prefix === "") {
|
|
1412
|
+
cssKey = `--${key}`;
|
|
1413
|
+
} else {
|
|
1414
|
+
cssKey = `--${prefix}-${key}`;
|
|
1415
|
+
}
|
|
1416
|
+
let finalValue = value;
|
|
1417
|
+
if (isColorContext && typeof value === "string" && isHexColor2(value)) {
|
|
1418
|
+
finalValue = hexToHSL2(value);
|
|
1419
|
+
}
|
|
1420
|
+
result[cssKey] = finalValue;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
return result;
|
|
1424
|
+
}
|
|
1425
|
+
function mapToTailwindVarsSync(cssVars) {
|
|
1426
|
+
const mapped = { ...cssVars };
|
|
1427
|
+
if (cssVars["--radius-button"]) {
|
|
1428
|
+
mapped["--radius"] = cssVars["--radius-button"];
|
|
1429
|
+
}
|
|
1430
|
+
if (cssVars["--radius-card"]) {
|
|
1431
|
+
mapped["--radius-lg"] = cssVars["--radius-card"];
|
|
1432
|
+
}
|
|
1433
|
+
if (cssVars["--font-body"]) {
|
|
1434
|
+
mapped["--font-sans"] = cssVars["--font-body"];
|
|
1435
|
+
}
|
|
1436
|
+
if (cssVars["--spacing-base"]) {
|
|
1437
|
+
mapped["--spacing"] = cssVars["--spacing-base"];
|
|
1438
|
+
} else if (cssVars["--spacing-component-md"]) {
|
|
1439
|
+
mapped["--spacing"] = cssVars["--spacing-component-md"];
|
|
1440
|
+
}
|
|
1441
|
+
return mapped;
|
|
1442
|
+
}
|
|
361
1443
|
|
|
362
|
-
export { Badge, Button, Modal, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, badgeVariants, buttonVariants };
|
|
1444
|
+
export { Badge, Button, Modal, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, ThemeToggle, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, applyThemeSync, badgeVariants, buttonVariants, getTheme, getThemeCategories, getThemeFilePath, getThemesForCategory, registerTheme, useTheme, useThemeToggle };
|
|
363
1445
|
//# sourceMappingURL=index.mjs.map
|
|
364
1446
|
//# sourceMappingURL=index.mjs.map
|