shru-design-system 0.1.9 → 0.1.11

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/README.md CHANGED
@@ -88,18 +88,36 @@ Local test environment for developing and testing the library:
88
88
 
89
89
  ## Usage
90
90
 
91
- Install the package:
91
+ Install:
92
92
  ```bash
93
93
  npm install shru-design-system
94
94
  ```
95
95
 
96
- The `postinstall` script automatically sets up Tailwind CSS, PostCSS, and token files in your project.
96
+ ### Theme setup (new flow)
97
+ - Tokens are embedded in the bundle, so the theme system works out-of-the-box (no `/public/tokens` required).
98
+ - If you want to self-host tokens (for custom themes or CDN):
99
+ - Set `window.__THEME_TOKENS_BASE__ = "/tokens"` (or your URL) before the app renders, or set `DESIGN_SYSTEM_TOKENS_BASE` env at build/runtime.
100
+ - Serve token JSONs at `<base>/base.json`, `<base>/palettes.json`, `<base>/themes/<category>/<theme>.json`.
101
+ - Blocking HTML injection is removed; the runtime/applyThemeSync appends `#dynamic-theme` at the end of `<head>` so its CSS vars override compiled CSS.
102
+
103
+ Optional token copy:
104
+ ```bash
105
+ npx shru-design-system-init # copies tokens into public/tokens if you want static hosting
106
+ ```
97
107
 
98
108
  Import components:
99
109
  ```tsx
100
- import { Button, Modal, ThemeToggle } from 'shru-design-system'
110
+ import { Button, Modal, ThemeToggle, registerThemeFromFile } from 'shru-design-system'
101
111
  ```
102
112
 
113
+ Registering themes:
114
+ - Add a custom theme file at your token base, e.g. `/tokens/themes/custom/my-brand.json`.
115
+ - Call at runtime (after page load):
116
+ ```ts
117
+ registerThemeFromFile('custom', 'my-brand', '/tokens/themes/custom/my-brand.json')
118
+ ```
119
+ The theme toggle will pick it up automatically.
120
+
103
121
  ## Development
104
122
 
105
123
  Build the library:
@@ -111,4 +129,3 @@ Test locally:
111
129
  ```bash
112
130
  cd test && npm run dev
113
131
  ```
114
-
package/dist/index.js CHANGED
@@ -762,6 +762,331 @@ async function getTheme(category, themeId) {
762
762
  return categories[category]?.themes[themeId] || null;
763
763
  }
764
764
 
765
+ // src/tokens/base.json
766
+ var base_default = {
767
+ _createdBy: "shru-design-system library",
768
+ color: {
769
+ primary: "{palette.blue.500}",
770
+ "primary-hover": "{palette.blue.600}",
771
+ "primary-foreground": "{palette.white}",
772
+ secondary: "{palette.gray.100}",
773
+ "secondary-foreground": "{palette.gray.900}",
774
+ background: "{palette.white}",
775
+ foreground: "{palette.gray.900}",
776
+ card: "{palette.white}",
777
+ "card-foreground": "{palette.gray.900}",
778
+ popover: "{palette.white}",
779
+ "popover-foreground": "{palette.gray.900}",
780
+ muted: "{palette.gray.100}",
781
+ "muted-foreground": "{palette.gray.500}",
782
+ accent: "{palette.gray.100}",
783
+ "accent-foreground": "{palette.gray.900}",
784
+ destructive: "{palette.red.500}",
785
+ "destructive-foreground": "{palette.white}",
786
+ border: "{palette.gray.200}",
787
+ input: "{palette.gray.200}",
788
+ ring: "{palette.gray.400}",
789
+ skeleton: "{palette.gray.200}"
790
+ },
791
+ spacing: {
792
+ component: {
793
+ xs: "0.25rem",
794
+ sm: "0.5rem",
795
+ md: "1rem",
796
+ lg: "1.5rem",
797
+ xl: "2rem"
798
+ },
799
+ base: "0.25rem"
800
+ },
801
+ font: {
802
+ body: "var(--font-sans)",
803
+ sans: "var(--font-sans)",
804
+ mono: "var(--font-mono)"
805
+ },
806
+ radius: {
807
+ button: "0.375rem",
808
+ card: "0.5rem",
809
+ input: "0.375rem"
810
+ }
811
+ };
812
+
813
+ // src/tokens/palettes.json
814
+ var palettes_default = {
815
+ _createdBy: "shru-design-system library",
816
+ palette: {
817
+ white: "#ffffff",
818
+ black: "#000000",
819
+ transparent: "transparent",
820
+ gray: {
821
+ "50": "#f9fafb",
822
+ "100": "#f3f4f6",
823
+ "200": "#e5e7eb",
824
+ "300": "#d1d5db",
825
+ "400": "#9ca3af",
826
+ "500": "#6b7280",
827
+ "600": "#4b5563",
828
+ "700": "#374151",
829
+ "800": "#1f2937",
830
+ "900": "#111827",
831
+ "950": "#030712"
832
+ },
833
+ blue: {
834
+ "50": "#eff6ff",
835
+ "100": "#dbeafe",
836
+ "200": "#bfdbfe",
837
+ "300": "#93c5fd",
838
+ "400": "#60a5fa",
839
+ "500": "#3b82f6",
840
+ "600": "#2563eb",
841
+ "700": "#1d4ed8",
842
+ "800": "#1e40af",
843
+ "900": "#1e3a8a",
844
+ "950": "#172554"
845
+ },
846
+ red: {
847
+ "50": "#fef2f2",
848
+ "100": "#fee2e2",
849
+ "200": "#fecaca",
850
+ "300": "#fca5a5",
851
+ "400": "#f87171",
852
+ "500": "#ef4444",
853
+ "600": "#dc2626",
854
+ "700": "#b91c1c",
855
+ "800": "#991b1b",
856
+ "900": "#7f1d1d",
857
+ "950": "#450a0a"
858
+ },
859
+ purple: {
860
+ "50": "#faf5ff",
861
+ "100": "#f3e8ff",
862
+ "200": "#e9d5ff",
863
+ "300": "#d8b4fe",
864
+ "400": "#c084fc",
865
+ "500": "#a855f7",
866
+ "600": "#9333ea",
867
+ "700": "#7e22ce",
868
+ "800": "#6b21a8",
869
+ "900": "#581c87",
870
+ "950": "#3b0764"
871
+ },
872
+ pink: {
873
+ "50": "#fdf2f8",
874
+ "100": "#fce7f3",
875
+ "200": "#fbcfe8",
876
+ "300": "#f9a8d4",
877
+ "400": "#f472b6",
878
+ "500": "#ec4899",
879
+ "600": "#db2777",
880
+ "700": "#be185d",
881
+ "800": "#9f1239",
882
+ "900": "#831843",
883
+ "950": "#500724"
884
+ }
885
+ }
886
+ };
887
+
888
+ // src/tokens/themes/color/dark.json
889
+ var dark_default = {
890
+ _createdBy: "shru-design-system library",
891
+ color: {
892
+ primary: "{palette.blue.400}",
893
+ "primary-foreground": "{palette.gray.900}",
894
+ background: "{palette.gray.900}",
895
+ foreground: "{palette.gray.50}",
896
+ card: "{palette.gray.800}",
897
+ "card-foreground": "{palette.gray.50}",
898
+ popover: "{palette.gray.800}",
899
+ "popover-foreground": "{palette.gray.50}",
900
+ secondary: "{palette.gray.800}",
901
+ "secondary-foreground": "{palette.gray.50}",
902
+ muted: "{palette.gray.800}",
903
+ "muted-foreground": "{palette.gray.400}",
904
+ accent: "{palette.gray.800}",
905
+ "accent-foreground": "{palette.gray.50}",
906
+ destructive: "{palette.red.500}",
907
+ "destructive-foreground": "{palette.white}",
908
+ border: "{palette.gray.700}",
909
+ input: "{palette.gray.700}",
910
+ ring: "{palette.gray.600}",
911
+ skeleton: "{palette.gray.700}"
912
+ }
913
+ };
914
+
915
+ // src/tokens/themes/color/white.json
916
+ var white_default = {
917
+ _createdBy: "shru-design-system library",
918
+ color: {
919
+ primary: "{palette.blue.500}",
920
+ "primary-foreground": "{palette.white}",
921
+ background: "{palette.white}",
922
+ foreground: "{palette.gray.900}",
923
+ card: "{palette.white}",
924
+ "card-foreground": "{palette.gray.900}",
925
+ popover: "{palette.white}",
926
+ "popover-foreground": "{palette.gray.900}",
927
+ secondary: "{palette.gray.100}",
928
+ "secondary-foreground": "{palette.gray.900}",
929
+ muted: "{palette.gray.100}",
930
+ "muted-foreground": "{palette.gray.500}",
931
+ accent: "{palette.gray.100}",
932
+ "accent-foreground": "{palette.gray.900}",
933
+ destructive: "{palette.red.500}",
934
+ "destructive-foreground": "{palette.white}",
935
+ border: "{palette.gray.200}",
936
+ input: "{palette.gray.200}",
937
+ ring: "{palette.gray.400}",
938
+ skeleton: "{palette.gray.200}"
939
+ }
940
+ };
941
+
942
+ // src/tokens/themes/typography/sans.json
943
+ var sans_default = {
944
+ _createdBy: "shru-design-system library",
945
+ font: {
946
+ body: "system-ui, -apple-system, sans-serif",
947
+ sans: "system-ui, -apple-system, sans-serif"
948
+ }
949
+ };
950
+
951
+ // src/tokens/themes/typography/serif.json
952
+ var serif_default = {
953
+ _createdBy: "shru-design-system library",
954
+ font: {
955
+ body: "Georgia, serif",
956
+ sans: "Georgia, serif"
957
+ }
958
+ };
959
+
960
+ // src/tokens/themes/shape/smooth.json
961
+ var smooth_default = {
962
+ _createdBy: "shru-design-system library",
963
+ radius: {
964
+ button: "0.5rem",
965
+ card: "0.75rem",
966
+ input: "0.5rem"
967
+ }
968
+ };
969
+
970
+ // src/tokens/themes/shape/sharp.json
971
+ var sharp_default = {
972
+ _createdBy: "shru-design-system library",
973
+ radius: {
974
+ button: "0",
975
+ card: "0",
976
+ input: "0"
977
+ }
978
+ };
979
+
980
+ // src/tokens/themes/density/comfortable.json
981
+ var comfortable_default = {
982
+ _createdBy: "shru-design-system library",
983
+ spacing: {
984
+ component: {
985
+ xs: "0.5rem",
986
+ sm: "0.75rem",
987
+ md: "1.25rem",
988
+ lg: "2rem",
989
+ xl: "2.5rem"
990
+ }
991
+ }
992
+ };
993
+
994
+ // src/tokens/themes/density/compact.json
995
+ var compact_default = {
996
+ _createdBy: "shru-design-system library",
997
+ spacing: {
998
+ component: {
999
+ xs: "0.25rem",
1000
+ sm: "0.5rem",
1001
+ md: "0.75rem",
1002
+ lg: "1rem",
1003
+ xl: "1.5rem"
1004
+ }
1005
+ }
1006
+ };
1007
+
1008
+ // src/tokens/themes/animation/gentle.json
1009
+ var gentle_default = {
1010
+ _createdBy: "shru-design-system library",
1011
+ animation: {
1012
+ duration: {
1013
+ fast: "150ms",
1014
+ normal: "300ms",
1015
+ slow: "500ms"
1016
+ }
1017
+ }
1018
+ };
1019
+
1020
+ // src/tokens/themes/animation/brisk.json
1021
+ var brisk_default = {
1022
+ _createdBy: "shru-design-system library",
1023
+ animation: {
1024
+ duration: {
1025
+ fast: "100ms",
1026
+ normal: "200ms",
1027
+ slow: "300ms"
1028
+ }
1029
+ }
1030
+ };
1031
+
1032
+ // src/tokens/themes/custom/brand.json
1033
+ var brand_default = {
1034
+ _createdBy: "shru-design-system library",
1035
+ color: {
1036
+ primary: "{palette.purple.600}",
1037
+ "primary-foreground": "{palette.white}",
1038
+ accent: "{palette.pink.500}",
1039
+ "accent-foreground": "{palette.white}"
1040
+ },
1041
+ radius: {
1042
+ button: "0.5rem",
1043
+ card: "0.75rem"
1044
+ }
1045
+ };
1046
+
1047
+ // src/tokens/themes/custom/minimal.json
1048
+ var minimal_default = {
1049
+ _createdBy: "shru-design-system library",
1050
+ color: {
1051
+ primary: "{palette.gray.700}",
1052
+ "primary-foreground": "{palette.white}",
1053
+ background: "{palette.white}",
1054
+ foreground: "{palette.gray.900}"
1055
+ },
1056
+ spacing: {
1057
+ component: {
1058
+ xs: "0.375rem",
1059
+ sm: "0.5rem",
1060
+ md: "0.75rem"
1061
+ }
1062
+ }
1063
+ };
1064
+
1065
+ // src/themes/tokenLoader.ts
1066
+ var EMBEDDED_TOKEN_BASE = "__EMBEDDED__";
1067
+ var EMBEDDED_TOKENS = {
1068
+ "base.json": base_default,
1069
+ "palettes.json": palettes_default,
1070
+ "themes/color/dark.json": dark_default,
1071
+ "themes/color/white.json": white_default,
1072
+ "themes/typography/sans.json": sans_default,
1073
+ "themes/typography/serif.json": serif_default,
1074
+ "themes/shape/smooth.json": smooth_default,
1075
+ "themes/shape/sharp.json": sharp_default,
1076
+ "themes/density/comfortable.json": comfortable_default,
1077
+ "themes/density/compact.json": compact_default,
1078
+ "themes/animation/gentle.json": gentle_default,
1079
+ "themes/animation/brisk.json": brisk_default,
1080
+ "themes/custom/brand.json": brand_default,
1081
+ "themes/custom/minimal.json": minimal_default
1082
+ };
1083
+ function normalizeTokenPath(path) {
1084
+ return path.replace(/^\/?tokens\//, "");
1085
+ }
1086
+ function getEmbeddedToken(normalizedPath) {
1087
+ return EMBEDDED_TOKENS[normalizedPath];
1088
+ }
1089
+
765
1090
  // src/themes/themeUtils.ts
766
1091
  function getThemeName(selectedThemes) {
767
1092
  const parts = [];
@@ -824,31 +1149,57 @@ function deepMerge(target, source) {
824
1149
  return output;
825
1150
  }
826
1151
  var tokenCache = /* @__PURE__ */ new Map();
1152
+ function getTokenBaseCandidates() {
1153
+ const bases = [];
1154
+ if (typeof window !== "undefined" && window.__THEME_TOKENS_BASE__) {
1155
+ bases.push(window.__THEME_TOKENS_BASE__);
1156
+ }
1157
+ const env = typeof globalThis !== "undefined" ? globalThis.process?.env : void 0;
1158
+ if (env?.DESIGN_SYSTEM_TOKENS_BASE) {
1159
+ bases.push(env.DESIGN_SYSTEM_TOKENS_BASE);
1160
+ }
1161
+ bases.push(EMBEDDED_TOKEN_BASE);
1162
+ bases.push("/tokens");
1163
+ return Array.from(new Set(bases.filter(Boolean)));
1164
+ }
827
1165
  async function loadTokenFile(path) {
828
- if (tokenCache.has(path)) {
829
- return deepClone(tokenCache.get(path));
1166
+ const normalizedPath = normalizeTokenPath(path);
1167
+ if (tokenCache.has(normalizedPath)) {
1168
+ return deepClone(tokenCache.get(normalizedPath));
830
1169
  }
831
- try {
832
- const response = await fetch(path);
833
- if (!response.ok) {
834
- if (response.status === 404) {
835
- return null;
1170
+ const bases = getTokenBaseCandidates();
1171
+ for (const base of bases) {
1172
+ if (base === EMBEDDED_TOKEN_BASE) {
1173
+ const embedded = getEmbeddedToken(normalizedPath);
1174
+ if (embedded) {
1175
+ tokenCache.set(normalizedPath, embedded);
1176
+ return deepClone(embedded);
836
1177
  }
837
- throw new Error(`Failed to load ${path}: ${response.statusText}`);
838
- }
839
- const contentType = response.headers.get("content-type");
840
- if (!contentType || !contentType.includes("application/json")) {
841
- return null;
1178
+ continue;
842
1179
  }
843
- const data = await response.json();
844
- tokenCache.set(path, data);
845
- return deepClone(data);
846
- } catch (error) {
847
- if (typeof window !== "undefined" && window.__DESIGN_SYSTEM_DEBUG__) {
848
- console.warn(`Error loading token file ${path}:`, error);
1180
+ const url = base.endsWith("/") ? `${base}${normalizedPath}` : `${base}/${normalizedPath}`;
1181
+ try {
1182
+ const response = await fetch(url);
1183
+ if (!response.ok) {
1184
+ if (response.status === 404) {
1185
+ continue;
1186
+ }
1187
+ throw new Error(`Failed to load ${url}: ${response.statusText}`);
1188
+ }
1189
+ const contentType = response.headers.get("content-type");
1190
+ if (!contentType || !contentType.includes("application/json")) {
1191
+ continue;
1192
+ }
1193
+ const data = await response.json();
1194
+ tokenCache.set(normalizedPath, data);
1195
+ return deepClone(data);
1196
+ } catch (error) {
1197
+ if (typeof window !== "undefined" && window.__DESIGN_SYSTEM_DEBUG__) {
1198
+ console.warn(`Error loading token file ${url}:`, error);
1199
+ }
849
1200
  }
850
- return null;
851
1201
  }
1202
+ return null;
852
1203
  }
853
1204
  function resolveReferences(tokens, palette) {
854
1205
  const resolved = JSON.parse(JSON.stringify(tokens));
@@ -1495,10 +1846,24 @@ function ThemeRingAsync({
1495
1846
 
1496
1847
  // src/themes/applyThemeSync.ts
1497
1848
  var STORAGE_KEY2 = "design-system-theme";
1849
+ function getTokenBaseCandidatesSync() {
1850
+ const bases = [];
1851
+ if (typeof window !== "undefined" && window.__THEME_TOKENS_BASE__) {
1852
+ bases.push(window.__THEME_TOKENS_BASE__);
1853
+ }
1854
+ const env = typeof globalThis !== "undefined" ? globalThis.process?.env : void 0;
1855
+ if (env?.DESIGN_SYSTEM_TOKENS_BASE) {
1856
+ bases.push(env.DESIGN_SYSTEM_TOKENS_BASE);
1857
+ }
1858
+ bases.push(EMBEDDED_TOKEN_BASE);
1859
+ bases.push("/tokens");
1860
+ return Array.from(new Set(bases.filter(Boolean)));
1861
+ }
1498
1862
  function applyThemeSync() {
1499
1863
  if (typeof window === "undefined" || typeof document === "undefined") {
1500
1864
  return;
1501
1865
  }
1866
+ const tokenBases = getTokenBaseCandidatesSync();
1502
1867
  let selectedThemes = getDefaultThemes();
1503
1868
  try {
1504
1869
  const stored = localStorage.getItem(STORAGE_KEY2);
@@ -1508,17 +1873,17 @@ function applyThemeSync() {
1508
1873
  } catch {
1509
1874
  }
1510
1875
  try {
1511
- const base = loadJSONSync("/tokens/base.json");
1876
+ const base = loadJSONSync("/tokens/base.json", tokenBases);
1512
1877
  if (!base) {
1513
1878
  return;
1514
1879
  }
1515
- const palettes = loadJSONSync("/tokens/palettes.json");
1880
+ const palettes = loadJSONSync("/tokens/palettes.json", tokenBases);
1516
1881
  const palette = palettes?.palette || {};
1517
1882
  let merged = deepMergeSync(base, { palette });
1518
1883
  for (const category of THEME_CATEGORY_ORDER) {
1519
1884
  const themeId = selectedThemes[category];
1520
1885
  if (!themeId) continue;
1521
- const themeData = loadJSONSync(`/tokens/themes/${category}/${themeId}.json`);
1886
+ const themeData = loadJSONSync(`/tokens/themes/${category}/${themeId}.json`, tokenBases);
1522
1887
  if (themeData) {
1523
1888
  merged = deepMergeSync(merged, themeData);
1524
1889
  }
@@ -1533,8 +1898,8 @@ ${Object.entries(mappedVars).map(([key, value]) => ` ${key}: ${value};`).join("
1533
1898
  if (!styleTag) {
1534
1899
  styleTag = document.createElement("style");
1535
1900
  styleTag.id = "dynamic-theme";
1536
- document.head.insertBefore(styleTag, document.head.firstChild);
1537
1901
  }
1902
+ document.head.appendChild(styleTag);
1538
1903
  styleTag.textContent = css;
1539
1904
  } catch (error) {
1540
1905
  if (typeof window !== "undefined" && window.__DESIGN_SYSTEM_DEBUG__) {
@@ -1542,22 +1907,33 @@ ${Object.entries(mappedVars).map(([key, value]) => ` ${key}: ${value};`).join("
1542
1907
  }
1543
1908
  }
1544
1909
  }
1545
- function loadJSONSync(path) {
1546
- try {
1547
- const xhr = new XMLHttpRequest();
1548
- xhr.open("GET", path, false);
1549
- xhr.send(null);
1550
- if (xhr.status === 404) {
1551
- return null;
1552
- }
1553
- if (xhr.status === 200 || xhr.status === 0) {
1554
- const contentType = xhr.getResponseHeader("content-type");
1555
- if (contentType && contentType.includes("application/json")) {
1556
- return JSON.parse(xhr.responseText);
1910
+ function loadJSONSync(path, bases) {
1911
+ const normalizedPath = normalizeTokenPath(path);
1912
+ for (const base of bases) {
1913
+ if (base === EMBEDDED_TOKEN_BASE) {
1914
+ const embedded = getEmbeddedToken(normalizedPath);
1915
+ if (embedded) {
1916
+ return deepCloneSync(embedded);
1557
1917
  }
1558
- return null;
1918
+ continue;
1919
+ }
1920
+ try {
1921
+ const xhr = new XMLHttpRequest();
1922
+ const url = base.endsWith("/") ? `${base}${normalizedPath}` : `${base}/${normalizedPath}`;
1923
+ xhr.open("GET", url, false);
1924
+ xhr.send(null);
1925
+ if (xhr.status === 404) {
1926
+ continue;
1927
+ }
1928
+ if (xhr.status === 200 || xhr.status === 0) {
1929
+ const contentType = xhr.getResponseHeader("content-type");
1930
+ if (contentType && contentType.includes("application/json")) {
1931
+ return JSON.parse(xhr.responseText);
1932
+ }
1933
+ continue;
1934
+ }
1935
+ } catch {
1559
1936
  }
1560
- } catch {
1561
1937
  }
1562
1938
  return null;
1563
1939
  }
@@ -1578,6 +1954,9 @@ function deepMergeSync(target, source) {
1578
1954
  }
1579
1955
  return output;
1580
1956
  }
1957
+ function deepCloneSync(value) {
1958
+ return JSON.parse(JSON.stringify(value));
1959
+ }
1581
1960
  function isObjectSync(item) {
1582
1961
  return item && typeof item === "object" && !Array.isArray(item);
1583
1962
  }