canvas-ui-sdk 0.2.0 → 0.3.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.
package/dist/index.js CHANGED
@@ -165,7 +165,8 @@ var sizeStyles = {
165
165
  fontSize: "var(--btn-mini-font-size)",
166
166
  borderRadius: "var(--btn-mini-radius)",
167
167
  fontWeight: "var(--btn-mini-font-weight)",
168
- letterSpacing: "var(--btn-mini-letter-spacing)"
168
+ letterSpacing: "var(--btn-mini-letter-spacing)",
169
+ fontFamily: "var(--btn-mini-font-family, var(--typo-button-font, var(--typo-global-font)))"
169
170
  },
170
171
  sm: {
171
172
  height: "var(--btn-small-height)",
@@ -174,7 +175,8 @@ var sizeStyles = {
174
175
  fontSize: "var(--btn-small-font-size)",
175
176
  borderRadius: "var(--btn-small-radius)",
176
177
  fontWeight: "var(--btn-small-font-weight)",
177
- letterSpacing: "var(--btn-small-letter-spacing)"
178
+ letterSpacing: "var(--btn-small-letter-spacing)",
179
+ fontFamily: "var(--btn-small-font-family, var(--typo-button-font, var(--typo-global-font)))"
178
180
  },
179
181
  default: {
180
182
  height: "var(--btn-standard-height)",
@@ -183,7 +185,8 @@ var sizeStyles = {
183
185
  fontSize: "var(--btn-standard-font-size)",
184
186
  borderRadius: "var(--btn-standard-radius)",
185
187
  fontWeight: "var(--btn-standard-font-weight)",
186
- letterSpacing: "var(--btn-standard-letter-spacing)"
188
+ letterSpacing: "var(--btn-standard-letter-spacing)",
189
+ fontFamily: "var(--btn-standard-font-family, var(--typo-button-font, var(--typo-global-font)))"
187
190
  },
188
191
  lg: {
189
192
  height: "var(--btn-large-height)",
@@ -192,7 +195,8 @@ var sizeStyles = {
192
195
  fontSize: "var(--btn-large-font-size)",
193
196
  borderRadius: "var(--btn-large-radius)",
194
197
  fontWeight: "var(--btn-large-font-weight)",
195
- letterSpacing: "var(--btn-large-letter-spacing)"
198
+ letterSpacing: "var(--btn-large-letter-spacing)",
199
+ fontFamily: "var(--btn-large-font-family, var(--typo-button-font, var(--typo-global-font)))"
196
200
  }
197
201
  };
198
202
  var canvasVariants = ["primary", "primary-outline", "primary-neutral", "neutral", "neutral-delete", "delete"];
@@ -3468,10 +3472,15 @@ var ThemeContext = createContext({
3468
3472
  var PreviewBrandingContext = createContext({
3469
3473
  previewBranding: null,
3470
3474
  setPreviewBranding: () => {
3475
+ },
3476
+ previewImages: null,
3477
+ setPreviewImages: () => {
3471
3478
  }
3472
3479
  });
3473
3480
  function useThemeImages() {
3474
- return useContext(ThemeContext).images;
3481
+ const { images } = useContext(ThemeContext);
3482
+ const { previewImages } = useContext(PreviewBrandingContext);
3483
+ return previewImages ?? images;
3475
3484
  }
3476
3485
  function useThemeBranding() {
3477
3486
  const { branding, isMounted } = useContext(ThemeContext);
@@ -3491,6 +3500,7 @@ function ThemeProvider({
3491
3500
  brandAssets = []
3492
3501
  }) {
3493
3502
  const [previewBranding, setPreviewBranding] = useState(null);
3503
+ const [previewImages, setPreviewImages] = useState(null);
3494
3504
  const contextValue = {
3495
3505
  images,
3496
3506
  branding,
@@ -3499,7 +3509,9 @@ function ThemeProvider({
3499
3509
  };
3500
3510
  const previewBrandingValue = {
3501
3511
  previewBranding,
3502
- setPreviewBranding
3512
+ setPreviewBranding,
3513
+ previewImages,
3514
+ setPreviewImages
3503
3515
  };
3504
3516
  return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(PreviewBrandingContext.Provider, { value: previewBrandingValue, children }) });
3505
3517
  }
@@ -4552,19 +4564,24 @@ function Sidebar2({
4552
4564
  }
4553
4565
 
4554
4566
  // src/lib/css-variable-sync.ts
4567
+ function getStyleTarget() {
4568
+ return document.body ?? document.documentElement;
4569
+ }
4555
4570
  function setCSSVariable(name, value) {
4556
- document.documentElement.style.setProperty(name, value);
4571
+ getStyleTarget().style.setProperty(name, value);
4557
4572
  broadcastCSSVariables({ [name]: value });
4558
4573
  }
4559
4574
  function setCSSVariables(variables) {
4575
+ const target = getStyleTarget();
4560
4576
  Object.entries(variables).forEach(([name, value]) => {
4561
- document.documentElement.style.setProperty(name, value);
4577
+ target.style.setProperty(name, value);
4562
4578
  });
4563
4579
  broadcastCSSVariables(variables);
4564
4580
  }
4565
4581
  function removeCSSVariables(names) {
4582
+ const target = getStyleTarget();
4566
4583
  names.forEach((name) => {
4567
- document.documentElement.style.removeProperty(name);
4584
+ target.style.removeProperty(name);
4568
4585
  });
4569
4586
  const iframes = document.querySelectorAll("iframe");
4570
4587
  iframes.forEach((iframe) => {
@@ -24222,6 +24239,86 @@ function isVariableReference(value) {
24222
24239
  return value?.startsWith("var(") ?? false;
24223
24240
  }
24224
24241
 
24242
+ // src/components/theme-drawer/components/font-config.ts
24243
+ var googleFontFamilies = {
24244
+ // Sans-Serif
24245
+ Inter: "Inter",
24246
+ Geist: "Geist",
24247
+ "Plus Jakarta Sans": "Plus+Jakarta+Sans",
24248
+ Poppins: "Poppins",
24249
+ "IBM Plex Sans": "IBM+Plex+Sans",
24250
+ Roboto: "Roboto",
24251
+ "Open Sans": "Open+Sans",
24252
+ "Noto Sans": "Noto+Sans",
24253
+ Montserrat: "Montserrat",
24254
+ Lato: "Lato",
24255
+ Raleway: "Raleway",
24256
+ "DM Sans": "DM+Sans",
24257
+ "Work Sans": "Work+Sans",
24258
+ Manrope: "Manrope",
24259
+ Outfit: "Outfit",
24260
+ "Source Sans 3": "Source+Sans+3",
24261
+ // Serif
24262
+ "Playfair Display": "Playfair+Display",
24263
+ Merriweather: "Merriweather",
24264
+ "Noto Serif": "Noto+Serif",
24265
+ "Libre Baskerville": "Libre+Baskerville",
24266
+ // Monospace
24267
+ "IBM Plex Mono": "IBM+Plex+Mono",
24268
+ "Courier Prime": "Courier+Prime",
24269
+ "Geist Mono": "Geist+Mono"
24270
+ };
24271
+ var loadedFonts = /* @__PURE__ */ new Set();
24272
+ function loadGoogleFont(fontName) {
24273
+ if (loadedFonts.has(fontName)) return;
24274
+ const fontFamily = googleFontFamilies[fontName];
24275
+ if (!fontFamily) return;
24276
+ const link = document.createElement("link");
24277
+ link.rel = "stylesheet";
24278
+ link.href = `https://fonts.googleapis.com/css2?family=${fontFamily}:wght@400;500;600;700&display=swap`;
24279
+ document.head.appendChild(link);
24280
+ loadedFonts.add(fontName);
24281
+ }
24282
+ var fontsPreloaded = false;
24283
+ function preloadAllFonts() {
24284
+ if (fontsPreloaded) return;
24285
+ Object.keys(googleFontFamilies).forEach((fontName) => {
24286
+ loadGoogleFont(fontName);
24287
+ });
24288
+ fontsPreloaded = true;
24289
+ }
24290
+ var fontOptions = [
24291
+ { value: "Courier Prime", label: "Courier Prime" },
24292
+ { value: "DM Sans", label: "DM Sans" },
24293
+ { value: "Geist", label: "Geist" },
24294
+ { value: "Geist Mono", label: "Geist Mono" },
24295
+ { value: "IBM Plex Mono", label: "IBM Plex Mono" },
24296
+ { value: "IBM Plex Sans", label: "IBM Plex Sans" },
24297
+ { value: "Inter", label: "Inter" },
24298
+ { value: "Lato", label: "Lato" },
24299
+ { value: "Libre Baskerville", label: "Libre Baskerville" },
24300
+ { value: "Manrope", label: "Manrope" },
24301
+ { value: "Merriweather", label: "Merriweather" },
24302
+ { value: "Montserrat", label: "Montserrat" },
24303
+ { value: "Noto Sans", label: "Noto Sans" },
24304
+ { value: "Noto Serif", label: "Noto Serif" },
24305
+ { value: "Open Sans", label: "Open Sans" },
24306
+ { value: "Outfit", label: "Outfit" },
24307
+ { value: "Playfair Display", label: "Playfair Display" },
24308
+ { value: "Plus Jakarta Sans", label: "Plus Jakarta Sans" },
24309
+ { value: "Poppins", label: "Poppins" },
24310
+ { value: "Raleway", label: "Raleway" },
24311
+ { value: "Roboto", label: "Roboto" },
24312
+ { value: "Source Sans 3", label: "Source Sans 3" },
24313
+ { value: "Work Sans", label: "Work Sans" }
24314
+ ];
24315
+ var fontWeightOptions = [
24316
+ { value: "400", label: "Regular (400)" },
24317
+ { value: "500", label: "Medium (500)" },
24318
+ { value: "600", label: "Semibold (600)" },
24319
+ { value: "700", label: "Bold (700)" }
24320
+ ];
24321
+
24225
24322
  // src/components/theme-drawer/hooks/use-theme-state.ts
24226
24323
  var FONT_VARS = [
24227
24324
  "--typo-h1-font",
@@ -24267,22 +24364,72 @@ var brandColorVars = [
24267
24364
  "--canvas-sidebar-dark-border",
24268
24365
  "--canvas-border-input-focus"
24269
24366
  ];
24367
+ function isEqual(a, b) {
24368
+ if (a === b) return true;
24369
+ if (a == null || b == null) return false;
24370
+ if (typeof a !== typeof b) return false;
24371
+ if (Array.isArray(a) && Array.isArray(b)) {
24372
+ if (a.length !== b.length) return false;
24373
+ return a.every((v, i) => isEqual(v, b[i]));
24374
+ }
24375
+ if (typeof a === "object" && typeof b === "object") {
24376
+ const aObj = a;
24377
+ const bObj = b;
24378
+ const aKeys = Object.keys(aObj);
24379
+ const bKeys = Object.keys(bObj);
24380
+ if (aKeys.length !== bKeys.length) return false;
24381
+ return aKeys.every((k) => isEqual(aObj[k], bObj[k]));
24382
+ }
24383
+ return false;
24384
+ }
24385
+ var FONT_VAR_PREFIXES = ["--typo-", "--btn-"];
24386
+ var FONT_VAR_SUFFIXES = ["-font", "-font-family"];
24387
+ function loadFontsFromOverrides(overrides) {
24388
+ for (const [key, value] of Object.entries(overrides)) {
24389
+ if (!value) continue;
24390
+ const isFontVar = FONT_VAR_PREFIXES.some((p) => key.startsWith(p)) && FONT_VAR_SUFFIXES.some((s) => key.endsWith(s));
24391
+ if (isFontVar) {
24392
+ const fontName = value.replace(/^["']|["']$/g, "").split(",")[0].trim();
24393
+ if (fontName) loadGoogleFont(fontName);
24394
+ }
24395
+ }
24396
+ }
24270
24397
  function useThemeState(options) {
24271
24398
  const {
24272
24399
  storageKey = "canvas-ui-theme",
24273
24400
  initialOverrides,
24401
+ initialBranding,
24402
+ initialImages,
24403
+ initialCustomButtonStyles,
24274
24404
  onThemeChange,
24275
24405
  onBrandingChange,
24276
- onImagesChange
24406
+ onImagesChange,
24407
+ onSave
24277
24408
  } = options ?? {};
24409
+ const [savedOverrides, setSavedOverrides] = useState(() => {
24410
+ return initialOverrides ?? {};
24411
+ });
24412
+ const [savedBranding, setSavedBranding] = useState(
24413
+ () => initialBranding ?? { ...defaultBranding }
24414
+ );
24415
+ const [savedImages, setSavedImages] = useState(
24416
+ () => initialImages ?? { ...defaultImages }
24417
+ );
24418
+ const [savedCustomButtonStyles, setSavedCustomButtonStyles] = useState(
24419
+ () => initialCustomButtonStyles ?? [...defaultCustomButtonStyles]
24420
+ );
24278
24421
  const [overrides, setOverrides] = useState(() => {
24279
24422
  return initialOverrides ?? {};
24280
24423
  });
24281
- const [branding, setBrandingState] = useState({ ...defaultBranding });
24282
- const [images, setImagesState] = useState({ ...defaultImages });
24283
- const [customButtonStyles, setCustomButtonStyles] = useState([
24284
- ...defaultCustomButtonStyles
24285
- ]);
24424
+ const [branding, setBrandingState] = useState(
24425
+ () => initialBranding ?? { ...defaultBranding }
24426
+ );
24427
+ const [images, setImagesState] = useState(
24428
+ () => initialImages ?? { ...defaultImages }
24429
+ );
24430
+ const [customButtonStyles, setCustomButtonStyles] = useState(
24431
+ () => initialCustomButtonStyles ?? [...defaultCustomButtonStyles]
24432
+ );
24286
24433
  const [brandHue, setBrandHueState] = useState(217);
24287
24434
  const [brandVibrancy, setBrandVibrancyState] = useState(1);
24288
24435
  const [syncRelatedColors, setSyncRelatedColorsState] = useState(true);
@@ -24292,7 +24439,9 @@ function useThemeState(options) {
24292
24439
  onBrandingChangeRef.current = onBrandingChange;
24293
24440
  const onImagesChangeRef = useRef(onImagesChange);
24294
24441
  onImagesChangeRef.current = onImagesChange;
24295
- const persistState = useCallback(
24442
+ const onSaveRef = useRef(onSave);
24443
+ onSaveRef.current = onSave;
24444
+ const persistToStorage = useCallback(
24296
24445
  (ovr, brd, img, cbs) => {
24297
24446
  try {
24298
24447
  localStorage.setItem(
@@ -24306,21 +24455,31 @@ function useThemeState(options) {
24306
24455
  );
24307
24456
  useEffect(() => {
24308
24457
  try {
24458
+ if (initialOverrides && Object.keys(initialOverrides).length > 0) {
24459
+ setCSSVariables(initialOverrides);
24460
+ loadFontsFromOverrides(initialOverrides);
24461
+ return;
24462
+ }
24309
24463
  const stored = localStorage.getItem(storageKey);
24310
24464
  if (stored) {
24311
24465
  const parsed = importJSON(stored);
24312
24466
  if (parsed) {
24313
24467
  if (parsed.overrides) {
24468
+ setSavedOverrides(parsed.overrides);
24314
24469
  setOverrides(parsed.overrides);
24315
24470
  setCSSVariables(parsed.overrides);
24471
+ loadFontsFromOverrides(parsed.overrides);
24316
24472
  }
24317
24473
  if (parsed.branding) {
24474
+ setSavedBranding(parsed.branding);
24318
24475
  setBrandingState(parsed.branding);
24319
24476
  }
24320
24477
  if (parsed.images) {
24478
+ setSavedImages(parsed.images);
24321
24479
  setImagesState(parsed.images);
24322
24480
  }
24323
24481
  if (parsed.customButtonStyles) {
24482
+ setSavedCustomButtonStyles(parsed.customButtonStyles);
24324
24483
  setCustomButtonStyles(parsed.customButtonStyles);
24325
24484
  }
24326
24485
  if (parsed.overrides?.["--canvas-primary"]) {
@@ -24328,8 +24487,6 @@ function useThemeState(options) {
24328
24487
  setBrandHueState(hsl.h);
24329
24488
  }
24330
24489
  }
24331
- } else if (initialOverrides) {
24332
- setCSSVariables(initialOverrides);
24333
24490
  }
24334
24491
  } catch {
24335
24492
  }
@@ -24338,13 +24495,12 @@ function useThemeState(options) {
24338
24495
  (name, value) => {
24339
24496
  setOverrides((prev) => {
24340
24497
  const next = { ...prev, [name]: value };
24341
- persistState(next, void 0, void 0, void 0);
24342
24498
  setCSSVariables({ [name]: value });
24343
24499
  onThemeChangeRef.current?.(next);
24344
24500
  return next;
24345
24501
  });
24346
24502
  },
24347
- [persistState]
24503
+ []
24348
24504
  );
24349
24505
  const resetCategory = useCallback(
24350
24506
  (prefix) => {
@@ -24360,7 +24516,6 @@ function useThemeState(options) {
24360
24516
  next[key] = value;
24361
24517
  }
24362
24518
  }
24363
- persistState(next);
24364
24519
  const fontVarsToRemove = FONT_VARS.filter((v) => v.startsWith(prefix));
24365
24520
  if (fontVarsToRemove.length > 0) {
24366
24521
  removeCSSVariables(fontVarsToRemove);
@@ -24372,8 +24527,60 @@ function useThemeState(options) {
24372
24527
  return next;
24373
24528
  });
24374
24529
  },
24375
- [persistState]
24530
+ []
24376
24531
  );
24532
+ const save = useCallback(() => {
24533
+ setOverrides((currentOverrides) => {
24534
+ setBrandingState((currentBranding) => {
24535
+ setImagesState((currentImages) => {
24536
+ setCustomButtonStyles((currentCbs) => {
24537
+ setSavedOverrides(currentOverrides);
24538
+ setSavedBranding(currentBranding);
24539
+ setSavedImages(currentImages);
24540
+ setSavedCustomButtonStyles(currentCbs);
24541
+ persistToStorage(currentOverrides, currentBranding, currentImages, currentCbs);
24542
+ onSaveRef.current?.({
24543
+ overrides: currentOverrides,
24544
+ branding: currentBranding,
24545
+ images: currentImages,
24546
+ customButtonStyles: currentCbs
24547
+ });
24548
+ return currentCbs;
24549
+ });
24550
+ return currentImages;
24551
+ });
24552
+ return currentBranding;
24553
+ });
24554
+ return currentOverrides;
24555
+ });
24556
+ }, [persistToStorage]);
24557
+ const discard = useCallback(() => {
24558
+ setSavedOverrides((currentSaved) => {
24559
+ setOverrides(currentSaved);
24560
+ removeCSSVariables(FONT_VARS);
24561
+ setCSSVariables({ ...allDefaults, ...currentSaved });
24562
+ onThemeChangeRef.current?.(currentSaved);
24563
+ if (currentSaved["--canvas-primary"]) {
24564
+ const hsl = hexToHsl2(currentSaved["--canvas-primary"]);
24565
+ setBrandHueState(hsl.h);
24566
+ }
24567
+ return currentSaved;
24568
+ });
24569
+ setSavedBranding((currentSaved) => {
24570
+ setBrandingState(currentSaved);
24571
+ onBrandingChangeRef.current?.(currentSaved);
24572
+ return currentSaved;
24573
+ });
24574
+ setSavedImages((currentSaved) => {
24575
+ setImagesState(currentSaved);
24576
+ onImagesChangeRef.current?.(currentSaved);
24577
+ return currentSaved;
24578
+ });
24579
+ setSavedCustomButtonStyles((currentSaved) => {
24580
+ setCustomButtonStyles(currentSaved);
24581
+ return currentSaved;
24582
+ });
24583
+ }, []);
24377
24584
  const resetAll = useCallback(() => {
24378
24585
  setOverrides({});
24379
24586
  setBrandingState({ ...defaultBranding });
@@ -24381,6 +24588,10 @@ function useThemeState(options) {
24381
24588
  setCustomButtonStyles([...defaultCustomButtonStyles]);
24382
24589
  setBrandHueState(217);
24383
24590
  setBrandVibrancyState(1);
24591
+ setSavedOverrides({});
24592
+ setSavedBranding({ ...defaultBranding });
24593
+ setSavedImages({ ...defaultImages });
24594
+ setSavedCustomButtonStyles([...defaultCustomButtonStyles]);
24384
24595
  try {
24385
24596
  localStorage.removeItem(storageKey);
24386
24597
  } catch {
@@ -24398,60 +24609,47 @@ function useThemeState(options) {
24398
24609
  if (parsed.branding) setBrandingState(parsed.branding);
24399
24610
  if (parsed.images) setImagesState(parsed.images);
24400
24611
  if (parsed.customButtonStyles) setCustomButtonStyles(parsed.customButtonStyles);
24401
- persistState(ovr, parsed.branding, parsed.images, parsed.customButtonStyles);
24402
24612
  removeCSSVariables(FONT_VARS);
24403
24613
  setCSSVariables({ ...allDefaults, ...ovr });
24404
24614
  onThemeChangeRef.current?.(ovr);
24405
24615
  return true;
24406
24616
  },
24407
- [persistState]
24617
+ []
24408
24618
  );
24409
24619
  const setBranding = useCallback(
24410
24620
  (updates) => {
24411
24621
  setBrandingState((prev) => {
24412
24622
  const next = { ...prev, ...updates };
24413
- setOverrides((ovr) => {
24414
- persistState(ovr, next);
24415
- return ovr;
24416
- });
24417
24623
  onBrandingChangeRef.current?.(next);
24418
24624
  return next;
24419
24625
  });
24420
24626
  },
24421
- [persistState]
24627
+ []
24422
24628
  );
24423
24629
  const setImage = useCallback(
24424
24630
  (key, url) => {
24425
24631
  setImagesState((prev) => {
24426
24632
  const next = { ...prev, [key]: url };
24427
- setOverrides((ovr) => {
24428
- persistState(ovr, void 0, next);
24429
- return ovr;
24430
- });
24431
24633
  onImagesChangeRef.current?.(next);
24432
24634
  return next;
24433
24635
  });
24434
24636
  },
24435
- [persistState]
24637
+ []
24436
24638
  );
24437
24639
  const deleteImage = useCallback(
24438
24640
  (key) => {
24439
24641
  setImagesState((prev) => {
24440
24642
  const next = { ...prev, [key]: "" };
24441
- setOverrides((ovr) => {
24442
- persistState(ovr, void 0, next);
24443
- return ovr;
24444
- });
24445
24643
  onImagesChangeRef.current?.(next);
24446
24644
  return next;
24447
24645
  });
24448
24646
  },
24449
- [persistState]
24647
+ []
24450
24648
  );
24451
24649
  const addCustomButtonStyle = useCallback(() => {
24452
24650
  setCustomButtonStyles((prev) => {
24453
24651
  const id = `custom-${Date.now()}`;
24454
- const next = [
24652
+ return [
24455
24653
  ...prev,
24456
24654
  {
24457
24655
  id,
@@ -24465,38 +24663,21 @@ function useThemeState(options) {
24465
24663
  borderHover: "var(--canvas-primary-dark)"
24466
24664
  }
24467
24665
  ];
24468
- setOverrides((ovr) => {
24469
- persistState(ovr, void 0, void 0, next);
24470
- return ovr;
24471
- });
24472
- return next;
24473
24666
  });
24474
- }, [persistState]);
24667
+ }, []);
24475
24668
  const updateCustomButtonStyle = useCallback(
24476
24669
  (id, updates) => {
24477
- setCustomButtonStyles((prev) => {
24478
- const next = prev.map((s) => s.id === id ? { ...s, ...updates } : s);
24479
- setOverrides((ovr) => {
24480
- persistState(ovr, void 0, void 0, next);
24481
- return ovr;
24482
- });
24483
- return next;
24484
- });
24670
+ setCustomButtonStyles(
24671
+ (prev) => prev.map((s) => s.id === id ? { ...s, ...updates } : s)
24672
+ );
24485
24673
  },
24486
- [persistState]
24674
+ []
24487
24675
  );
24488
24676
  const deleteCustomButtonStyle = useCallback(
24489
24677
  (id) => {
24490
- setCustomButtonStyles((prev) => {
24491
- const next = prev.filter((s) => s.id !== id);
24492
- setOverrides((ovr) => {
24493
- persistState(ovr, void 0, void 0, next);
24494
- return ovr;
24495
- });
24496
- return next;
24497
- });
24678
+ setCustomButtonStyles((prev) => prev.filter((s) => s.id !== id));
24498
24679
  },
24499
- [persistState]
24680
+ []
24500
24681
  );
24501
24682
  const applyBrandHueVibrancy = useCallback(
24502
24683
  (hue, vibrancy, skipPrimary = false) => {
@@ -24508,7 +24689,7 @@ function useThemeState(options) {
24508
24689
  const defaultHsl = hexToHsl2(defaultColor);
24509
24690
  const newHex = hslToHex2(hue, defaultHsl.s, defaultHsl.l);
24510
24691
  newValues["--canvas-primary"] = newHex;
24511
- document.documentElement.style.setProperty("--canvas-primary", newHex);
24692
+ (document.body ?? document.documentElement).style.setProperty("--canvas-primary", newHex);
24512
24693
  }
24513
24694
  }
24514
24695
  setOverrides((prev) => {
@@ -24516,7 +24697,7 @@ function useThemeState(options) {
24516
24697
  varsToUpdate.forEach((varName) => {
24517
24698
  if (varName === "--canvas-sidebar-light-active-text" || varName === "--canvas-border-input-focus") {
24518
24699
  newValues[varName] = effectivePrimaryColor;
24519
- document.documentElement.style.setProperty(varName, effectivePrimaryColor);
24700
+ (document.body ?? document.documentElement).style.setProperty(varName, effectivePrimaryColor);
24520
24701
  return;
24521
24702
  }
24522
24703
  if (varName === "--canvas-primary") return;
@@ -24536,15 +24717,14 @@ function useThemeState(options) {
24536
24717
  );
24537
24718
  const newHex = hslToHex2(hue, newS, newL);
24538
24719
  newValues[varName] = newHex;
24539
- document.documentElement.style.setProperty(varName, newHex);
24720
+ (document.body ?? document.documentElement).style.setProperty(varName, newHex);
24540
24721
  });
24541
24722
  const next = { ...prev, ...newValues };
24542
- persistState(next);
24543
24723
  onThemeChangeRef.current?.(next);
24544
24724
  return next;
24545
24725
  });
24546
24726
  },
24547
- [persistState]
24727
+ []
24548
24728
  );
24549
24729
  const setBrandHue = useCallback(
24550
24730
  (hue) => {
@@ -24565,11 +24745,24 @@ function useThemeState(options) {
24565
24745
  []
24566
24746
  );
24567
24747
  const variables = useMemo(() => ({ ...allDefaults, ...overrides }), [overrides]);
24568
- const unsavedChangesCount = Object.keys(overrides).length;
24748
+ const isDirty = useMemo(() => {
24749
+ return !isEqual(overrides, savedOverrides) || !isEqual(branding, savedBranding) || !isEqual(images, savedImages) || !isEqual(customButtonStyles, savedCustomButtonStyles);
24750
+ }, [overrides, savedOverrides, branding, savedBranding, images, savedImages, customButtonStyles, savedCustomButtonStyles]);
24751
+ const unsavedChangesCount = useMemo(() => {
24752
+ let count = 0;
24753
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(overrides), ...Object.keys(savedOverrides)]);
24754
+ for (const key of allKeys) {
24755
+ if (overrides[key] !== savedOverrides[key]) count++;
24756
+ }
24757
+ if (!isEqual(branding, savedBranding)) count++;
24758
+ if (!isEqual(images, savedImages)) count++;
24759
+ if (!isEqual(customButtonStyles, savedCustomButtonStyles)) count++;
24760
+ return count;
24761
+ }, [overrides, savedOverrides, branding, savedBranding, images, savedImages, customButtonStyles, savedCustomButtonStyles]);
24569
24762
  return {
24570
24763
  variables,
24571
24764
  overrides,
24572
- isDirty: unsavedChangesCount > 0,
24765
+ isDirty,
24573
24766
  unsavedChangesCount,
24574
24767
  setVariable,
24575
24768
  resetCategory,
@@ -24577,6 +24770,8 @@ function useThemeState(options) {
24577
24770
  exportCSS: () => exportCSS(overrides),
24578
24771
  exportJSON: () => exportJSON(overrides, branding, images, customButtonStyles),
24579
24772
  importJSON: handleImportJSON,
24773
+ save,
24774
+ discard,
24580
24775
  branding,
24581
24776
  setBranding,
24582
24777
  images,
@@ -25735,86 +25930,6 @@ function ImageUploadRow({
25735
25930
  }
25736
25931
  );
25737
25932
  }
25738
-
25739
- // src/components/theme-drawer/components/font-config.ts
25740
- var googleFontFamilies = {
25741
- // Sans-Serif
25742
- Inter: "Inter",
25743
- Geist: "Geist",
25744
- "Plus Jakarta Sans": "Plus+Jakarta+Sans",
25745
- Poppins: "Poppins",
25746
- "IBM Plex Sans": "IBM+Plex+Sans",
25747
- Roboto: "Roboto",
25748
- "Open Sans": "Open+Sans",
25749
- "Noto Sans": "Noto+Sans",
25750
- Montserrat: "Montserrat",
25751
- Lato: "Lato",
25752
- Raleway: "Raleway",
25753
- "DM Sans": "DM+Sans",
25754
- "Work Sans": "Work+Sans",
25755
- Manrope: "Manrope",
25756
- Outfit: "Outfit",
25757
- "Source Sans 3": "Source+Sans+3",
25758
- // Serif
25759
- "Playfair Display": "Playfair+Display",
25760
- Merriweather: "Merriweather",
25761
- "Noto Serif": "Noto+Serif",
25762
- "Libre Baskerville": "Libre+Baskerville",
25763
- // Monospace
25764
- "IBM Plex Mono": "IBM+Plex+Mono",
25765
- "Courier Prime": "Courier+Prime",
25766
- "Geist Mono": "Geist+Mono"
25767
- };
25768
- var loadedFonts = /* @__PURE__ */ new Set();
25769
- function loadGoogleFont(fontName) {
25770
- if (loadedFonts.has(fontName)) return;
25771
- const fontFamily = googleFontFamilies[fontName];
25772
- if (!fontFamily) return;
25773
- const link = document.createElement("link");
25774
- link.rel = "stylesheet";
25775
- link.href = `https://fonts.googleapis.com/css2?family=${fontFamily}:wght@400;500;600;700&display=swap`;
25776
- document.head.appendChild(link);
25777
- loadedFonts.add(fontName);
25778
- }
25779
- var fontsPreloaded = false;
25780
- function preloadAllFonts() {
25781
- if (fontsPreloaded) return;
25782
- Object.keys(googleFontFamilies).forEach((fontName) => {
25783
- loadGoogleFont(fontName);
25784
- });
25785
- fontsPreloaded = true;
25786
- }
25787
- var fontOptions = [
25788
- { value: "Courier Prime", label: "Courier Prime" },
25789
- { value: "DM Sans", label: "DM Sans" },
25790
- { value: "Geist", label: "Geist" },
25791
- { value: "Geist Mono", label: "Geist Mono" },
25792
- { value: "IBM Plex Mono", label: "IBM Plex Mono" },
25793
- { value: "IBM Plex Sans", label: "IBM Plex Sans" },
25794
- { value: "Inter", label: "Inter" },
25795
- { value: "Lato", label: "Lato" },
25796
- { value: "Libre Baskerville", label: "Libre Baskerville" },
25797
- { value: "Manrope", label: "Manrope" },
25798
- { value: "Merriweather", label: "Merriweather" },
25799
- { value: "Montserrat", label: "Montserrat" },
25800
- { value: "Noto Sans", label: "Noto Sans" },
25801
- { value: "Noto Serif", label: "Noto Serif" },
25802
- { value: "Open Sans", label: "Open Sans" },
25803
- { value: "Outfit", label: "Outfit" },
25804
- { value: "Playfair Display", label: "Playfair Display" },
25805
- { value: "Plus Jakarta Sans", label: "Plus Jakarta Sans" },
25806
- { value: "Poppins", label: "Poppins" },
25807
- { value: "Raleway", label: "Raleway" },
25808
- { value: "Roboto", label: "Roboto" },
25809
- { value: "Source Sans 3", label: "Source Sans 3" },
25810
- { value: "Work Sans", label: "Work Sans" }
25811
- ];
25812
- var fontWeightOptions = [
25813
- { value: "400", label: "Regular (400)" },
25814
- { value: "500", label: "Medium (500)" },
25815
- { value: "600", label: "Semibold (600)" },
25816
- { value: "700", label: "Bold (700)" }
25817
- ];
25818
25933
  var typographyColorOptions = [
25819
25934
  { value: "var(--canvas-text)", label: "Text" },
25820
25935
  { value: "var(--canvas-text-muted)", label: "Text Muted" },
@@ -27441,8 +27556,8 @@ var drawerStyles = {
27441
27556
  }),
27442
27557
  footer: {
27443
27558
  display: "flex",
27444
- justifyContent: "center",
27445
- alignItems: "center",
27559
+ flexDirection: "column",
27560
+ gap: "8px",
27446
27561
  padding: "12px 16px",
27447
27562
  borderTop: "1px solid #e5e7eb",
27448
27563
  flexShrink: 0,
@@ -27480,7 +27595,11 @@ function ThemeDrawer({
27480
27595
  onBrandingChange,
27481
27596
  onImagesChange,
27482
27597
  onImageUpload,
27598
+ onSave,
27483
27599
  initialOverrides,
27600
+ initialBranding,
27601
+ initialImages,
27602
+ initialCustomButtonStyles,
27484
27603
  storageKey,
27485
27604
  title = "Design Variables",
27486
27605
  width = 464
@@ -27494,15 +27613,53 @@ function ThemeDrawer({
27494
27613
  const theme = useThemeState({
27495
27614
  storageKey,
27496
27615
  initialOverrides,
27616
+ initialBranding,
27617
+ initialImages,
27618
+ initialCustomButtonStyles,
27497
27619
  onThemeChange,
27498
27620
  onBrandingChange,
27499
- onImagesChange
27621
+ onImagesChange,
27622
+ onSave
27500
27623
  });
27501
- const activePanels = enabledPanels ? panelConfig.filter((p) => enabledPanels.includes(p.id)) : panelConfig;
27502
- const handleReset = () => {
27624
+ const { setPreviewBranding, setPreviewImages } = usePreviewBranding();
27625
+ useEffect(() => {
27626
+ if (open) {
27627
+ setPreviewBranding(theme.branding);
27628
+ }
27629
+ }, [open, theme.branding, setPreviewBranding]);
27630
+ useEffect(() => {
27631
+ if (open) {
27632
+ setPreviewImages(theme.images);
27633
+ }
27634
+ }, [open, theme.images, setPreviewImages]);
27635
+ const handleOpenChange = useCallback(
27636
+ (newOpen) => {
27637
+ if (!newOpen) {
27638
+ if (theme.isDirty) {
27639
+ theme.discard();
27640
+ }
27641
+ setPreviewBranding(null);
27642
+ setPreviewImages(null);
27643
+ setResetConfirmOpen(false);
27644
+ }
27645
+ setOpen(newOpen);
27646
+ },
27647
+ [theme, setPreviewBranding, setPreviewImages]
27648
+ );
27649
+ const handleSave = useCallback(() => {
27650
+ theme.save();
27651
+ }, [theme]);
27652
+ const handleDiscard = useCallback(() => {
27653
+ theme.discard();
27654
+ }, [theme]);
27655
+ const handleReset = useCallback(() => {
27503
27656
  theme.resetAll();
27657
+ setPreviewBranding(null);
27658
+ setPreviewImages(null);
27504
27659
  setResetConfirmOpen(false);
27505
- };
27660
+ }, [theme, setPreviewBranding, setPreviewImages]);
27661
+ const activePanels = enabledPanels ? panelConfig.filter((p) => enabledPanels.includes(p.id)) : panelConfig;
27662
+ const showFooter = theme.isDirty || resetConfirmOpen;
27506
27663
  return /* @__PURE__ */ jsxs(Fragment, { children: [
27507
27664
  !open && /* @__PURE__ */ jsx(
27508
27665
  "button",
@@ -27521,7 +27678,7 @@ function ThemeDrawer({
27521
27678
  children: /* @__PURE__ */ jsx(PaletteIcon6, {})
27522
27679
  }
27523
27680
  ),
27524
- /* @__PURE__ */ jsx(Sheet, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs(
27681
+ /* @__PURE__ */ jsx(Sheet, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs(
27525
27682
  SheetContent,
27526
27683
  {
27527
27684
  side: "right",
@@ -27564,7 +27721,7 @@ function ThemeDrawer({
27564
27721
  marginTop: "2px",
27565
27722
  fontFamily: drawerStyles.container.fontFamily
27566
27723
  },
27567
- children: theme.unsavedChangesCount > 0 ? `${theme.unsavedChangesCount} unsaved modification${theme.unsavedChangesCount !== 1 ? "s" : ""}` : "Customize your design system"
27724
+ children: theme.unsavedChangesCount > 0 ? `${theme.unsavedChangesCount} unsaved change${theme.unsavedChangesCount !== 1 ? "s" : ""}` : "Customize your design system"
27568
27725
  }
27569
27726
  )
27570
27727
  ] })
@@ -27583,7 +27740,7 @@ function ThemeDrawer({
27583
27740
  ScrollArea,
27584
27741
  {
27585
27742
  className: "flex-1 overflow-hidden",
27586
- style: { height: `calc(100vh - ${theme.isDirty ? 200 : 150}px)` },
27743
+ style: { height: `calc(100vh - ${showFooter ? 200 : 150}px)` },
27587
27744
  children: /* @__PURE__ */ jsxs("div", { style: drawerStyles.container, children: [
27588
27745
  activeTab === "colors" && /* @__PURE__ */ jsx(ColorsPanel, { theme }),
27589
27746
  activeTab === "images" && /* @__PURE__ */ jsx(ImagesPanel, { theme, onImageUpload }),
@@ -27594,7 +27751,7 @@ function ThemeDrawer({
27594
27751
  ] })
27595
27752
  }
27596
27753
  ),
27597
- theme.isDirty && /* @__PURE__ */ jsx("div", { style: drawerStyles.footer, children: resetConfirmOpen ? /* @__PURE__ */ jsxs(
27754
+ showFooter && /* @__PURE__ */ jsx("div", { style: drawerStyles.footer, children: resetConfirmOpen ? /* @__PURE__ */ jsxs(
27598
27755
  "div",
27599
27756
  {
27600
27757
  style: {
@@ -27663,24 +27820,75 @@ function ThemeDrawer({
27663
27820
  )
27664
27821
  ]
27665
27822
  }
27666
- ) : /* @__PURE__ */ jsx(
27667
- "button",
27668
- {
27669
- onClick: () => setResetConfirmOpen(true),
27670
- style: {
27671
- fontSize: "13px",
27672
- fontWeight: 500,
27673
- color: "#dc2626",
27674
- background: "none",
27675
- border: "1px solid #fca5a5",
27676
- cursor: "pointer",
27677
- padding: "8px 16px",
27678
- borderRadius: "6px",
27679
- width: "100%"
27680
- },
27681
- children: "Reset All to Defaults"
27682
- }
27683
- ) })
27823
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
27824
+ /* @__PURE__ */ jsxs(
27825
+ "div",
27826
+ {
27827
+ style: {
27828
+ display: "flex",
27829
+ gap: "8px",
27830
+ width: "100%"
27831
+ },
27832
+ children: [
27833
+ /* @__PURE__ */ jsx(
27834
+ "button",
27835
+ {
27836
+ onClick: handleDiscard,
27837
+ style: {
27838
+ flex: 1,
27839
+ fontSize: "13px",
27840
+ fontWeight: 500,
27841
+ color: "#374151",
27842
+ background: "#ffffff",
27843
+ border: "1px solid #e5e7eb",
27844
+ cursor: "pointer",
27845
+ padding: "8px 16px",
27846
+ borderRadius: "6px"
27847
+ },
27848
+ children: "Discard"
27849
+ }
27850
+ ),
27851
+ /* @__PURE__ */ jsx(
27852
+ "button",
27853
+ {
27854
+ onClick: handleSave,
27855
+ style: {
27856
+ flex: 1,
27857
+ fontSize: "13px",
27858
+ fontWeight: 600,
27859
+ color: "#ffffff",
27860
+ background: "#1f2937",
27861
+ border: "1px solid #1f2937",
27862
+ cursor: "pointer",
27863
+ padding: "8px 16px",
27864
+ borderRadius: "6px"
27865
+ },
27866
+ children: "Save"
27867
+ }
27868
+ )
27869
+ ]
27870
+ }
27871
+ ),
27872
+ /* @__PURE__ */ jsx(
27873
+ "button",
27874
+ {
27875
+ onClick: () => setResetConfirmOpen(true),
27876
+ style: {
27877
+ fontSize: "12px",
27878
+ fontWeight: 400,
27879
+ color: "#9ca3af",
27880
+ background: "none",
27881
+ border: "none",
27882
+ cursor: "pointer",
27883
+ padding: "2px 0",
27884
+ textAlign: "center",
27885
+ textDecoration: "underline",
27886
+ textUnderlineOffset: "2px"
27887
+ },
27888
+ children: "Reset all to defaults"
27889
+ }
27890
+ )
27891
+ ] }) })
27684
27892
  ]
27685
27893
  }
27686
27894
  ) })