@dheme/react 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  DhemeScript: () => DhemeScript,
35
35
  ThemeActionsContext: () => ThemeActionsContext,
36
36
  ThemeDataContext: () => ThemeDataContext,
37
+ ThemeGenerator: () => ThemeGenerator,
37
38
  applyThemeCSSVariables: () => applyThemeCSSVariables,
38
39
  buildCacheKey: () => buildCacheKey,
39
40
  getBlockingScriptPayload: () => getBlockingScriptPayload,
@@ -406,33 +407,900 @@ function DhemeScript({
406
407
  });
407
408
  }
408
409
 
409
- // src/hooks/useTheme.ts
410
+ // src/components/ThemeGenerator.tsx
411
+ var import_react7 = require("react");
412
+
413
+ // src/hooks/useThemeActions.ts
410
414
  var import_react5 = require("react");
411
- function useTheme() {
412
- const context = (0, import_react5.useContext)(ThemeDataContext);
415
+ function useThemeActions() {
416
+ const context = (0, import_react5.useContext)(ThemeActionsContext);
413
417
  if (!context) {
414
- throw new Error("useTheme must be used within a <DhemeProvider>");
418
+ throw new Error("useThemeActions must be used within a <DhemeProvider>");
415
419
  }
416
420
  return context;
417
421
  }
418
422
 
419
- // src/hooks/useThemeActions.ts
423
+ // src/hooks/useDebounce.ts
420
424
  var import_react6 = require("react");
421
- function useThemeActions() {
422
- const context = (0, import_react6.useContext)(ThemeActionsContext);
425
+ function useDebounce(value, delay) {
426
+ const [debouncedValue, setDebouncedValue] = (0, import_react6.useState)(value);
427
+ (0, import_react6.useEffect)(() => {
428
+ const timer = setTimeout(() => setDebouncedValue(value), delay);
429
+ return () => clearTimeout(timer);
430
+ }, [value, delay]);
431
+ return debouncedValue;
432
+ }
433
+
434
+ // src/components/ThemeGenerator.tsx
435
+ var import_jsx_runtime = require("react/jsx-runtime");
436
+ function SparklesIcon({ className }) {
437
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className, xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
438
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" }),
439
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 3v4" }),
440
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M19 17v4" }),
441
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 5h4" }),
442
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M17 19h4" })
443
+ ] });
444
+ }
445
+ function XIcon({ className }) {
446
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className, xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
447
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18 6 6 18" }),
448
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m6 6 12 12" })
449
+ ] });
450
+ }
451
+ function PlusIcon({ className }) {
452
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className, xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
453
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 12h14" }),
454
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 5v14" })
455
+ ] });
456
+ }
457
+ function ChevronUpIcon({ className }) {
458
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className, xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m18 15-6-6-6 6" }) });
459
+ }
460
+ function RotateCcwIcon({ className }) {
461
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className, xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
462
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
463
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 3v5h5" })
464
+ ] });
465
+ }
466
+ function HexColorPicker({ color, onChange }) {
467
+ const hexToRgb = (hex) => {
468
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
469
+ return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : { r: 0, g: 0, b: 0 };
470
+ };
471
+ const rgbToHsv = (r, g, b) => {
472
+ r /= 255;
473
+ g /= 255;
474
+ b /= 255;
475
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
476
+ const d = max - min;
477
+ let h = 0;
478
+ const s = max === 0 ? 0 : d / max;
479
+ const v = max;
480
+ if (max !== min) {
481
+ switch (max) {
482
+ case r:
483
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
484
+ break;
485
+ case g:
486
+ h = ((b - r) / d + 2) / 6;
487
+ break;
488
+ case b:
489
+ h = ((r - g) / d + 4) / 6;
490
+ break;
491
+ }
492
+ }
493
+ return { h: h * 360, s: s * 100, v: v * 100 };
494
+ };
495
+ const hsvToHex = (h, s, v) => {
496
+ s /= 100;
497
+ v /= 100;
498
+ const i = Math.floor(h / 60);
499
+ const f = h / 60 - i;
500
+ const p = v * (1 - s);
501
+ const q = v * (1 - f * s);
502
+ const t = v * (1 - (1 - f) * s);
503
+ let r = 0, g = 0, b = 0;
504
+ switch (i % 6) {
505
+ case 0:
506
+ r = v;
507
+ g = t;
508
+ b = p;
509
+ break;
510
+ case 1:
511
+ r = q;
512
+ g = v;
513
+ b = p;
514
+ break;
515
+ case 2:
516
+ r = p;
517
+ g = v;
518
+ b = t;
519
+ break;
520
+ case 3:
521
+ r = p;
522
+ g = q;
523
+ b = v;
524
+ break;
525
+ case 4:
526
+ r = t;
527
+ g = p;
528
+ b = v;
529
+ break;
530
+ case 5:
531
+ r = v;
532
+ g = p;
533
+ b = q;
534
+ break;
535
+ }
536
+ return "#" + [r, g, b].map((x) => Math.round(x * 255).toString(16).padStart(2, "0")).join("");
537
+ };
538
+ const rgb = hexToRgb(color);
539
+ const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
540
+ const gradientRef = (0, import_react7.useRef)(null);
541
+ const hueRef = (0, import_react7.useRef)(null);
542
+ const isDraggingGradient = (0, import_react7.useRef)(false);
543
+ const isDraggingHue = (0, import_react7.useRef)(false);
544
+ const handleGradientPointer = (0, import_react7.useCallback)((e) => {
545
+ const rect = gradientRef.current?.getBoundingClientRect();
546
+ if (!rect) return;
547
+ const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
548
+ const y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height));
549
+ onChange(hsvToHex(hsv.h, x * 100, (1 - y) * 100));
550
+ }, [hsv.h, onChange]);
551
+ const handleHuePointer = (0, import_react7.useCallback)((e) => {
552
+ const rect = hueRef.current?.getBoundingClientRect();
553
+ if (!rect) return;
554
+ const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
555
+ onChange(hsvToHex(x * 360, hsv.s, hsv.v));
556
+ }, [hsv.s, hsv.v, onChange]);
557
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "8px", userSelect: "none" }, children: [
558
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
559
+ "div",
560
+ {
561
+ ref: gradientRef,
562
+ style: {
563
+ position: "relative",
564
+ height: "120px",
565
+ borderRadius: "6px",
566
+ cursor: "crosshair",
567
+ background: `linear-gradient(to right, #fff, hsl(${hsv.h}, 100%, 50%))`,
568
+ touchAction: "none"
569
+ },
570
+ onPointerDown: (e) => {
571
+ isDraggingGradient.current = true;
572
+ e.currentTarget.setPointerCapture(e.pointerId);
573
+ handleGradientPointer(e);
574
+ },
575
+ onPointerMove: (e) => {
576
+ if (isDraggingGradient.current) handleGradientPointer(e);
577
+ },
578
+ onPointerUp: () => {
579
+ isDraggingGradient.current = false;
580
+ },
581
+ children: [
582
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
583
+ position: "absolute",
584
+ inset: 0,
585
+ borderRadius: "6px",
586
+ background: "linear-gradient(to top, #000, transparent)"
587
+ } }),
588
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
589
+ position: "absolute",
590
+ left: `${hsv.s}%`,
591
+ top: `${100 - hsv.v}%`,
592
+ transform: "translate(-50%, -50%)",
593
+ width: "12px",
594
+ height: "12px",
595
+ borderRadius: "50%",
596
+ border: "2px solid white",
597
+ boxShadow: "0 0 0 1px rgba(0,0,0,0.3)",
598
+ pointerEvents: "none"
599
+ } })
600
+ ]
601
+ }
602
+ ),
603
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
604
+ "div",
605
+ {
606
+ ref: hueRef,
607
+ style: {
608
+ position: "relative",
609
+ height: "12px",
610
+ borderRadius: "6px",
611
+ cursor: "pointer",
612
+ background: "linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)",
613
+ touchAction: "none"
614
+ },
615
+ onPointerDown: (e) => {
616
+ isDraggingHue.current = true;
617
+ e.currentTarget.setPointerCapture(e.pointerId);
618
+ handleHuePointer(e);
619
+ },
620
+ onPointerMove: (e) => {
621
+ if (isDraggingHue.current) handleHuePointer(e);
622
+ },
623
+ onPointerUp: () => {
624
+ isDraggingHue.current = false;
625
+ },
626
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
627
+ position: "absolute",
628
+ left: `${hsv.h / 360 * 100}%`,
629
+ top: "50%",
630
+ transform: "translate(-50%, -50%)",
631
+ width: "16px",
632
+ height: "16px",
633
+ borderRadius: "50%",
634
+ border: "2px solid white",
635
+ boxShadow: "0 0 0 1px rgba(0,0,0,0.3)",
636
+ background: `hsl(${hsv.h}, 100%, 50%)`,
637
+ pointerEvents: "none"
638
+ } })
639
+ }
640
+ )
641
+ ] });
642
+ }
643
+ function SectionHeading({ children }) {
644
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
645
+ fontSize: "10px",
646
+ fontWeight: 700,
647
+ letterSpacing: "0.08em",
648
+ textTransform: "uppercase",
649
+ color: "hsl(var(--muted-foreground))",
650
+ marginBottom: "10px"
651
+ }, children });
652
+ }
653
+ function ColorRow({ label, badge, color, disabled, onColorChange, onInputChange, actionButton }) {
654
+ const [pickerOpen, setPickerOpen] = (0, import_react7.useState)(false);
655
+ const hex = color.replace("#", "").toUpperCase();
656
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: "10px" }, children: [
657
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "6px" }, children: [
658
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [
659
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "12px", fontWeight: 500, color: "hsl(var(--foreground))" }, children: label }),
660
+ badge && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: {
661
+ fontSize: "9px",
662
+ fontWeight: 600,
663
+ letterSpacing: "0.05em",
664
+ padding: "1px 5px",
665
+ borderRadius: "99px",
666
+ background: "hsl(var(--muted))",
667
+ color: "hsl(var(--muted-foreground))"
668
+ }, children: badge })
669
+ ] }),
670
+ actionButton
671
+ ] }),
672
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
673
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
674
+ "button",
675
+ {
676
+ onClick: () => !disabled && setPickerOpen((p) => !p),
677
+ style: {
678
+ width: "32px",
679
+ height: "32px",
680
+ borderRadius: "6px",
681
+ border: "1px solid hsl(var(--border))",
682
+ background: disabled ? "hsl(var(--muted))" : color,
683
+ cursor: disabled ? "not-allowed" : "pointer",
684
+ flexShrink: 0,
685
+ opacity: disabled ? 0.4 : 1,
686
+ transition: "opacity 0.15s"
687
+ }
688
+ }
689
+ ),
690
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
691
+ display: "flex",
692
+ alignItems: "center",
693
+ border: "1px solid hsl(var(--border))",
694
+ borderRadius: "6px",
695
+ overflow: "hidden",
696
+ background: disabled ? "hsl(var(--muted))" : "hsl(var(--background))",
697
+ flex: 1,
698
+ opacity: disabled ? 0.5 : 1
699
+ }, children: [
700
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { padding: "0 8px", fontSize: "12px", color: "hsl(var(--muted-foreground))", fontFamily: "monospace" }, children: "#" }),
701
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
702
+ "input",
703
+ {
704
+ type: "text",
705
+ value: hex,
706
+ disabled,
707
+ maxLength: 6,
708
+ onChange: (e) => {
709
+ const v = e.target.value.replace(/[^0-9a-fA-F]/g, "");
710
+ onInputChange(v);
711
+ },
712
+ style: {
713
+ background: "transparent",
714
+ border: "none",
715
+ outline: "none",
716
+ fontSize: "12px",
717
+ fontFamily: "monospace",
718
+ fontWeight: 500,
719
+ color: "hsl(var(--foreground))",
720
+ width: "100%",
721
+ padding: "7px 8px 7px 0",
722
+ cursor: disabled ? "not-allowed" : "text"
723
+ }
724
+ }
725
+ )
726
+ ] })
727
+ ] }),
728
+ pickerOpen && !disabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
729
+ marginTop: "8px",
730
+ padding: "10px",
731
+ background: "hsl(var(--card))",
732
+ border: "1px solid hsl(var(--border))",
733
+ borderRadius: "8px",
734
+ boxShadow: "0 4px 20px rgba(0,0,0,0.15)"
735
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HexColorPicker, { color, onChange: onColorChange }) })
736
+ ] });
737
+ }
738
+ function SliderRow({ label, value, onChange, min, max, step, display }) {
739
+ const pct = (value[0] - min) / (max - min) * 100;
740
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "10px" }, children: [
741
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "12px", color: "hsl(var(--foreground))", minWidth: "70px" }, children: label }),
742
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
743
+ "input",
744
+ {
745
+ type: "range",
746
+ min,
747
+ max,
748
+ step,
749
+ value: value[0],
750
+ onChange: (e) => onChange([Number(e.target.value)]),
751
+ style: {
752
+ flex: 1,
753
+ height: "4px",
754
+ appearance: "none",
755
+ WebkitAppearance: "none",
756
+ background: `linear-gradient(to right, hsl(var(--primary)) ${pct}%, hsl(var(--muted)) ${pct}%)`,
757
+ borderRadius: "2px",
758
+ cursor: "pointer",
759
+ outline: "none"
760
+ }
761
+ }
762
+ ),
763
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: {
764
+ fontSize: "11px",
765
+ fontFamily: "monospace",
766
+ fontWeight: 500,
767
+ color: "hsl(var(--muted-foreground))",
768
+ minWidth: "48px",
769
+ textAlign: "right"
770
+ }, children: display(value[0]) })
771
+ ] });
772
+ }
773
+ function ToggleRow({ label, checked, onChange }) {
774
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
775
+ display: "flex",
776
+ alignItems: "center",
777
+ justifyContent: "space-between",
778
+ marginBottom: "8px"
779
+ }, children: [
780
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "12px", color: "hsl(var(--foreground))" }, children: label }),
781
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
782
+ "button",
783
+ {
784
+ role: "switch",
785
+ "aria-checked": checked,
786
+ onClick: () => onChange(!checked),
787
+ style: {
788
+ width: "36px",
789
+ height: "20px",
790
+ borderRadius: "10px",
791
+ background: checked ? "hsl(var(--primary))" : "hsl(var(--muted))",
792
+ border: "none",
793
+ cursor: "pointer",
794
+ position: "relative",
795
+ transition: "background 0.2s",
796
+ flexShrink: 0
797
+ },
798
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: {
799
+ position: "absolute",
800
+ top: "2px",
801
+ left: checked ? "18px" : "2px",
802
+ width: "16px",
803
+ height: "16px",
804
+ borderRadius: "50%",
805
+ background: "white",
806
+ transition: "left 0.2s",
807
+ boxShadow: "0 1px 3px rgba(0,0,0,0.2)"
808
+ } })
809
+ }
810
+ )
811
+ ] });
812
+ }
813
+ function cn(...classes) {
814
+ return classes.filter(Boolean).join(" ");
815
+ }
816
+ function ThemeGenerator({
817
+ defaultTheme = "#4332f6",
818
+ defaultSecondaryColor = "#ab67f1",
819
+ defaultSaturation = 10,
820
+ defaultLightness = 2,
821
+ defaultRadius = 0,
822
+ position = "bottom-right",
823
+ open: controlledOpen,
824
+ onOpenChange,
825
+ labels: labelsProp,
826
+ className
827
+ }) {
828
+ const { generateTheme } = useThemeActions();
829
+ const labels = {
830
+ title: "Theme Generator",
831
+ description: "Generate complete themes from a single color. Changes apply in real time.",
832
+ baseColors: "Base Colors",
833
+ primary: "Primary",
834
+ secondary: "Secondary",
835
+ optional: "Optional",
836
+ fineTuning: "Fine Tuning",
837
+ saturation: "Saturation",
838
+ lightness: "Lightness",
839
+ borderRadius: "Border Radius",
840
+ advancedOptions: "Advanced Options",
841
+ colorfulCard: "Colorful Card",
842
+ colorfulBackground: "Colorful Background",
843
+ colorfulBorder: "Colorful Border",
844
+ reset: "Reset",
845
+ fabPrimaryLabel: "Primary",
846
+ ...labelsProp
847
+ };
848
+ const [internalOpen, setInternalOpen] = (0, import_react7.useState)(controlledOpen ?? false);
849
+ const isOpen = controlledOpen !== void 0 ? controlledOpen : internalOpen;
850
+ const setIsOpen = (0, import_react7.useCallback)((next) => {
851
+ if (controlledOpen === void 0) setInternalOpen(next);
852
+ onOpenChange?.(next);
853
+ }, [controlledOpen, onOpenChange]);
854
+ const [localPrimary, setLocalPrimary] = (0, import_react7.useState)(defaultTheme);
855
+ const [localSecondary, setLocalSecondary] = (0, import_react7.useState)(defaultSecondaryColor);
856
+ const [isSecondaryEnabled, setIsSecondaryEnabled] = (0, import_react7.useState)(false);
857
+ const [localSaturation, setLocalSaturation] = (0, import_react7.useState)([defaultSaturation]);
858
+ const [localLightness, setLocalLightness] = (0, import_react7.useState)([defaultLightness]);
859
+ const [localRadius, setLocalRadius] = (0, import_react7.useState)([defaultRadius]);
860
+ const [localCardIsColored, setLocalCardIsColored] = (0, import_react7.useState)(false);
861
+ const [localBackgroundIsColored, setLocalBackgroundIsColored] = (0, import_react7.useState)(false);
862
+ const [localBorderIsColored, setLocalBorderIsColored] = (0, import_react7.useState)(false);
863
+ const paramsRef = (0, import_react7.useRef)({
864
+ theme: localPrimary,
865
+ secondaryColor: isSecondaryEnabled ? localSecondary : void 0,
866
+ saturationAdjust: localSaturation[0],
867
+ lightnessAdjust: localLightness[0],
868
+ radius: localRadius[0],
869
+ cardIsColored: localCardIsColored,
870
+ backgroundIsColored: localBackgroundIsColored,
871
+ borderIsColored: localBorderIsColored
872
+ });
873
+ (0, import_react7.useEffect)(() => {
874
+ paramsRef.current = {
875
+ theme: localPrimary,
876
+ secondaryColor: isSecondaryEnabled ? localSecondary : void 0,
877
+ saturationAdjust: localSaturation[0],
878
+ lightnessAdjust: localLightness[0],
879
+ radius: localRadius[0],
880
+ cardIsColored: localCardIsColored,
881
+ backgroundIsColored: localBackgroundIsColored,
882
+ borderIsColored: localBorderIsColored
883
+ };
884
+ });
885
+ const debouncedPrimary = useDebounce(localPrimary, 150);
886
+ const debouncedSecondary = useDebounce(localSecondary, 150);
887
+ const debouncedSaturation = useDebounce(localSaturation[0], 200);
888
+ const debouncedLightness = useDebounce(localLightness[0], 200);
889
+ const debouncedRadius = useDebounce(localRadius[0], 200);
890
+ (0, import_react7.useEffect)(() => {
891
+ generateTheme(paramsRef.current);
892
+ }, [debouncedPrimary]);
893
+ (0, import_react7.useEffect)(() => {
894
+ if (isSecondaryEnabled) generateTheme(paramsRef.current);
895
+ }, [debouncedSecondary]);
896
+ (0, import_react7.useEffect)(() => {
897
+ generateTheme(paramsRef.current);
898
+ }, [debouncedSaturation]);
899
+ (0, import_react7.useEffect)(() => {
900
+ generateTheme(paramsRef.current);
901
+ }, [debouncedLightness]);
902
+ (0, import_react7.useEffect)(() => {
903
+ generateTheme(paramsRef.current);
904
+ }, [debouncedRadius]);
905
+ const handleEnableSecondary = () => {
906
+ setIsSecondaryEnabled(true);
907
+ generateTheme({ ...paramsRef.current, secondaryColor: localSecondary });
908
+ };
909
+ const handleDisableSecondary = () => {
910
+ setIsSecondaryEnabled(false);
911
+ generateTheme({ ...paramsRef.current, secondaryColor: void 0 });
912
+ };
913
+ const handleToggle = (key, value) => {
914
+ if (key === "cardIsColored") setLocalCardIsColored(value);
915
+ if (key === "backgroundIsColored") setLocalBackgroundIsColored(value);
916
+ if (key === "borderIsColored") setLocalBorderIsColored(value);
917
+ generateTheme({ ...paramsRef.current, [key]: value });
918
+ };
919
+ const handleReset = () => {
920
+ setLocalPrimary(defaultTheme);
921
+ setLocalSecondary(defaultSecondaryColor);
922
+ setLocalSaturation([defaultSaturation]);
923
+ setLocalLightness([defaultLightness]);
924
+ setLocalRadius([defaultRadius]);
925
+ setIsSecondaryEnabled(false);
926
+ setLocalCardIsColored(false);
927
+ setLocalBackgroundIsColored(false);
928
+ setLocalBorderIsColored(false);
929
+ generateTheme({
930
+ theme: defaultTheme,
931
+ saturationAdjust: defaultSaturation,
932
+ lightnessAdjust: defaultLightness,
933
+ radius: defaultRadius,
934
+ cardIsColored: false,
935
+ backgroundIsColored: false,
936
+ borderIsColored: false
937
+ });
938
+ };
939
+ const positionStyle = position === "bottom-left" ? { bottom: "24px", left: "24px", alignItems: "flex-start" } : { bottom: "24px", right: "24px", alignItems: "flex-end" };
940
+ const panelOrigin = position === "bottom-left" ? "bottom left" : "bottom right";
941
+ const panelPosition = position === "bottom-left" ? { bottom: 0, left: 0 } : { bottom: 0, right: 0 };
942
+ const styleId = "dheme-slider-styles";
943
+ if (typeof document !== "undefined" && !document.getElementById(styleId)) {
944
+ const style = document.createElement("style");
945
+ style.id = styleId;
946
+ style.textContent = `
947
+ input[type=range].dheme-slider::-webkit-slider-thumb {
948
+ -webkit-appearance: none;
949
+ width: 14px; height: 14px;
950
+ border-radius: 50%;
951
+ background: hsl(var(--primary, 221.2 83.2% 53.3%));
952
+ border: 2px solid white;
953
+ box-shadow: 0 0 0 1px rgba(0,0,0,0.2);
954
+ cursor: pointer;
955
+ }
956
+ input[type=range].dheme-slider::-moz-range-thumb {
957
+ width: 14px; height: 14px;
958
+ border-radius: 50%;
959
+ background: hsl(var(--primary, 221.2 83.2% 53.3%));
960
+ border: 2px solid white;
961
+ box-shadow: 0 0 0 1px rgba(0,0,0,0.2);
962
+ cursor: pointer;
963
+ }
964
+ `;
965
+ document.head.appendChild(style);
966
+ }
967
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
968
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
969
+ "div",
970
+ {
971
+ onClick: () => setIsOpen(false),
972
+ style: {
973
+ position: "fixed",
974
+ inset: 0,
975
+ background: "rgba(0,0,0,0.2)",
976
+ backdropFilter: "blur(1px)",
977
+ zIndex: 40
978
+ }
979
+ }
980
+ ),
981
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
982
+ "div",
983
+ {
984
+ className: cn(className),
985
+ style: {
986
+ position: "fixed",
987
+ zIndex: 50,
988
+ display: "flex",
989
+ flexDirection: "column",
990
+ gap: "16px",
991
+ ...positionStyle
992
+ },
993
+ children: [
994
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
995
+ "div",
996
+ {
997
+ style: {
998
+ position: "absolute",
999
+ ...panelPosition,
1000
+ width: isOpen ? "340px" : "180px",
1001
+ height: isOpen ? "auto" : "56px",
1002
+ opacity: isOpen ? 1 : 0,
1003
+ transform: isOpen ? "scale(1) translateY(0)" : "scale(0.9) translateY(32px)",
1004
+ pointerEvents: isOpen ? "auto" : "none",
1005
+ transformOrigin: panelOrigin,
1006
+ transition: "all 500ms cubic-bezier(0.32, 0.72, 0, 1)",
1007
+ borderRadius: "12px",
1008
+ border: "1px solid hsl(var(--border))",
1009
+ background: "hsl(var(--card))",
1010
+ boxShadow: "0 25px 50px rgba(0,0,0,0.15)",
1011
+ overflow: "hidden"
1012
+ },
1013
+ children: [
1014
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
1015
+ display: "flex",
1016
+ alignItems: "center",
1017
+ justifyContent: "space-between",
1018
+ padding: "14px 16px",
1019
+ borderBottom: "1px solid hsl(var(--border))",
1020
+ background: "hsl(var(--muted) / 0.3)"
1021
+ }, children: [
1022
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
1023
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SparklesIcon, { className: "dheme-sparkles" }),
1024
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "13px", fontWeight: 600, color: "hsl(var(--foreground))" }, children: labels.title })
1025
+ ] }),
1026
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1027
+ "button",
1028
+ {
1029
+ onClick: () => setIsOpen(false),
1030
+ style: {
1031
+ background: "none",
1032
+ border: "none",
1033
+ cursor: "pointer",
1034
+ padding: "2px",
1035
+ color: "hsl(var(--muted-foreground))",
1036
+ display: "flex",
1037
+ alignItems: "center",
1038
+ justifyContent: "center",
1039
+ borderRadius: "4px"
1040
+ },
1041
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XIcon, {})
1042
+ }
1043
+ )
1044
+ ] }),
1045
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
1046
+ maxHeight: "calc(100vh - 200px)",
1047
+ overflowY: "auto",
1048
+ background: "hsl(var(--background))"
1049
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "16px", display: "flex", flexDirection: "column", gap: "24px" }, children: [
1050
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: "11px", color: "hsl(var(--muted-foreground))", margin: 0, lineHeight: 1.5 }, children: labels.description }),
1051
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
1052
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionHeading, { children: labels.baseColors }),
1053
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1054
+ ColorRow,
1055
+ {
1056
+ label: labels.primary,
1057
+ color: localPrimary,
1058
+ onColorChange: setLocalPrimary,
1059
+ onInputChange: (v) => {
1060
+ if (v.length === 6) setLocalPrimary(`#${v}`);
1061
+ }
1062
+ }
1063
+ ),
1064
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1065
+ ColorRow,
1066
+ {
1067
+ label: labels.secondary,
1068
+ badge: labels.optional,
1069
+ color: localSecondary,
1070
+ disabled: !isSecondaryEnabled,
1071
+ onColorChange: (c) => {
1072
+ if (isSecondaryEnabled) setLocalSecondary(c);
1073
+ },
1074
+ onInputChange: (v) => {
1075
+ if (isSecondaryEnabled && v.length === 6) setLocalSecondary(`#${v}`);
1076
+ },
1077
+ actionButton: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1078
+ "button",
1079
+ {
1080
+ onClick: isSecondaryEnabled ? handleDisableSecondary : handleEnableSecondary,
1081
+ style: {
1082
+ background: "none",
1083
+ border: "none",
1084
+ cursor: "pointer",
1085
+ padding: "2px",
1086
+ color: "hsl(var(--muted-foreground))",
1087
+ display: "flex",
1088
+ alignItems: "center",
1089
+ justifyContent: "center",
1090
+ borderRadius: "4px"
1091
+ },
1092
+ children: isSecondaryEnabled ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, {})
1093
+ }
1094
+ )
1095
+ }
1096
+ )
1097
+ ] }),
1098
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
1099
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionHeading, { children: labels.fineTuning }),
1100
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1101
+ SliderRow,
1102
+ {
1103
+ label: labels.saturation,
1104
+ value: localSaturation,
1105
+ onChange: setLocalSaturation,
1106
+ min: -100,
1107
+ max: 100,
1108
+ step: 1,
1109
+ display: (v) => `${v > 0 ? "+" : ""}${v}%`
1110
+ }
1111
+ ),
1112
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1113
+ SliderRow,
1114
+ {
1115
+ label: labels.lightness,
1116
+ value: localLightness,
1117
+ onChange: setLocalLightness,
1118
+ min: -100,
1119
+ max: 100,
1120
+ step: 1,
1121
+ display: (v) => `${v > 0 ? "+" : ""}${v}%`
1122
+ }
1123
+ ),
1124
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1125
+ SliderRow,
1126
+ {
1127
+ label: labels.borderRadius,
1128
+ value: localRadius,
1129
+ onChange: setLocalRadius,
1130
+ min: 0,
1131
+ max: 2,
1132
+ step: 0.1,
1133
+ display: (v) => `${v.toFixed(1)}rem`
1134
+ }
1135
+ )
1136
+ ] }),
1137
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
1138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionHeading, { children: labels.advancedOptions }),
1139
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1140
+ ToggleRow,
1141
+ {
1142
+ label: labels.colorfulCard,
1143
+ checked: localCardIsColored,
1144
+ onChange: (v) => handleToggle("cardIsColored", v)
1145
+ }
1146
+ ),
1147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1148
+ ToggleRow,
1149
+ {
1150
+ label: labels.colorfulBackground,
1151
+ checked: localBackgroundIsColored,
1152
+ onChange: (v) => handleToggle("backgroundIsColored", v)
1153
+ }
1154
+ ),
1155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1156
+ ToggleRow,
1157
+ {
1158
+ label: labels.colorfulBorder,
1159
+ checked: localBorderIsColored,
1160
+ onChange: (v) => handleToggle("borderIsColored", v)
1161
+ }
1162
+ )
1163
+ ] })
1164
+ ] }) }),
1165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
1166
+ padding: "10px 12px",
1167
+ borderTop: "1px solid hsl(var(--border))",
1168
+ background: "hsl(var(--muted) / 0.2)"
1169
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1170
+ "button",
1171
+ {
1172
+ onClick: handleReset,
1173
+ style: {
1174
+ width: "100%",
1175
+ height: "32px",
1176
+ background: "none",
1177
+ border: "1px solid hsl(var(--border))",
1178
+ borderRadius: "6px",
1179
+ cursor: "pointer",
1180
+ display: "flex",
1181
+ alignItems: "center",
1182
+ justifyContent: "center",
1183
+ gap: "6px",
1184
+ fontSize: "12px",
1185
+ color: "hsl(var(--muted-foreground))",
1186
+ transition: "background 0.15s"
1187
+ },
1188
+ onMouseEnter: (e) => {
1189
+ e.currentTarget.style.background = "hsl(var(--muted))";
1190
+ },
1191
+ onMouseLeave: (e) => {
1192
+ e.currentTarget.style.background = "none";
1193
+ },
1194
+ children: [
1195
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RotateCcwIcon, {}),
1196
+ labels.reset
1197
+ ]
1198
+ }
1199
+ ) })
1200
+ ]
1201
+ }
1202
+ ),
1203
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1204
+ "div",
1205
+ {
1206
+ onClick: () => setIsOpen(true),
1207
+ style: {
1208
+ width: isOpen ? "56px" : "180px",
1209
+ height: "56px",
1210
+ borderRadius: "99px",
1211
+ border: "1px solid hsl(var(--border))",
1212
+ background: "hsl(var(--card))",
1213
+ boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
1214
+ cursor: isOpen ? "default" : "pointer",
1215
+ position: "relative",
1216
+ overflow: "hidden",
1217
+ opacity: isOpen ? 0 : 1,
1218
+ transform: isOpen ? "scale(0.5) translateY(16px)" : "scale(1) translateY(0)",
1219
+ pointerEvents: isOpen ? "none" : "auto",
1220
+ transition: "all 500ms cubic-bezier(0.32, 0.72, 0, 1)"
1221
+ },
1222
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
1223
+ position: "absolute",
1224
+ inset: 0,
1225
+ display: "flex",
1226
+ alignItems: "center",
1227
+ justifyContent: "space-between",
1228
+ padding: "0 8px 0 12px"
1229
+ }, children: [
1230
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
1231
+ width: "32px",
1232
+ height: "32px",
1233
+ borderRadius: "50%",
1234
+ background: localPrimary,
1235
+ border: "1px solid hsl(var(--border))",
1236
+ boxShadow: "0 0 0 2px hsl(var(--background))",
1237
+ flexShrink: 0
1238
+ } }),
1239
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "2px", flex: 1, padding: "0 8px" }, children: [
1240
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: {
1241
+ fontSize: "10px",
1242
+ fontWeight: 700,
1243
+ letterSpacing: "0.08em",
1244
+ textTransform: "uppercase",
1245
+ color: "hsl(var(--foreground))"
1246
+ }, children: labels.fabPrimaryLabel }),
1247
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
1248
+ display: "flex",
1249
+ gap: "8px",
1250
+ fontSize: "10px",
1251
+ fontFamily: "monospace",
1252
+ color: "hsl(var(--muted-foreground))"
1253
+ }, children: [
1254
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1255
+ "S:",
1256
+ localSaturation[0] > 0 ? "+" : "",
1257
+ localSaturation[0],
1258
+ "%"
1259
+ ] }),
1260
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1261
+ "L:",
1262
+ localLightness[0] > 0 ? "+" : "",
1263
+ localLightness[0],
1264
+ "%"
1265
+ ] })
1266
+ ] })
1267
+ ] }),
1268
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
1269
+ width: "32px",
1270
+ height: "32px",
1271
+ display: "flex",
1272
+ alignItems: "center",
1273
+ justifyContent: "center",
1274
+ borderRadius: "50%",
1275
+ background: "hsl(var(--muted) / 0.5)",
1276
+ flexShrink: 0
1277
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronUpIcon, {}) })
1278
+ ] })
1279
+ }
1280
+ )
1281
+ ]
1282
+ }
1283
+ )
1284
+ ] });
1285
+ }
1286
+
1287
+ // src/hooks/useTheme.ts
1288
+ var import_react8 = require("react");
1289
+ function useTheme() {
1290
+ const context = (0, import_react8.useContext)(ThemeDataContext);
423
1291
  if (!context) {
424
- throw new Error("useThemeActions must be used within a <DhemeProvider>");
1292
+ throw new Error("useTheme must be used within a <DhemeProvider>");
425
1293
  }
426
1294
  return context;
427
1295
  }
428
1296
 
429
1297
  // src/hooks/useGenerateTheme.ts
430
- var import_react7 = require("react");
1298
+ var import_react9 = require("react");
431
1299
  function useGenerateTheme() {
432
1300
  const { generateTheme: generate } = useThemeActions();
433
- const [isGenerating, setIsGenerating] = (0, import_react7.useState)(false);
434
- const [error, setError] = (0, import_react7.useState)(null);
435
- const generateTheme = (0, import_react7.useCallback)(
1301
+ const [isGenerating, setIsGenerating] = (0, import_react9.useState)(false);
1302
+ const [error, setError] = (0, import_react9.useState)(null);
1303
+ const generateTheme = (0, import_react9.useCallback)(
436
1304
  async (params) => {
437
1305
  setIsGenerating(true);
438
1306
  setError(null);
@@ -462,6 +1330,7 @@ function useDhemeClient() {
462
1330
  DhemeScript,
463
1331
  ThemeActionsContext,
464
1332
  ThemeDataContext,
1333
+ ThemeGenerator,
465
1334
  applyThemeCSSVariables,
466
1335
  buildCacheKey,
467
1336
  getBlockingScriptPayload,