omnieye-theme-studio 1.0.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.
Files changed (54) hide show
  1. package/.claude/launch.json +11 -0
  2. package/.github/workflows/ci.yml +88 -0
  3. package/.github/workflows/promote.yml +48 -0
  4. package/Dockerfile +20 -0
  5. package/README.md +52 -0
  6. package/dist/assets/index-B0UK7Lt_.js +200 -0
  7. package/dist/index.html +18 -0
  8. package/index.html +18 -0
  9. package/nginx.conf +15 -0
  10. package/package.json +26 -0
  11. package/src/App.tsx +79 -0
  12. package/src/chrome/Footer.tsx +21 -0
  13. package/src/chrome/IconRail.tsx +49 -0
  14. package/src/chrome/StudioSubHeader.tsx +14 -0
  15. package/src/chrome/TopBar.tsx +35 -0
  16. package/src/main.tsx +23 -0
  17. package/src/panels/CardsPanel.tsx +75 -0
  18. package/src/panels/ColourPanel.tsx +175 -0
  19. package/src/panels/LoginPanel.tsx +62 -0
  20. package/src/panels/NotificationsPanel.tsx +37 -0
  21. package/src/panels/SurfacePanel.tsx +66 -0
  22. package/src/panels/TablesPanel.tsx +57 -0
  23. package/src/panels/ThemeManagement.tsx +178 -0
  24. package/src/panels/TypographyPanel.tsx +154 -0
  25. package/src/preview/CamerasPreview.tsx +140 -0
  26. package/src/preview/ComponentsPreview.tsx +146 -0
  27. package/src/preview/DashboardPreview.tsx +138 -0
  28. package/src/preview/LoginPreview.tsx +111 -0
  29. package/src/preview/OperationsPreview.tsx +103 -0
  30. package/src/preview/OrdersPreview.tsx +108 -0
  31. package/src/preview/PreviewPane.tsx +111 -0
  32. package/src/preview/ReportsPreview.tsx +140 -0
  33. package/src/preview/ToastLayer.tsx +34 -0
  34. package/src/preview/useFitCount.ts +68 -0
  35. package/src/shared/ChipGroup.tsx +52 -0
  36. package/src/shared/CropModal.tsx +73 -0
  37. package/src/shared/InfoBadge.tsx +13 -0
  38. package/src/shared/SectionAccordion.tsx +89 -0
  39. package/src/shared/StudioSlider.tsx +103 -0
  40. package/src/shared/SwatchPicker.tsx +44 -0
  41. package/src/shared/TabStrip.tsx +51 -0
  42. package/src/store/useStudioUi.ts +47 -0
  43. package/src/store/useThemeStore.ts +103 -0
  44. package/src/studio/StudioRail.tsx +167 -0
  45. package/src/theme/buildMuiTheme.ts +53 -0
  46. package/src/theme/color.ts +68 -0
  47. package/src/theme/presets.ts +17 -0
  48. package/src/theme/settings.ts +158 -0
  49. package/src/theme/studioTokens.ts +24 -0
  50. package/src/theme/tokens.ts +243 -0
  51. package/src/theme/useTokens.ts +10 -0
  52. package/tsconfig.json +23 -0
  53. package/tsconfig.tsbuildinfo +1 -0
  54. package/vite.config.ts +7 -0
@@ -0,0 +1,243 @@
1
+ // buildTokens — a faithful port of renderVals()'s token math (Theme Manager.dc.html).
2
+ // Pure function: settings -> the exact `t` token object + every derived scalar the
3
+ // preview screens consume (colours, type scale, density, radius, shadows, card/table/notif tokens).
4
+ // This is the single source of visual truth; screens read from it, so they match the HTML 1:1.
5
+
6
+ import { palette, hexToHSL, hslToHex, contrast, rgbaOf, type Palette } from './color';
7
+ import type { ThemeSettings } from './settings';
8
+
9
+ export function buildTokens(S: ThemeSettings) {
10
+ const pal: Palette = palette(S.primaryHex);
11
+ const spal: Palette = palette(S.secondaryHex);
12
+ const isDark = S.mode === 'dark';
13
+
14
+ const pMain = isDark ? pal[300] : S.primaryHex;
15
+ const sMain = isDark ? spal[300] : S.secondaryHex;
16
+
17
+ const t: Record<string, string> = {
18
+ pMain, pDark: pal[700], pLight: pal[300],
19
+ highlight: S.highlightHex || '#ffd400',
20
+ highlightSoft: rgbaOf(S.highlightHex || '#ffd400', 0.15),
21
+ highlightBd: rgbaOf(S.highlightHex || '#ffd400', 0.34),
22
+ pContrast: S.contrastMode === 'light' ? '#ffffff' : S.contrastMode === 'dark' ? 'rgba(0,0,0,0.87)' : (contrast(pMain, '#ffffff') >= 2.9 ? '#ffffff' : 'rgba(0,0,0,0.87)'),
23
+ pContainer: isDark ? rgbaOf(S.primaryHex, 0.26) : pal[50],
24
+ onPContainer: isDark ? pal[100] : pal[800],
25
+ sMain, sContrast: contrast(sMain, '#ffffff') >= 2.9 ? '#ffffff' : 'rgba(0,0,0,0.87)',
26
+ bgDefault: isDark ? '#121212' : '#f3f5f8', paper: isDark ? '#1e1e1e' : '#ffffff', paper2: isDark ? '#262630' : '#f6f8fb',
27
+ frameGutterBg: isDark ? '#0a0a0a' : '#e3e7ec', footerBg: isDark ? '#17171c' : '#eceef2',
28
+ textPrimary: isDark ? 'rgba(255,255,255,0.92)' : 'rgba(0,0,0,0.87)',
29
+ textSecondary: isDark ? 'rgba(255,255,255,0.62)' : 'rgba(0,0,0,0.6)',
30
+ textDisabled: isDark ? 'rgba(255,255,255,0.4)' : 'rgba(0,0,0,0.38)',
31
+ divider: isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.12)',
32
+ outline: isDark ? 'rgba(255,255,255,0.23)' : 'rgba(0,0,0,0.23)',
33
+ };
34
+
35
+ const SB = S.statusBold;
36
+ const errSoft = SB ? S.errorHex : rgbaOf(S.errorHex, isDark ? 0.22 : 0.13);
37
+ const warnSoft = SB ? S.warningHex : rgbaOf(S.warningHex, isDark ? 0.22 : 0.14);
38
+ const infoSoft = SB ? S.infoHex : rgbaOf(S.infoHex, isDark ? 0.22 : 0.13);
39
+ const sucSoft = SB ? S.successHex : rgbaOf(S.successHex, isDark ? 0.22 : 0.13);
40
+ const errTxt = SB ? '#ffffff' : (isDark ? palette(S.errorHex)[300] : S.errorHex);
41
+ const warnTxt = SB ? '#ffffff' : (isDark ? palette(S.warningHex)[300] : palette(S.warningHex)[700]);
42
+ const infoTxt = SB ? '#ffffff' : (isDark ? palette(S.infoHex)[300] : S.infoHex);
43
+ const sucTxt = SB ? '#ffffff' : (isDark ? palette(S.successHex)[300] : palette(S.successHex)[700]);
44
+
45
+ // surface tint + contrast (post-process base tokens)
46
+ const pHue = hexToHSL(S.primaryHex)[0];
47
+ const TINTMAP: Record<string, [number, number]> = { neutral: [220, 6], warm: [32, 26], cool: [210, 30], brand: [pHue, 42], slate: [214, 16], snow: [0, 0] };
48
+ let tn = TINTMAP[S.surfaceTint] || TINTMAP.neutral;
49
+ if (S.tintIntensity > 0) { const hasChip = !!(S.surfaceTint && TINTMAP[S.surfaceTint] && S.surfaceTint !== 'snow'); tn = [hasChip ? tn[0] : pHue, Math.round((hasChip ? tn[1] : 42) * S.tintIntensity / 100)]; }
50
+ if (isDark) { t.bgDefault = hslToHex(tn[0], Math.round(tn[1] * 0.7), 7); t.paper = hslToHex(tn[0], Math.round(tn[1] * 0.7), 13); t.paper2 = hslToHex(tn[0], Math.round(tn[1] * 0.85), 19); }
51
+ else if (S.surfaceTint === 'snow' && !(S.tintIntensity > 0)) { t.bgDefault = '#ffffff'; t.paper = '#ffffff'; t.paper2 = '#ffffff'; }
52
+ else { t.bgDefault = hslToHex(tn[0], Math.round(tn[1] * 0.9), S.surfaceTint === 'brand' ? 92 : S.surfaceTint === 'cool' ? 94 : 96); t.paper = hslToHex(tn[0], Math.round(tn[1] * 0.4), 99); t.paper2 = hslToHex(tn[0], Math.round(tn[1] * 0.85), S.surfaceTint === 'brand' ? 95 : 96.5); }
53
+ if (S.pageBgHex) t.bgDefault = S.pageBgHex;
54
+ if (S.cardBgHex) t.paper = S.cardBgHex;
55
+
56
+ const CONLV: Record<string, number> = { soft: 0, standard: 1, medium: 2, high: 3 };
57
+ const ci = (CONLV[S.contrast] !== undefined) ? CONLV[S.contrast] : 1;
58
+ const CTXT = isDark
59
+ ? [['rgba(255,255,255,0.82)', 'rgba(255,255,255,0.55)', 'rgba(255,255,255,0.09)', 'rgba(255,255,255,0.16)'], ['rgba(255,255,255,0.92)', 'rgba(255,255,255,0.62)', 'rgba(255,255,255,0.12)', 'rgba(255,255,255,0.23)'], ['rgba(255,255,255,0.96)', 'rgba(255,255,255,0.74)', 'rgba(255,255,255,0.18)', 'rgba(255,255,255,0.34)'], ['#ffffff', 'rgba(255,255,255,0.87)', 'rgba(255,255,255,0.26)', 'rgba(255,255,255,0.46)']]
60
+ : [['rgba(0,0,0,0.72)', 'rgba(0,0,0,0.5)', 'rgba(0,0,0,0.08)', 'rgba(0,0,0,0.16)'], ['rgba(0,0,0,0.87)', 'rgba(0,0,0,0.6)', 'rgba(0,0,0,0.12)', 'rgba(0,0,0,0.23)'], ['rgba(0,0,0,0.92)', 'rgba(0,0,0,0.7)', 'rgba(0,0,0,0.18)', 'rgba(0,0,0,0.34)'], ['#000000', 'rgba(0,0,0,0.8)', 'rgba(0,0,0,0.24)', 'rgba(0,0,0,0.46)']];
61
+ t.textPrimary = CTXT[ci][0]; t.textSecondary = CTXT[ci][1]; t.divider = CTXT[ci][2]; t.outline = CTXT[ci][3];
62
+ if (S.textPrimaryHex) t.textPrimary = S.textPrimaryHex;
63
+ if (S.textSecondaryHex) t.textSecondary = S.textSecondaryHex;
64
+ if (S.dividerHex) t.divider = S.dividerHex;
65
+
66
+ if (S.nestedStyle === 'lighter') t.paper2 = isDark ? hslToHex(tn[0], Math.round(tn[1] * 0.9), 22) : hslToHex(tn[0], Math.max(Math.round(tn[1] * 0.3), 4), 100);
67
+ else if (S.nestedStyle === 'bordered') t.paper2 = t.paper;
68
+ else t.paper2 = isDark ? hslToHex(tn[0], Math.round(tn[1] * 0.9), Math.max(3, 7 - 2)) : hslToHex(tn[0], Math.min(Math.round(tn[1] * 1.1), 60), Math.max(88, 94 - (tn[1] * 0.18)));
69
+
70
+ const btnBg0 = S.btnColorHex || pMain;
71
+ const accentBg = S.accentGradient ? ('linear-gradient(135deg,' + btnBg0 + ',' + (S.btnColorHex ? palette(S.btnColorHex)[700] : pal[isDark ? 500 : 700]) + ')') : btnBg0;
72
+ const btnText = S.btnTextHex || (contrast(btnBg0, '#ffffff') >= 2.9 ? '#ffffff' : 'rgba(0,0,0,0.87)');
73
+ const hc1 = S.h1Color || null, hc2 = S.h2Color || null, hc3 = S.h3Color || null, hc4 = S.h4Color || null, hc5 = S.h5Color || null, hc6 = S.h6Color || null;
74
+
75
+ // scales
76
+ const SIZES = [{ key: 'sm', px: 13 }, { key: 'df', px: 12 }, { key: 'lg', px: 18 }];
77
+ const sizeObj = SIZES.find((x) => x.key === S.textSize) || SIZES[1];
78
+ const base = S.baseFontPx > 0 ? S.baseFontPx : sizeObj.px;
79
+ const bodyLH = S.bodyLineHeight > 0 ? S.bodyLineHeight : 1.5;
80
+ const h1base = S.h1Size > 0 ? S.h1Size : 20;
81
+ const tDisplay = Math.round(h1base * 1.1), tH1 = h1base, tH2 = Math.max(Math.round(h1base * 0.9), 11), tTitle = Math.max(Math.round(h1base * 0.8), 11), tH4 = Math.max(Math.round(h1base * 0.7), 10), tH5 = Math.max(Math.round(h1base * 0.6), 10), tH6 = Math.max(Math.round(h1base * 0.5), 9);
82
+ const tBody = base, tBodySm = Math.round(base * 0.92), tCaption = Math.round(base * 0.8), tOverline = Math.round(base * 0.72);
83
+ const wH = S.headingWeight;
84
+ const headLH = S.headingLH > 0 ? S.headingLH : 1.2;
85
+ const bodyWeightEff = S.bodyWeight > 0 ? S.bodyWeight : 400;
86
+ const bwEmph = Math.min(900, bodyWeightEff + 200);
87
+
88
+ // density
89
+ const DENS: Record<string, number> = { compact: 0.82, default: 1, comfortable: 1.16 };
90
+ const f = S.densityScale > 0 ? (S.densityScale / 100) : (DENS[S.density] ?? 1);
91
+ const pad = Math.round(20 * f), padSm = Math.round(14 * f), gap = S.cardGapPx > 0 ? S.cardGapPx : Math.round(16 * f);
92
+ const rowPy = Math.round(11 * f * ({ compact: 0.66, default: 1, comfortable: 1.32 }[S.tableDensity] ?? 1));
93
+ const screenPad = S.containerPadPx > 0 ? S.containerPadPx : Math.round(10 * f);
94
+
95
+ // radius
96
+ const r = S.radiusVal; const rad = r + 'px', radSm = Math.max(Math.round(r * 0.6), 2) + 'px';
97
+
98
+ // shadows
99
+ const SH: Record<number, string> = {
100
+ 0: 'none',
101
+ 1: '0px 2px 1px -1px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 1px 3px 0px rgba(0,0,0,0.12)',
102
+ 2: '0px 3px 3px -2px rgba(0,0,0,0.2),0px 3px 4px 0px rgba(0,0,0,0.14),0px 1px 8px 0px rgba(0,0,0,0.12)',
103
+ 4: '0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12)',
104
+ 8: '0px 5px 5px -3px rgba(0,0,0,0.2),0px 8px 10px 1px rgba(0,0,0,0.14),0px 3px 14px 2px rgba(0,0,0,0.12)',
105
+ };
106
+ const dkb = '1px solid rgba(255,255,255,0.06)';
107
+ let cardShadow: string, cardBorder: string, btnShadow: string;
108
+ if (S.cardStyle === 'outlined') { cardShadow = 'none'; cardBorder = '1.5px solid ' + t.outline; btnShadow = 'none'; }
109
+ else if (S.cardStyle === 'soft') { cardShadow = SH[1]; cardBorder = isDark ? dkb : 'none'; btnShadow = SH[1]; }
110
+ else { cardShadow = SH[8]; cardBorder = isDark ? dkb : 'none'; btnShadow = SH[4]; } // floating
111
+ if (S.cardShadowPx > 0) cardShadow = '0 ' + Math.round(S.cardShadowPx * 0.45) + 'px ' + S.cardShadowPx + 'px rgba(16,24,40,' + (isDark ? 0.55 : 0.16) + ')';
112
+
113
+ // card header
114
+ let cardHdrBg = 'transparent', cardHdrBorder = '1px solid ' + t.divider, cardHdrAccent = 'none';
115
+ if (S.cardHeader === 'plain') cardHdrBorder = 'none';
116
+ else if (S.cardHeader === 'tinted') cardHdrBg = t.pContainer;
117
+ else if (S.cardHeader === 'accent') cardHdrAccent = 'inset 3px 0 0 ' + pMain;
118
+ if (S.headerStrength > 0) { cardHdrAccent = 'inset ' + Math.max(1, Math.round(S.headerStrength * 0.06)) + 'px 0 0 ' + pMain; cardHdrBg = rgbaOf(S.primaryHex, (isDark ? 0.14 : 0.08) * (S.headerStrength / 100)); }
119
+ if (S.cardHdrHex) cardHdrBg = S.cardHdrHex;
120
+
121
+ const appbarShadow = isDark ? 'none' : SH[4]; const appbarBorder = isDark ? '1px solid ' + t.divider : 'none';
122
+ const nestBorder = (S.nestedStyle === 'bordered') ? ('1.5px solid ' + t.outline) : ('1px solid ' + t.divider);
123
+ const nestShadow = (S.nestedDepth > 0) ? ('inset 3px 0 0 ' + rgbaOf(S.primaryHex, 0.55) + ', 0 ' + Math.round(S.nestedDepth * 0.4) + 'px ' + S.nestedDepth + 'px rgba(16,24,40,' + (isDark ? 0.42 : 0.1) + ')') : 'none';
124
+
125
+ // brand
126
+ const words = (S.appName || '').trim().split(/\s+/);
127
+ const iconInitials = words.slice(0, 2).map((w) => w[0] || '').join('').toUpperCase() || 'AP';
128
+ const hasLogo = !!(S.logoMode === 'image' && S.logoUrl);
129
+ const hasLoginImage = !!S.loginImage;
130
+ const headFam = S.headingFont || S.fontFamily;
131
+ const bodyFam = S.customFont || S.fontFamily;
132
+
133
+ // charts
134
+ const chartHue = hexToHSL(S.primaryHex)[0];
135
+ const rotc = (h: number, l?: number) => hslToHex(((h % 360) + 360) % 360, 62, (l === undefined) ? (isDark ? 60 : 46) : l);
136
+ const CHARTSCHEMES: Record<string, string[]> = {
137
+ brand: [pMain, sMain, rotc(chartHue + 42), rotc(chartHue + 205), rotc(chartHue + 120)],
138
+ categorical: [rotc(chartHue), rotc(chartHue + 72), rotc(chartHue + 144), rotc(chartHue + 216), rotc(chartHue + 288)],
139
+ sequential: [hslToHex(chartHue, 55, isDark ? 40 : 78), hslToHex(chartHue, 60, isDark ? 50 : 64), pMain, hslToHex(chartHue, 70, isDark ? 70 : 38), hslToHex(chartHue, 78, isDark ? 82 : 26)],
140
+ cbsafe: ['#0072B2', '#E69F00', '#009E73', '#CC79A7', '#56B4E9'],
141
+ warm: [rotc(8), rotc(28), rotc(45), rotc(350), rotc(18)],
142
+ cool: [rotc(200), rotc(220), rotc(255), rotc(180), rotc(235)],
143
+ };
144
+ const chartPalette = CHARTSCHEMES[S.chartScheme] || CHARTSCHEMES.brand;
145
+ const avatarColors = [pal[600], spal[600], hslToHex((chartHue + 42) % 360, 55, 46), hslToHex((chartHue + 205) % 360, 50, 46)];
146
+ const sevMap: Record<string, { cb: string; cc: string }> = { error: { cb: errSoft, cc: errTxt }, success: { cb: sucSoft, cc: sucTxt }, info: { cb: infoSoft, cc: infoTxt }, warning: { cb: warnSoft, cc: warnTxt } };
147
+
148
+ // media / overlays
149
+ const lightGlass = S.mapGlass === 'light';
150
+ const overlaySurface = lightGlass ? 'rgba(255,255,255,0.84)' : 'rgba(15,20,28,0.74)';
151
+ const onOverlay = lightGlass ? '#1c2733' : '#f3f6fa';
152
+ const overlayBorder = lightGlass ? 'rgba(0,0,0,0.12)' : 'rgba(255,255,255,0.18)';
153
+ const mediaSurface = '#0f141c';
154
+ const mediaBg = 'repeating-linear-gradient(45deg, rgba(255,255,255,0.04) 0px, rgba(255,255,255,0.04) 11px, rgba(255,255,255,0) 11px, rgba(255,255,255,0) 22px), radial-gradient(circle at 50% 38%, ' + rgbaOf(pMain, 0.22) + ', rgba(0,0,0,0) 60%), ' + mediaSurface;
155
+
156
+ // login gradients
157
+ const loginBrandGrad = 'linear-gradient(135deg,' + pMain + ',' + pal[700] + ')';
158
+ const loginScrimGrad = 'linear-gradient(180deg,' + rgbaOf(pal[900], 0.12) + ',' + rgbaOf(pal[900], 0.55) + ')';
159
+ const loginCenteredGrad = 'linear-gradient(135deg,' + pal[700] + ',' + pal[900] + ')';
160
+ const loginScrimGradC = 'linear-gradient(135deg,' + rgbaOf(pal[900], 0.55) + ',' + rgbaOf(S.primaryHex, 0.45) + ')';
161
+
162
+ // notification accent + placement
163
+ const NPOS: Record<string, Record<string, string>> = { 'top-right': { top: '14px', right: '14px' }, 'top-center': { top: '14px', left: '50%', transform: 'translateX(-50%)' }, 'bottom-right': { bottom: '14px', right: '14px' }, 'bottom-left': { bottom: '14px', left: '14px' } };
164
+ const npos = NPOS[S.notifPosition] || NPOS['top-right'];
165
+ const nacc = S.notifAccent === 'primary' ? { bar: pMain, icbg: t.pContainer, icc: t.onPContainer, dot: pMain }
166
+ : S.notifAccent === 'mono' ? { bar: t.textSecondary, icbg: t.paper2, icc: t.textPrimary, dot: t.textSecondary }
167
+ : { bar: S.warningHex, icbg: warnSoft, icc: warnTxt, dot: S.warningHex };
168
+ let toastDeco: Record<string, string>;
169
+ if (S.notifStyle === 'toast') toastDeco = { boxShadow: SH[8], border: (typeof cardBorder === 'string' && cardBorder !== 'none') ? cardBorder : 'none' };
170
+ else if (S.notifStyle === 'banner') toastDeco = { boxShadow: SH[2], borderLeft: '4px solid ' + nacc.bar };
171
+ else toastDeco = { boxShadow: 'none', border: '1px solid ' + t.divider };
172
+ const toastBg = S.notifCardHex || (isDark ? '#1e1e1e' : '#ffffff');
173
+
174
+ // table tokens
175
+ const TDENS: Record<string, string> = { compact: '7px', default: '11px', comfortable: '16px' };
176
+ const TCDENS: Record<string, string> = { compact: '6px', default: '12px', comfortable: '22px' };
177
+ const AVm: Record<string, string> = { top: 'flex-start', middle: 'center', bottom: 'flex-end' };
178
+ const AHm: Record<string, string> = { left: 'flex-start', center: 'center', right: 'flex-end' };
179
+ const THEAD: Record<string, string> = { plain: 'transparent', tinted: t.paper2, accent: t.highlightSoft };
180
+ const table = {
181
+ rowPy: TDENS[S.tableDensity] || '11px',
182
+ cellPx: TCDENS[S.tableColDensity] || '12px',
183
+ rowBorder: S.tableDividers ? ((S.tableRowDivW || 1) + 'px solid ' + t.divider) : 'none',
184
+ colBorder: S.tableColDividers ? ((S.tableColDivW || 1) + 'px solid ' + t.divider) : 'none',
185
+ headBg: THEAD[S.tableHeaderStyle] || t.paper2,
186
+ cellAI: AVm[S.tableCellAlignV] || 'center',
187
+ cellJC: AHm[S.tableCellAlignH] || 'flex-start',
188
+ stripeBg: isDark ? 'rgba(255,255,255,0.035)' : 'rgba(0,0,0,0.028)',
189
+ statusMap: {
190
+ Delivered: { sb: sucSoft, sc: sucTxt },
191
+ Processing: { sb: t.pContainer, sc: isDark ? t.pLight : t.pDark },
192
+ Pending: { sb: warnSoft, sc: warnTxt },
193
+ Shipped: { sb: infoSoft, sc: infoTxt },
194
+ } as Record<string, { sb: string; sc: string }>,
195
+ filters: ['All', 'Delivered', 'Processing', 'Pending'].map((label, i) => ({
196
+ label,
197
+ bg: i === 0 ? t.highlightSoft : 'transparent',
198
+ c: i === 0 ? t.textPrimary : t.textSecondary,
199
+ brd: i === 0 ? t.highlightBd : t.outline,
200
+ })),
201
+ };
202
+
203
+ // settings-screen summaries + category/nav data
204
+ const SIZE_LABEL: Record<string, string> = { sm: 'Small', df: 'Default', lg: 'Large' };
205
+ const DENS_LABEL: Record<string, string> = { compact: 'Compact', default: 'Default', comfortable: 'Comfortable' };
206
+ const CARDSTYLE_LABEL: Record<string, string> = { outlined: 'Outlined', soft: 'Soft', floating: 'Floating' };
207
+ const RAD_LABEL: Record<string, string> = { sharp: 'Sharp', rounded: 'Rounded', round: 'Round' };
208
+ const NAVS = [
209
+ { key: 'sidebar', label: 'Sidebar', desc: 'Full drawer', ic: '▤' },
210
+ { key: 'rail', label: 'Rail', desc: 'Icons only', ic: '▥' },
211
+ { key: 'topbar', label: 'Top Bar', desc: 'Horizontal', ic: '▦' },
212
+ ];
213
+ const typeSummary = S.fontFamily + ' · ' + (SIZE_LABEL[S.textSize] || 'Default');
214
+ const shapeSummary = (RAD_LABEL[S.radiusPreset] || 'Custom') + ' · ' + (DENS_LABEL[S.density] || 'Default') + ' · ' + (CARDSTYLE_LABEL[S.cardStyle] || 'Soft');
215
+ const SCATS = [
216
+ { label: 'General', ic: '◉' }, { label: 'Appearance', ic: '◧' }, { label: 'Modules', ic: '◫' },
217
+ { label: 'Team', ic: '◍' }, { label: 'Security', ic: '▣' }, { label: 'Billing', ic: '◳' },
218
+ ];
219
+ const settingsCats = SCATS.map((c, i) => ({ ...c, on: i === 1, bg: i === 1 ? t.highlightSoft : 'transparent', c: i === 1 ? t.textPrimary : t.textSecondary, bar: i === 1 ? '3px solid ' + t.highlight : '3px solid transparent' }));
220
+ const navSettingsChips = NAVS.map((x) => ({ ...x, active: S.navPattern === x.key, brd: S.navPattern === x.key ? pMain : t.divider, bg: S.navPattern === x.key ? t.pContainer : t.paper, color: S.navPattern === x.key ? t.onPContainer : t.textPrimary }));
221
+ const sidebarWidth = S.sidebarWidth;
222
+ const appBarHEff = S.appBarH > 0 ? S.appBarH : 56;
223
+ const appBarHLabel = S.appBarH > 0 ? S.appBarH + 'px' : 'Auto (56px)';
224
+
225
+ return {
226
+ pal, spal, isDark, pMain, sMain, t,
227
+ errSoft, warnSoft, infoSoft, sucSoft, errTxt, warnTxt, infoTxt, sucTxt, sevMap,
228
+ accentBg, btnText, hc1, hc2, hc3, hc4, hc5, hc6,
229
+ base, bodyLH, h1base, tDisplay, tH1, tH2, tTitle, tH4, tH5, tH6, tBody, tBodySm, tCaption, tOverline,
230
+ wH1: wH, wH2: wH, wH3: wH, wH4: wH, wH5: wH, wH6: wH, headLH, bodyWeightEff, bwEmph,
231
+ pad, padSm, gap, rowPy, screenPad, r, rad, radSm,
232
+ SH, cardShadow, cardBorder, btnShadow, cardHdrBg, cardHdrBorder, cardHdrAccent,
233
+ appbarShadow, appbarBorder, nestBorder, nestShadow,
234
+ iconInitials, hasLogo, hasLoginImage, headFam, bodyFam,
235
+ chartHue, chartPalette, avatarColors,
236
+ overlaySurface, onOverlay, overlayBorder, mediaBg,
237
+ loginBrandGrad, loginScrimGrad, loginCenteredGrad, loginScrimGradC,
238
+ npos, nacc, toastDeco, toastBg, btnCase: S.btnCase !== 'none' ? 'uppercase' : 'none',
239
+ table, typeSummary, shapeSummary, settingsCats, navSettingsChips, sidebarWidth, appBarHEff, appBarHLabel,
240
+ };
241
+ }
242
+
243
+ export type Tokens = ReturnType<typeof buildTokens>;
@@ -0,0 +1,10 @@
1
+ import { useMemo } from 'react';
2
+ import { useThemeStore } from '../store/useThemeStore';
3
+ import { buildTokens, type Tokens } from './tokens';
4
+
5
+ // Hook: the exact design tokens for the current settings. Preview screens read from this
6
+ // so they render 1:1 with the HTML app.
7
+ export function useTokens(): Tokens {
8
+ const settings = useThemeStore((s) => s.settings);
9
+ return useMemo(() => buildTokens(settings), [settings]);
10
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "moduleDetection": "force",
13
+ "noEmit": true,
14
+ "jsx": "react-jsx",
15
+ "strict": true,
16
+ "noUnusedLocals": true,
17
+ "noUnusedParameters": true,
18
+ "noFallthroughCasesInSwitch": true,
19
+ "baseUrl": ".",
20
+ "paths": { "@/*": ["src/*"] }
21
+ },
22
+ "include": ["src"]
23
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/app.tsx","./src/main.tsx","./src/chrome/footer.tsx","./src/chrome/iconrail.tsx","./src/chrome/studiosubheader.tsx","./src/chrome/topbar.tsx","./src/panels/cardspanel.tsx","./src/panels/colourpanel.tsx","./src/panels/loginpanel.tsx","./src/panels/notificationspanel.tsx","./src/panels/surfacepanel.tsx","./src/panels/tablespanel.tsx","./src/panels/thememanagement.tsx","./src/panels/typographypanel.tsx","./src/preview/cameraspreview.tsx","./src/preview/componentspreview.tsx","./src/preview/dashboardpreview.tsx","./src/preview/loginpreview.tsx","./src/preview/operationspreview.tsx","./src/preview/orderspreview.tsx","./src/preview/previewpane.tsx","./src/preview/reportspreview.tsx","./src/preview/toastlayer.tsx","./src/preview/usefitcount.ts","./src/shared/chipgroup.tsx","./src/shared/cropmodal.tsx","./src/shared/infobadge.tsx","./src/shared/sectionaccordion.tsx","./src/shared/studioslider.tsx","./src/shared/swatchpicker.tsx","./src/shared/tabstrip.tsx","./src/store/usestudioui.ts","./src/store/usethemestore.ts","./src/studio/studiorail.tsx","./src/theme/buildmuitheme.ts","./src/theme/color.ts","./src/theme/presets.ts","./src/theme/settings.ts","./src/theme/studiotokens.ts","./src/theme/tokens.ts","./src/theme/usetokens.ts"],"version":"5.9.3"}
package/vite.config.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: { port: 5173, open: true },
7
+ });