shru-design-system 0.1.2 → 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 +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +241 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +241 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/scripts/apply-theme-sync.js +230 -0
- package/scripts/init.js +65 -0
package/dist/index.d.mts
CHANGED
|
@@ -152,4 +152,14 @@ declare function getThemesForCategory(category: string): Promise<Record<string,
|
|
|
152
152
|
*/
|
|
153
153
|
declare function getTheme(category: string, themeId: string): Promise<ThemeMetadata | null>;
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Synchronous theme application for blocking script execution
|
|
157
|
+
* This runs before React hydrates to prevent theme flash
|
|
158
|
+
*/
|
|
159
|
+
/**
|
|
160
|
+
* Apply theme synchronously using blocking XMLHttpRequest
|
|
161
|
+
* This prevents flash of unstyled content
|
|
162
|
+
*/
|
|
163
|
+
declare function applyThemeSync(): void;
|
|
164
|
+
|
|
165
|
+
export { Badge, Button, Modal, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, type ThemeMetadata$1 as ThemeMetadata, type ThemeSelection, ThemeToggle, type ThemeToggleProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, applyThemeSync, badgeVariants, buttonVariants, getTheme, getThemeCategories, getThemeFilePath, getThemesForCategory, registerTheme, useTheme, useThemeToggle };
|
package/dist/index.d.ts
CHANGED
|
@@ -152,4 +152,14 @@ declare function getThemesForCategory(category: string): Promise<Record<string,
|
|
|
152
152
|
*/
|
|
153
153
|
declare function getTheme(category: string, themeId: string): Promise<ThemeMetadata | null>;
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Synchronous theme application for blocking script execution
|
|
157
|
+
* This runs before React hydrates to prevent theme flash
|
|
158
|
+
*/
|
|
159
|
+
/**
|
|
160
|
+
* Apply theme synchronously using blocking XMLHttpRequest
|
|
161
|
+
* This prevents flash of unstyled content
|
|
162
|
+
*/
|
|
163
|
+
declare function applyThemeSync(): void;
|
|
164
|
+
|
|
165
|
+
export { Badge, Button, Modal, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, type ThemeMetadata$1 as ThemeMetadata, type ThemeSelection, ThemeToggle, type ThemeToggleProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, applyThemeSync, badgeVariants, buttonVariants, getTheme, getThemeCategories, getThemeFilePath, getThemesForCategory, registerTheme, useTheme, useThemeToggle };
|
package/dist/index.js
CHANGED
|
@@ -712,6 +712,39 @@ function resolveReferences(tokens, palette) {
|
|
|
712
712
|
traverse(resolved);
|
|
713
713
|
return resolved;
|
|
714
714
|
}
|
|
715
|
+
function hexToHSL(hex) {
|
|
716
|
+
hex = hex.replace("#", "");
|
|
717
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
718
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
719
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
720
|
+
const max = Math.max(r, g, b);
|
|
721
|
+
const min = Math.min(r, g, b);
|
|
722
|
+
let h = 0;
|
|
723
|
+
let s = 0;
|
|
724
|
+
const l = (max + min) / 2;
|
|
725
|
+
if (max !== min) {
|
|
726
|
+
const d = max - min;
|
|
727
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
728
|
+
switch (max) {
|
|
729
|
+
case r:
|
|
730
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
731
|
+
break;
|
|
732
|
+
case g:
|
|
733
|
+
h = ((b - r) / d + 2) / 6;
|
|
734
|
+
break;
|
|
735
|
+
case b:
|
|
736
|
+
h = ((r - g) / d + 4) / 6;
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
h = Math.round(h * 360);
|
|
741
|
+
s = Math.round(s * 100);
|
|
742
|
+
const lPercent = Math.round(l * 100);
|
|
743
|
+
return `${h} ${s}% ${lPercent}%`;
|
|
744
|
+
}
|
|
745
|
+
function isHexColor(value) {
|
|
746
|
+
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value);
|
|
747
|
+
}
|
|
715
748
|
function flattenToCSS(tokens, prefix = "", result = {}, isColorContext = false) {
|
|
716
749
|
for (const key in tokens) {
|
|
717
750
|
const value = tokens[key];
|
|
@@ -735,7 +768,11 @@ function flattenToCSS(tokens, prefix = "", result = {}, isColorContext = false)
|
|
|
735
768
|
} else {
|
|
736
769
|
cssKey = `--${prefix}-${key}`;
|
|
737
770
|
}
|
|
738
|
-
|
|
771
|
+
let finalValue = value;
|
|
772
|
+
if (isColorContext && typeof value === "string" && isHexColor(value)) {
|
|
773
|
+
finalValue = hexToHSL(value);
|
|
774
|
+
}
|
|
775
|
+
result[cssKey] = finalValue;
|
|
739
776
|
}
|
|
740
777
|
}
|
|
741
778
|
return result;
|
|
@@ -1226,6 +1263,208 @@ function ThemeRingAsync({
|
|
|
1226
1263
|
] });
|
|
1227
1264
|
}
|
|
1228
1265
|
|
|
1266
|
+
// src/themes/applyThemeSync.ts
|
|
1267
|
+
var STORAGE_KEY2 = "design-system-theme";
|
|
1268
|
+
function applyThemeSync() {
|
|
1269
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
let selectedThemes = getDefaultThemes();
|
|
1273
|
+
try {
|
|
1274
|
+
const stored = localStorage.getItem(STORAGE_KEY2);
|
|
1275
|
+
if (stored) {
|
|
1276
|
+
selectedThemes = JSON.parse(stored);
|
|
1277
|
+
}
|
|
1278
|
+
} catch {
|
|
1279
|
+
}
|
|
1280
|
+
try {
|
|
1281
|
+
const base = loadJSONSync("/tokens/base.json");
|
|
1282
|
+
const palettes = loadJSONSync("/tokens/palettes.json");
|
|
1283
|
+
const palette = palettes?.palette || {};
|
|
1284
|
+
let merged = deepMergeSync(base || {}, { palette });
|
|
1285
|
+
const categoryOrder = ["color", "typography", "shape", "density", "animation"];
|
|
1286
|
+
for (const category of categoryOrder) {
|
|
1287
|
+
const themeId = selectedThemes[category];
|
|
1288
|
+
if (!themeId) continue;
|
|
1289
|
+
try {
|
|
1290
|
+
const themeData = loadJSONSync(`/tokens/themes/${category}/${themeId}.json`);
|
|
1291
|
+
if (themeData) {
|
|
1292
|
+
merged = deepMergeSync(merged, themeData);
|
|
1293
|
+
}
|
|
1294
|
+
} catch {
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
if (selectedThemes.custom) {
|
|
1298
|
+
try {
|
|
1299
|
+
const customData = loadJSONSync(`/tokens/themes/custom/${selectedThemes.custom}.json`);
|
|
1300
|
+
if (customData) {
|
|
1301
|
+
merged = deepMergeSync(merged, customData);
|
|
1302
|
+
}
|
|
1303
|
+
} catch {
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
const resolved = resolveReferencesSync(merged, palette);
|
|
1307
|
+
const cssVars = flattenToCSSSync(resolved);
|
|
1308
|
+
const mappedVars = mapToTailwindVarsSync(cssVars);
|
|
1309
|
+
const css = `:root {
|
|
1310
|
+
${Object.entries(mappedVars).map(([key, value]) => ` ${key}: ${value};`).join("\n")}
|
|
1311
|
+
}`;
|
|
1312
|
+
let styleTag = document.getElementById("dynamic-theme");
|
|
1313
|
+
if (!styleTag) {
|
|
1314
|
+
styleTag = document.createElement("style");
|
|
1315
|
+
styleTag.id = "dynamic-theme";
|
|
1316
|
+
document.head.insertBefore(styleTag, document.head.firstChild);
|
|
1317
|
+
}
|
|
1318
|
+
styleTag.textContent = css;
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
console.warn("Sync theme application failed, will apply via React:", error);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
function loadJSONSync(path) {
|
|
1324
|
+
try {
|
|
1325
|
+
const xhr = new XMLHttpRequest();
|
|
1326
|
+
xhr.open("GET", path, false);
|
|
1327
|
+
xhr.send(null);
|
|
1328
|
+
if (xhr.status === 200 || xhr.status === 0) {
|
|
1329
|
+
return JSON.parse(xhr.responseText);
|
|
1330
|
+
}
|
|
1331
|
+
} catch {
|
|
1332
|
+
}
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
function deepMergeSync(target, source) {
|
|
1336
|
+
const output = { ...target };
|
|
1337
|
+
if (isObjectSync(target) && isObjectSync(source)) {
|
|
1338
|
+
Object.keys(source).forEach((key) => {
|
|
1339
|
+
if (isObjectSync(source[key])) {
|
|
1340
|
+
if (!(key in target)) {
|
|
1341
|
+
Object.assign(output, { [key]: source[key] });
|
|
1342
|
+
} else {
|
|
1343
|
+
output[key] = deepMergeSync(target[key], source[key]);
|
|
1344
|
+
}
|
|
1345
|
+
} else {
|
|
1346
|
+
Object.assign(output, { [key]: source[key] });
|
|
1347
|
+
}
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
return output;
|
|
1351
|
+
}
|
|
1352
|
+
function isObjectSync(item) {
|
|
1353
|
+
return item && typeof item === "object" && !Array.isArray(item);
|
|
1354
|
+
}
|
|
1355
|
+
function resolveReferencesSync(tokens, palette) {
|
|
1356
|
+
const resolved = JSON.parse(JSON.stringify(tokens));
|
|
1357
|
+
function resolveValue(value) {
|
|
1358
|
+
if (typeof value !== "string") return value;
|
|
1359
|
+
const match = value.match(/^\{([^}]+)\}$/);
|
|
1360
|
+
if (!match) return value;
|
|
1361
|
+
const path = match[1].split(".");
|
|
1362
|
+
let current = { palette, ...resolved };
|
|
1363
|
+
for (const key of path) {
|
|
1364
|
+
if (current && typeof current === "object" && key in current) {
|
|
1365
|
+
current = current[key];
|
|
1366
|
+
} else {
|
|
1367
|
+
return value;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
return typeof current === "string" ? current : value;
|
|
1371
|
+
}
|
|
1372
|
+
function traverse(obj) {
|
|
1373
|
+
for (const key in obj) {
|
|
1374
|
+
if (typeof obj[key] === "object" && obj[key] !== null) {
|
|
1375
|
+
traverse(obj[key]);
|
|
1376
|
+
} else {
|
|
1377
|
+
obj[key] = resolveValue(obj[key]);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
traverse(resolved);
|
|
1382
|
+
return resolved;
|
|
1383
|
+
}
|
|
1384
|
+
function hexToHSL2(hex) {
|
|
1385
|
+
hex = hex.replace("#", "");
|
|
1386
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
1387
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
1388
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
1389
|
+
const max = Math.max(r, g, b);
|
|
1390
|
+
const min = Math.min(r, g, b);
|
|
1391
|
+
let h = 0;
|
|
1392
|
+
let s = 0;
|
|
1393
|
+
const l = (max + min) / 2;
|
|
1394
|
+
if (max !== min) {
|
|
1395
|
+
const d = max - min;
|
|
1396
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
1397
|
+
switch (max) {
|
|
1398
|
+
case r:
|
|
1399
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
1400
|
+
break;
|
|
1401
|
+
case g:
|
|
1402
|
+
h = ((b - r) / d + 2) / 6;
|
|
1403
|
+
break;
|
|
1404
|
+
case b:
|
|
1405
|
+
h = ((r - g) / d + 4) / 6;
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
h = Math.round(h * 360);
|
|
1410
|
+
s = Math.round(s * 100);
|
|
1411
|
+
const lPercent = Math.round(l * 100);
|
|
1412
|
+
return `${h} ${s}% ${lPercent}%`;
|
|
1413
|
+
}
|
|
1414
|
+
function isHexColor2(value) {
|
|
1415
|
+
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value);
|
|
1416
|
+
}
|
|
1417
|
+
function flattenToCSSSync(tokens, prefix = "", result = {}, isColorContext = false) {
|
|
1418
|
+
for (const key in tokens) {
|
|
1419
|
+
const value = tokens[key];
|
|
1420
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1421
|
+
const enteringColor = key === "color" && prefix === "";
|
|
1422
|
+
const inColorContext = isColorContext || enteringColor;
|
|
1423
|
+
if (enteringColor) {
|
|
1424
|
+
flattenToCSSSync(value, "", result, true);
|
|
1425
|
+
} else if (inColorContext) {
|
|
1426
|
+
flattenToCSSSync(value, "", result, true);
|
|
1427
|
+
} else {
|
|
1428
|
+
const newPrefix = prefix ? `${prefix}-${key}` : key;
|
|
1429
|
+
flattenToCSSSync(value, newPrefix, result, false);
|
|
1430
|
+
}
|
|
1431
|
+
} else {
|
|
1432
|
+
let cssKey;
|
|
1433
|
+
if (isColorContext || prefix === "" && key === "color") {
|
|
1434
|
+
cssKey = `--${key}`;
|
|
1435
|
+
} else if (prefix === "") {
|
|
1436
|
+
cssKey = `--${key}`;
|
|
1437
|
+
} else {
|
|
1438
|
+
cssKey = `--${prefix}-${key}`;
|
|
1439
|
+
}
|
|
1440
|
+
let finalValue = value;
|
|
1441
|
+
if (isColorContext && typeof value === "string" && isHexColor2(value)) {
|
|
1442
|
+
finalValue = hexToHSL2(value);
|
|
1443
|
+
}
|
|
1444
|
+
result[cssKey] = finalValue;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return result;
|
|
1448
|
+
}
|
|
1449
|
+
function mapToTailwindVarsSync(cssVars) {
|
|
1450
|
+
const mapped = { ...cssVars };
|
|
1451
|
+
if (cssVars["--radius-button"]) {
|
|
1452
|
+
mapped["--radius"] = cssVars["--radius-button"];
|
|
1453
|
+
}
|
|
1454
|
+
if (cssVars["--radius-card"]) {
|
|
1455
|
+
mapped["--radius-lg"] = cssVars["--radius-card"];
|
|
1456
|
+
}
|
|
1457
|
+
if (cssVars["--font-body"]) {
|
|
1458
|
+
mapped["--font-sans"] = cssVars["--font-body"];
|
|
1459
|
+
}
|
|
1460
|
+
if (cssVars["--spacing-base"]) {
|
|
1461
|
+
mapped["--spacing"] = cssVars["--spacing-base"];
|
|
1462
|
+
} else if (cssVars["--spacing-component-md"]) {
|
|
1463
|
+
mapped["--spacing"] = cssVars["--spacing-component-md"];
|
|
1464
|
+
}
|
|
1465
|
+
return mapped;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1229
1468
|
exports.Badge = Badge;
|
|
1230
1469
|
exports.Button = Button;
|
|
1231
1470
|
exports.Modal = Modal;
|
|
@@ -1253,6 +1492,7 @@ exports.Tooltip = Tooltip;
|
|
|
1253
1492
|
exports.TooltipContent = TooltipContent;
|
|
1254
1493
|
exports.TooltipProvider = TooltipProvider;
|
|
1255
1494
|
exports.TooltipTrigger = TooltipTrigger;
|
|
1495
|
+
exports.applyThemeSync = applyThemeSync;
|
|
1256
1496
|
exports.badgeVariants = badgeVariants;
|
|
1257
1497
|
exports.buttonVariants = buttonVariants;
|
|
1258
1498
|
exports.getTheme = getTheme;
|