bosun 0.40.2 → 0.40.4

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/ui/modules/mui.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * ────────────────────────────────────────────────────────────── */
6
6
 
7
7
  import { h } from "preact";
8
+ import { useEffect, useMemo, useState } from "preact/hooks";
8
9
  import htm from "htm";
9
10
  import {
10
11
  Alert,
@@ -39,6 +40,560 @@ import {
39
40
 
40
41
  const html = htm.bind(h);
41
42
 
43
+ const DEFAULT_THEME_TOKENS = {
44
+ primary: "#da7756",
45
+ primaryContrast: "#1e1d1a",
46
+ secondary: "#3b82f6",
47
+ error: "#e5534b",
48
+ warning: "#f59e0b",
49
+ success: "#22c55e",
50
+ info: "#3b82f6",
51
+ backgroundDefault: "#1f1e1c",
52
+ backgroundPaper: "#2b2a27",
53
+ backgroundSurface: "#23221f",
54
+ textPrimary: "#e8e5de",
55
+ textSecondary: "#b5b0a6",
56
+ textHint: "#908b81",
57
+ divider: "rgba(255, 255, 255, 0.08)",
58
+ borderStrong: "rgba(255, 255, 255, 0.14)",
59
+ actionHoverDark: "rgba(255, 255, 255, 0.05)",
60
+ actionSelectedDark: "rgba(218, 119, 86, 0.18)",
61
+ actionDisabledDark: "rgba(255, 255, 255, 0.26)",
62
+ actionDisabledBackgroundDark: "rgba(255, 255, 255, 0.08)",
63
+ actionHoverLight: "rgba(15, 23, 42, 0.06)",
64
+ actionSelectedLight: "rgba(37, 99, 235, 0.12)",
65
+ actionDisabledLight: "rgba(15, 23, 42, 0.32)",
66
+ actionDisabledBackgroundLight: "rgba(15, 23, 42, 0.08)",
67
+ selectedGlowDark: "rgba(218,119,86,0.15)",
68
+ selectedGlowHoverDark: "rgba(218,119,86,0.22)",
69
+ selectedGlowLight: "rgba(37,99,235,0.10)",
70
+ selectedGlowHoverLight: "rgba(37,99,235,0.16)",
71
+ skeletonDark: "rgba(255,255,255,0.06)",
72
+ skeletonLight: "rgba(15,23,42,0.08)",
73
+ scrollbarDark: "rgba(255,255,255,0.12)",
74
+ scrollbarLight: "rgba(15,23,42,0.18)",
75
+ };
76
+
77
+ function readCssVar(styles, name, fallback) {
78
+ if (!styles) return fallback;
79
+ const value = styles.getPropertyValue(name)?.trim();
80
+ return value || fallback;
81
+ }
82
+
83
+ function parseColorChannels(color) {
84
+ const value = String(color || "").trim().toLowerCase();
85
+ if (!value) return null;
86
+
87
+ if (value.startsWith("#")) {
88
+ const hex = value.slice(1);
89
+ if (hex.length === 3 || hex.length === 4) {
90
+ return hex
91
+ .slice(0, 3)
92
+ .split("")
93
+ .map((part) => Number.parseInt(part + part, 16));
94
+ }
95
+ if (hex.length === 6 || hex.length === 8) {
96
+ return [hex.slice(0, 2), hex.slice(2, 4), hex.slice(4, 6)].map((part) => Number.parseInt(part, 16));
97
+ }
98
+ return null;
99
+ }
100
+
101
+ const rgbMatch = value.match(/^rgba?\(([^)]+)\)$/);
102
+ if (!rgbMatch) return null;
103
+ const channels = rgbMatch[1]
104
+ .split(",")
105
+ .slice(0, 3)
106
+ .map((part) => Number.parseFloat(part.trim()));
107
+ return channels.every((part) => Number.isFinite(part)) ? channels : null;
108
+ }
109
+
110
+ function isLightColor(color) {
111
+ const channels = parseColorChannels(color);
112
+ if (!channels) return false;
113
+ const [red, green, blue] = channels;
114
+ const luminance = (0.2126 * red + 0.7152 * green + 0.0722 * blue) / 255;
115
+ return luminance > 0.62;
116
+ }
117
+
118
+ function resolveThemeTokens() {
119
+ if (typeof document === "undefined" || typeof getComputedStyle !== "function") {
120
+ return {
121
+ ...DEFAULT_THEME_TOKENS,
122
+ mode: "dark",
123
+ actionHover: DEFAULT_THEME_TOKENS.actionHoverDark,
124
+ actionSelected: DEFAULT_THEME_TOKENS.actionSelectedDark,
125
+ actionDisabled: DEFAULT_THEME_TOKENS.actionDisabledDark,
126
+ actionDisabledBackground: DEFAULT_THEME_TOKENS.actionDisabledBackgroundDark,
127
+ selectedGlow: DEFAULT_THEME_TOKENS.selectedGlowDark,
128
+ selectedGlowHover: DEFAULT_THEME_TOKENS.selectedGlowHoverDark,
129
+ skeleton: DEFAULT_THEME_TOKENS.skeletonDark,
130
+ scrollbar: DEFAULT_THEME_TOKENS.scrollbarDark,
131
+ };
132
+ }
133
+
134
+ const styles = getComputedStyle(document.documentElement);
135
+ const backgroundDefault = readCssVar(styles, "--bg-primary", DEFAULT_THEME_TOKENS.backgroundDefault);
136
+ const computedScheme = styles.colorScheme?.includes("light")
137
+ ? "light"
138
+ : styles.colorScheme?.includes("dark")
139
+ ? "dark"
140
+ : "";
141
+ const mode = computedScheme || (isLightColor(backgroundDefault) ? "light" : "dark");
142
+ const isLight = mode === "light";
143
+
144
+ return {
145
+ ...DEFAULT_THEME_TOKENS,
146
+ mode,
147
+ primary: readCssVar(styles, "--accent", DEFAULT_THEME_TOKENS.primary),
148
+ primaryContrast: readCssVar(styles, "--accent-text", DEFAULT_THEME_TOKENS.primaryContrast),
149
+ backgroundDefault,
150
+ backgroundPaper: readCssVar(styles, "--bg-card", DEFAULT_THEME_TOKENS.backgroundPaper),
151
+ backgroundSurface: readCssVar(styles, "--bg-surface", DEFAULT_THEME_TOKENS.backgroundSurface),
152
+ textPrimary: readCssVar(styles, "--text-primary", DEFAULT_THEME_TOKENS.textPrimary),
153
+ textSecondary: readCssVar(styles, "--text-secondary", DEFAULT_THEME_TOKENS.textSecondary),
154
+ textHint: readCssVar(styles, "--text-hint", DEFAULT_THEME_TOKENS.textHint),
155
+ divider: readCssVar(styles, "--border", DEFAULT_THEME_TOKENS.divider),
156
+ borderStrong: readCssVar(styles, "--border-strong", DEFAULT_THEME_TOKENS.borderStrong),
157
+ actionHover: isLight ? DEFAULT_THEME_TOKENS.actionHoverLight : DEFAULT_THEME_TOKENS.actionHoverDark,
158
+ actionSelected: isLight ? DEFAULT_THEME_TOKENS.actionSelectedLight : DEFAULT_THEME_TOKENS.actionSelectedDark,
159
+ actionDisabled: isLight ? DEFAULT_THEME_TOKENS.actionDisabledLight : DEFAULT_THEME_TOKENS.actionDisabledDark,
160
+ actionDisabledBackground: isLight ? DEFAULT_THEME_TOKENS.actionDisabledBackgroundLight : DEFAULT_THEME_TOKENS.actionDisabledBackgroundDark,
161
+ selectedGlow: isLight ? DEFAULT_THEME_TOKENS.selectedGlowLight : DEFAULT_THEME_TOKENS.selectedGlowDark,
162
+ selectedGlowHover: isLight ? DEFAULT_THEME_TOKENS.selectedGlowHoverLight : DEFAULT_THEME_TOKENS.selectedGlowHoverDark,
163
+ skeleton: isLight ? DEFAULT_THEME_TOKENS.skeletonLight : DEFAULT_THEME_TOKENS.skeletonDark,
164
+ scrollbar: isLight ? DEFAULT_THEME_TOKENS.scrollbarLight : DEFAULT_THEME_TOKENS.scrollbarDark,
165
+ };
166
+ }
167
+
168
+ function buildThemeOptions(tokens = resolveThemeTokens()) {
169
+ return {
170
+ palette: {
171
+ mode: tokens.mode,
172
+ primary: {
173
+ main: tokens.primary,
174
+ light: tokens.primary,
175
+ dark: tokens.primary,
176
+ contrastText: tokens.primaryContrast,
177
+ },
178
+ secondary: {
179
+ main: tokens.secondary,
180
+ light: tokens.secondary,
181
+ dark: tokens.secondary,
182
+ },
183
+ error: { main: tokens.error },
184
+ warning: { main: tokens.warning },
185
+ success: { main: tokens.success },
186
+ info: { main: tokens.info },
187
+ background: {
188
+ default: tokens.backgroundDefault,
189
+ paper: tokens.backgroundPaper,
190
+ },
191
+ text: {
192
+ primary: tokens.textPrimary,
193
+ secondary: tokens.textSecondary,
194
+ disabled: tokens.textHint,
195
+ },
196
+ divider: tokens.divider,
197
+ action: {
198
+ active: tokens.textSecondary,
199
+ hover: tokens.actionHover,
200
+ selected: tokens.actionSelected,
201
+ disabled: tokens.actionDisabled,
202
+ disabledBackground: tokens.actionDisabledBackground,
203
+ },
204
+ },
205
+ typography: {
206
+ fontFamily:
207
+ '"Instrument Sans", "SF Pro Text", "SF Pro Display", -apple-system, BlinkMacSystemFont, "IBM Plex Sans", "Segoe UI", sans-serif',
208
+ fontSize: 13,
209
+ h1: { fontSize: "1.75rem", fontWeight: 700, letterSpacing: "-0.02em" },
210
+ h2: { fontSize: "1.4rem", fontWeight: 700, letterSpacing: "-0.02em" },
211
+ h3: { fontSize: "1.15rem", fontWeight: 600, letterSpacing: "-0.01em" },
212
+ h4: { fontSize: "1rem", fontWeight: 600 },
213
+ h5: { fontSize: "0.9rem", fontWeight: 600 },
214
+ h6: { fontSize: "0.8rem", fontWeight: 600 },
215
+ body1: { fontSize: "0.875rem", lineHeight: 1.6 },
216
+ body2: { fontSize: "0.8125rem", lineHeight: 1.5 },
217
+ caption: { fontSize: "0.75rem", color: tokens.textHint },
218
+ button: { textTransform: "none", fontWeight: 500 },
219
+ overline: { fontSize: "0.65rem", fontWeight: 600, letterSpacing: "0.05em" },
220
+ },
221
+ shape: {
222
+ borderRadius: 6,
223
+ },
224
+ shadows: [
225
+ "none",
226
+ "0 1px 2px rgba(5,10,18,0.35)",
227
+ "0 2px 4px rgba(5,10,18,0.3)",
228
+ "0 4px 8px rgba(5,10,18,0.28)",
229
+ "0 6px 16px rgba(5,10,18,0.28)",
230
+ "0 8px 20px rgba(5,10,18,0.3)",
231
+ "0 12px 28px rgba(5,10,18,0.32)",
232
+ "0 14px 32px rgba(5,10,18,0.32)",
233
+ "0 16px 36px rgba(5,10,18,0.34)",
234
+ "0 18px 40px rgba(5,10,18,0.34)",
235
+ "0 20px 48px rgba(5,10,18,0.38)",
236
+ ...Array(14).fill("0 20px 48px rgba(5,10,18,0.38)"),
237
+ ],
238
+ zIndex: {
239
+ modal: 11000,
240
+ snackbar: 11050,
241
+ tooltip: 11100,
242
+ },
243
+ components: {
244
+ MuiCssBaseline: {
245
+ styleOverrides: {
246
+ body: {
247
+ scrollbarColor: `${tokens.scrollbar} transparent`,
248
+ "&::-webkit-scrollbar": { width: 6 },
249
+ "&::-webkit-scrollbar-thumb": {
250
+ background: tokens.scrollbar,
251
+ borderRadius: 3,
252
+ },
253
+ },
254
+ },
255
+ },
256
+ MuiButton: {
257
+ defaultProps: { disableElevation: true, size: "small" },
258
+ styleOverrides: {
259
+ root: {
260
+ borderRadius: 6,
261
+ textTransform: "none",
262
+ fontWeight: 500,
263
+ fontSize: "0.8125rem",
264
+ lineHeight: 1.4,
265
+ padding: "6px 14px",
266
+ },
267
+ sizeSmall: { padding: "4px 10px", fontSize: "0.75rem" },
268
+ containedPrimary: {
269
+ color: tokens.primaryContrast,
270
+ backgroundColor: tokens.primary,
271
+ "&:hover": { backgroundColor: tokens.primary },
272
+ },
273
+ outlined: {
274
+ borderColor: tokens.borderStrong,
275
+ color: tokens.textPrimary,
276
+ },
277
+ },
278
+ },
279
+ MuiIconButton: {
280
+ defaultProps: { size: "small" },
281
+ styleOverrides: {
282
+ root: { borderRadius: 6, color: tokens.textSecondary },
283
+ sizeSmall: { padding: 4 },
284
+ },
285
+ },
286
+ MuiPaper: {
287
+ defaultProps: { elevation: 0 },
288
+ styleOverrides: {
289
+ root: {
290
+ backgroundImage: "none",
291
+ backgroundColor: tokens.backgroundPaper,
292
+ color: tokens.textPrimary,
293
+ border: `1px solid ${tokens.divider}`,
294
+ },
295
+ },
296
+ },
297
+ MuiCard: {
298
+ defaultProps: { elevation: 0 },
299
+ styleOverrides: {
300
+ root: {
301
+ backgroundImage: "none",
302
+ backgroundColor: tokens.backgroundPaper,
303
+ color: tokens.textPrimary,
304
+ border: `1px solid ${tokens.divider}`,
305
+ borderRadius: 8,
306
+ },
307
+ },
308
+ },
309
+ MuiChip: {
310
+ defaultProps: { size: "small" },
311
+ styleOverrides: {
312
+ root: {
313
+ fontWeight: 500,
314
+ fontSize: "0.7rem",
315
+ height: 22,
316
+ color: tokens.textSecondary,
317
+ backgroundColor: tokens.backgroundPaper,
318
+ borderColor: tokens.divider,
319
+ },
320
+ sizeSmall: { height: 20 },
321
+ },
322
+ },
323
+ MuiDialog: {
324
+ styleOverrides: {
325
+ paper: {
326
+ backgroundImage: "none",
327
+ backgroundColor: tokens.backgroundPaper,
328
+ borderRadius: 12,
329
+ color: tokens.textPrimary,
330
+ border: `1px solid ${tokens.borderStrong}`,
331
+ },
332
+ },
333
+ },
334
+ MuiTooltip: {
335
+ defaultProps: { arrow: true, enterDelay: 400 },
336
+ styleOverrides: {
337
+ tooltip: {
338
+ backgroundColor: tokens.backgroundDefault,
339
+ color: tokens.textPrimary,
340
+ border: `1px solid ${tokens.borderStrong}`,
341
+ fontSize: "0.75rem",
342
+ },
343
+ arrow: { color: tokens.backgroundDefault },
344
+ },
345
+ },
346
+ MuiTextField: {
347
+ defaultProps: { size: "small", variant: "outlined" },
348
+ styleOverrides: {
349
+ root: {
350
+ "& .MuiOutlinedInput-root": {
351
+ fontSize: "0.8125rem",
352
+ borderRadius: 6,
353
+ color: tokens.textPrimary,
354
+ "& fieldset": {
355
+ borderColor: tokens.borderStrong,
356
+ },
357
+ "&:hover fieldset": {
358
+ borderColor: tokens.textSecondary,
359
+ },
360
+ },
361
+ },
362
+ },
363
+ },
364
+ MuiSelect: {
365
+ defaultProps: { size: "small" },
366
+ },
367
+ MuiSwitch: {
368
+ defaultProps: { size: "small" },
369
+ },
370
+ MuiAccordion: {
371
+ defaultProps: { disableGutters: true, elevation: 0 },
372
+ styleOverrides: {
373
+ root: {
374
+ backgroundColor: "transparent",
375
+ border: `1px solid ${tokens.divider}`,
376
+ borderRadius: "6px !important",
377
+ "&:before": { display: "none" },
378
+ "&.Mui-expanded": { margin: 0 },
379
+ },
380
+ },
381
+ },
382
+ MuiAccordionSummary: {
383
+ styleOverrides: {
384
+ root: {
385
+ minHeight: 40,
386
+ padding: "0 12px",
387
+ fontSize: "0.8125rem",
388
+ fontWeight: 500,
389
+ color: tokens.textPrimary,
390
+ "&.Mui-expanded": { minHeight: 40 },
391
+ },
392
+ content: { margin: "8px 0", "&.Mui-expanded": { margin: "8px 0" } },
393
+ },
394
+ },
395
+ MuiAccordionDetails: {
396
+ styleOverrides: {
397
+ root: { padding: "4px 12px 12px", color: tokens.textSecondary },
398
+ },
399
+ },
400
+ MuiList: {
401
+ styleOverrides: {
402
+ root: { padding: 0 },
403
+ },
404
+ },
405
+ MuiListItem: {
406
+ styleOverrides: {
407
+ root: { padding: "4px 12px" },
408
+ },
409
+ },
410
+ MuiListItemButton: {
411
+ styleOverrides: {
412
+ root: {
413
+ borderRadius: 6,
414
+ padding: "6px 12px",
415
+ color: tokens.textPrimary,
416
+ "&.Mui-selected": {
417
+ backgroundColor: tokens.selectedGlow,
418
+ "&:hover": { backgroundColor: tokens.selectedGlowHover },
419
+ },
420
+ },
421
+ },
422
+ },
423
+ MuiDivider: {
424
+ styleOverrides: {
425
+ root: { borderColor: tokens.divider },
426
+ },
427
+ },
428
+ MuiAlert: {
429
+ styleOverrides: {
430
+ root: { borderRadius: 8, fontSize: "0.8125rem" },
431
+ },
432
+ },
433
+ MuiSnackbar: {
434
+ defaultProps: {
435
+ anchorOrigin: { vertical: "bottom", horizontal: "center" },
436
+ autoHideDuration: 4000,
437
+ },
438
+ },
439
+ MuiSkeleton: {
440
+ defaultProps: { animation: "wave" },
441
+ styleOverrides: {
442
+ root: { backgroundColor: tokens.skeleton },
443
+ },
444
+ },
445
+ MuiTab: {
446
+ styleOverrides: {
447
+ root: {
448
+ textTransform: "none",
449
+ fontWeight: 500,
450
+ fontSize: "0.8125rem",
451
+ minHeight: 40,
452
+ padding: "8px 14px",
453
+ color: tokens.textPrimary,
454
+ opacity: 0.95,
455
+ flexDirection: "row",
456
+ gap: 8,
457
+ "&.Mui-selected": {
458
+ color: tokens.primary,
459
+ opacity: 1,
460
+ },
461
+ "&:hover": {
462
+ opacity: 1,
463
+ },
464
+ "& .MuiTab-iconWrapper": { marginBottom: "0 !important", marginRight: 0 },
465
+ },
466
+ iconWrapper: {
467
+ display: "flex",
468
+ alignItems: "center",
469
+ justifyContent: "center",
470
+ flexShrink: 0,
471
+ width: 20,
472
+ height: 20,
473
+ "& svg": { width: "100%", height: "100%" },
474
+ },
475
+ },
476
+ },
477
+ MuiTabs: {
478
+ styleOverrides: {
479
+ root: { minHeight: 40 },
480
+ indicator: {
481
+ backgroundColor: tokens.primary,
482
+ height: 2,
483
+ },
484
+ },
485
+ },
486
+ MuiBottomNavigation: {
487
+ styleOverrides: {
488
+ root: {
489
+ backgroundColor: tokens.backgroundSurface,
490
+ borderTop: `1px solid ${tokens.divider}`,
491
+ },
492
+ },
493
+ },
494
+ MuiBottomNavigationAction: {
495
+ styleOverrides: {
496
+ root: {
497
+ color: tokens.textHint,
498
+ minWidth: 64,
499
+ paddingTop: 8,
500
+ paddingBottom: 8,
501
+ transition: "color 0.18s ease, transform 0.18s ease",
502
+ "&.Mui-selected": {
503
+ color: tokens.primary,
504
+ },
505
+ "& svg": { width: 24, height: 24, flexShrink: 0 },
506
+ },
507
+ },
508
+ },
509
+ MuiMenu: {
510
+ styleOverrides: {
511
+ paper: {
512
+ backgroundColor: tokens.backgroundSurface,
513
+ border: `1px solid ${tokens.borderStrong}`,
514
+ borderRadius: 10,
515
+ color: tokens.textPrimary,
516
+ boxShadow: "0 8px 32px rgba(0,0,0,0.4)",
517
+ },
518
+ },
519
+ },
520
+ MuiMenuItem: {
521
+ styleOverrides: {
522
+ root: {
523
+ fontSize: "0.8125rem",
524
+ borderRadius: 6,
525
+ margin: "2px 4px",
526
+ padding: "6px 12px",
527
+ color: tokens.textPrimary,
528
+ },
529
+ },
530
+ },
531
+ MuiLinearProgress: {
532
+ styleOverrides: {
533
+ root: { borderRadius: 4, height: 6 },
534
+ bar: { borderRadius: 4 },
535
+ },
536
+ },
537
+ MuiCircularProgress: {
538
+ defaultProps: { size: 20, thickness: 3 },
539
+ },
540
+ MuiAvatar: {
541
+ styleOverrides: {
542
+ root: {
543
+ fontSize: "0.8125rem",
544
+ fontWeight: 600,
545
+ backgroundColor: tokens.primary,
546
+ color: tokens.primaryContrast,
547
+ },
548
+ },
549
+ },
550
+ MuiBadge: {
551
+ styleOverrides: {
552
+ badge: { fontSize: "0.65rem", fontWeight: 600 },
553
+ },
554
+ },
555
+ MuiFab: {
556
+ defaultProps: { size: "small" },
557
+ styleOverrides: {
558
+ root: { boxShadow: "0 4px 12px rgba(0,0,0,0.3)" },
559
+ },
560
+ },
561
+ MuiDrawer: {
562
+ styleOverrides: {
563
+ paper: {
564
+ backgroundColor: tokens.backgroundDefault,
565
+ color: tokens.textPrimary,
566
+ borderColor: tokens.divider,
567
+ },
568
+ },
569
+ },
570
+ MuiAppBar: {
571
+ defaultProps: { elevation: 0, color: "inherit" },
572
+ styleOverrides: {
573
+ root: {
574
+ backgroundColor: tokens.backgroundDefault,
575
+ color: tokens.textPrimary,
576
+ borderBottom: `1px solid ${tokens.divider}`,
577
+ },
578
+ },
579
+ },
580
+ },
581
+ };
582
+ }
583
+
584
+ const DARK_THEME_TOKENS = {
585
+ ...DEFAULT_THEME_TOKENS,
586
+ mode: "dark",
587
+ actionHover: DEFAULT_THEME_TOKENS.actionHoverDark,
588
+ actionSelected: DEFAULT_THEME_TOKENS.actionSelectedDark,
589
+ actionDisabled: DEFAULT_THEME_TOKENS.actionDisabledDark,
590
+ actionDisabledBackground: DEFAULT_THEME_TOKENS.actionDisabledBackgroundDark,
591
+ selectedGlow: DEFAULT_THEME_TOKENS.selectedGlowDark,
592
+ selectedGlowHover: DEFAULT_THEME_TOKENS.selectedGlowHoverDark,
593
+ skeleton: DEFAULT_THEME_TOKENS.skeletonDark,
594
+ scrollbar: DEFAULT_THEME_TOKENS.scrollbarDark,
595
+ };
596
+
42
597
  // Re-export everything apps need from @mui/material
43
598
  export {
44
599
  Alert,
@@ -68,16 +623,13 @@ export {
68
623
  Typography,
69
624
  };
70
625
  export {
71
- // Theme / styling
72
626
  ThemeProvider,
73
627
  createTheme,
74
628
  styled,
75
629
  useTheme,
76
630
  alpha,
77
- // Layout
78
631
  Container,
79
632
  Grid,
80
- // Inputs
81
633
  ButtonGroup,
82
634
  TextField,
83
635
  Select,
@@ -98,7 +650,6 @@ export {
98
650
  ToggleButtonGroup,
99
651
  Autocomplete,
100
652
  Rating,
101
- // Data display
102
653
  AvatarGroup,
103
654
  List,
104
655
  ListItem,
@@ -115,7 +666,6 @@ export {
115
666
  TableHead,
116
667
  TableRow,
117
668
  TableSortLabel,
118
- // Feedback
119
669
  AlertTitle,
120
670
  Backdrop,
121
671
  LinearProgress,
@@ -125,7 +675,6 @@ export {
125
675
  DialogContentText,
126
676
  DialogActions,
127
677
  Skeleton,
128
- // Surfaces
129
678
  Accordion,
130
679
  AccordionSummary,
131
680
  AccordionDetails,
@@ -136,7 +685,6 @@ export {
136
685
  CardActions,
137
686
  CardActionArea,
138
687
  CardMedia,
139
- // Navigation
140
688
  Breadcrumbs,
141
689
  Link,
142
690
  Collapse,
@@ -147,7 +695,6 @@ export {
147
695
  Step,
148
696
  StepLabel,
149
697
  Stepper,
150
- // Utils
151
698
  ClickAwayListener,
152
699
  Modal,
153
700
  Popover,
@@ -158,406 +705,62 @@ export {
158
705
  Slide,
159
706
  Zoom,
160
707
  SvgIcon,
161
- // Colors
162
708
  colors,
163
709
  } from "@mui/material";
164
710
 
165
711
  /**
166
- * VirtEngine dark theme matches the existing CSS custom properties.
167
- * Uses Bosun brand colors (#da7756 primary, warm dark palette).
712
+ * VirtEngine base theme export for compatibility.
713
+ * `VeTheme` resolves a live theme from the current CSS variables.
168
714
  */
169
- export const veTheme = createMuiTheme({
170
- palette: {
171
- mode: "dark",
172
- primary: {
173
- main: "#da7756",
174
- light: "#e5886a",
175
- dark: "#c66a4d",
176
- contrastText: "#1e1d1a",
177
- },
178
- secondary: {
179
- main: "#3b82f6",
180
- light: "#60a5fa",
181
- dark: "#2563eb",
182
- },
183
- error: {
184
- main: "#e5534b",
185
- },
186
- warning: {
187
- main: "#f59e0b",
188
- },
189
- success: {
190
- main: "#22c55e",
191
- },
192
- info: {
193
- main: "#3b82f6",
194
- },
195
- background: {
196
- default: "#1f1e1c",
197
- paper: "#2b2a27",
198
- },
199
- text: {
200
- primary: "#e8e5de",
201
- secondary: "#b5b0a6",
202
- disabled: "#908b81",
203
- },
204
- divider: "rgba(255, 255, 255, 0.08)",
205
- action: {
206
- active: "#b5b0a6",
207
- hover: "rgba(255, 255, 255, 0.05)",
208
- selected: "rgba(218, 119, 86, 0.18)",
209
- disabled: "rgba(255, 255, 255, 0.26)",
210
- disabledBackground: "rgba(255, 255, 255, 0.08)",
211
- },
212
- },
213
- typography: {
214
- fontFamily:
215
- '"Instrument Sans", "SF Pro Text", "SF Pro Display", -apple-system, BlinkMacSystemFont, "IBM Plex Sans", "Segoe UI", sans-serif',
216
- fontSize: 13,
217
- h1: { fontSize: "1.75rem", fontWeight: 700, letterSpacing: "-0.02em" },
218
- h2: { fontSize: "1.4rem", fontWeight: 700, letterSpacing: "-0.02em" },
219
- h3: { fontSize: "1.15rem", fontWeight: 600, letterSpacing: "-0.01em" },
220
- h4: { fontSize: "1rem", fontWeight: 600 },
221
- h5: { fontSize: "0.9rem", fontWeight: 600 },
222
- h6: { fontSize: "0.8rem", fontWeight: 600 },
223
- body1: { fontSize: "0.875rem", lineHeight: 1.6 },
224
- body2: { fontSize: "0.8125rem", lineHeight: 1.5 },
225
- caption: { fontSize: "0.75rem", color: "#908b81" },
226
- button: { textTransform: "none", fontWeight: 500 },
227
- overline: { fontSize: "0.65rem", fontWeight: 600, letterSpacing: "0.05em" },
228
- },
229
- shape: {
230
- borderRadius: 6,
231
- },
232
- shadows: [
233
- "none",
234
- "0 1px 2px rgba(5,10,18,0.35)",
235
- "0 2px 4px rgba(5,10,18,0.3)",
236
- "0 4px 8px rgba(5,10,18,0.28)",
237
- "0 6px 16px rgba(5,10,18,0.28)",
238
- "0 8px 20px rgba(5,10,18,0.3)",
239
- "0 12px 28px rgba(5,10,18,0.32)",
240
- "0 14px 32px rgba(5,10,18,0.32)",
241
- "0 16px 36px rgba(5,10,18,0.34)",
242
- "0 18px 40px rgba(5,10,18,0.34)",
243
- "0 20px 48px rgba(5,10,18,0.38)",
244
- ...Array(14).fill("0 20px 48px rgba(5,10,18,0.38)"),
245
- ],
246
- // Bosun uses a custom modal sheet at z-index 10000. Keep MUI overlays above it.
247
- zIndex: {
248
- modal: 11000,
249
- snackbar: 11050,
250
- tooltip: 11100,
251
- },
252
- components: {
253
- MuiCssBaseline: {
254
- styleOverrides: {
255
- body: {
256
- scrollbarColor: "rgba(255,255,255,0.12) transparent",
257
- "&::-webkit-scrollbar": { width: 6 },
258
- "&::-webkit-scrollbar-thumb": {
259
- background: "rgba(255,255,255,0.12)",
260
- borderRadius: 3,
261
- },
262
- },
263
- },
264
- },
265
- MuiButton: {
266
- defaultProps: { disableElevation: true, size: "small" },
267
- styleOverrides: {
268
- root: {
269
- borderRadius: 6,
270
- textTransform: "none",
271
- fontWeight: 500,
272
- fontSize: "0.8125rem",
273
- lineHeight: 1.4,
274
- padding: "6px 14px",
275
- },
276
- sizeSmall: { padding: "4px 10px", fontSize: "0.75rem" },
277
- containedPrimary: {
278
- "&:hover": { backgroundColor: "#e5886a" },
279
- },
280
- },
281
- },
282
- MuiIconButton: {
283
- defaultProps: { size: "small" },
284
- styleOverrides: {
285
- root: { borderRadius: 6, color: "#b5b0a6" },
286
- sizeSmall: { padding: 4 },
287
- },
288
- },
289
- MuiPaper: {
290
- defaultProps: { elevation: 0 },
291
- styleOverrides: {
292
- root: {
293
- backgroundImage: "none",
294
- backgroundColor: "#2b2a27",
295
- border: "1px solid rgba(255,255,255,0.06)",
296
- },
297
- },
298
- },
299
- MuiCard: {
300
- defaultProps: { elevation: 0 },
301
- styleOverrides: {
302
- root: {
303
- backgroundImage: "none",
304
- backgroundColor: "#2b2a27",
305
- border: "1px solid rgba(255,255,255,0.06)",
306
- borderRadius: 8,
307
- },
308
- },
309
- },
310
- MuiChip: {
311
- defaultProps: { size: "small" },
312
- styleOverrides: {
313
- root: { fontWeight: 500, fontSize: "0.7rem", height: 22 },
314
- sizeSmall: { height: 20 },
315
- },
316
- },
317
- MuiDialog: {
318
- styleOverrides: {
319
- paper: {
320
- backgroundImage: "none",
321
- backgroundColor: "#2b2a27",
322
- borderRadius: 12,
323
- border: "1px solid rgba(255,255,255,0.1)",
324
- },
325
- },
326
- },
327
- MuiTooltip: {
328
- defaultProps: { arrow: true, enterDelay: 400 },
329
- styleOverrides: {
330
- tooltip: {
331
- backgroundColor: "#1f1e1c",
332
- border: "1px solid rgba(255,255,255,0.1)",
333
- fontSize: "0.75rem",
334
- },
335
- arrow: { color: "#1f1e1c" },
336
- },
337
- },
338
- MuiTextField: {
339
- defaultProps: { size: "small", variant: "outlined" },
340
- styleOverrides: {
341
- root: {
342
- "& .MuiOutlinedInput-root": {
343
- fontSize: "0.8125rem",
344
- borderRadius: 6,
345
- "& fieldset": {
346
- borderColor: "rgba(255,255,255,0.1)",
347
- },
348
- "&:hover fieldset": {
349
- borderColor: "rgba(255,255,255,0.2)",
350
- },
351
- },
352
- },
353
- },
354
- },
355
- MuiSelect: {
356
- defaultProps: { size: "small" },
357
- },
358
- MuiSwitch: {
359
- defaultProps: { size: "small" },
360
- },
361
- MuiAccordion: {
362
- defaultProps: { disableGutters: true, elevation: 0 },
363
- styleOverrides: {
364
- root: {
365
- backgroundColor: "transparent",
366
- border: "1px solid rgba(255,255,255,0.06)",
367
- borderRadius: "6px !important",
368
- "&:before": { display: "none" },
369
- "&.Mui-expanded": { margin: 0 },
370
- },
371
- },
372
- },
373
- MuiAccordionSummary: {
374
- styleOverrides: {
375
- root: {
376
- minHeight: 40,
377
- padding: "0 12px",
378
- fontSize: "0.8125rem",
379
- fontWeight: 500,
380
- "&.Mui-expanded": { minHeight: 40 },
381
- },
382
- content: { margin: "8px 0", "&.Mui-expanded": { margin: "8px 0" } },
383
- },
384
- },
385
- MuiAccordionDetails: {
386
- styleOverrides: {
387
- root: { padding: "4px 12px 12px" },
388
- },
389
- },
390
- MuiList: {
391
- styleOverrides: {
392
- root: { padding: 0 },
393
- },
394
- },
395
- MuiListItem: {
396
- styleOverrides: {
397
- root: { padding: "4px 12px" },
398
- },
399
- },
400
- MuiListItemButton: {
401
- styleOverrides: {
402
- root: {
403
- borderRadius: 6,
404
- padding: "6px 12px",
405
- "&.Mui-selected": {
406
- backgroundColor: "rgba(218,119,86,0.15)",
407
- "&:hover": { backgroundColor: "rgba(218,119,86,0.22)" },
408
- },
409
- },
410
- },
411
- },
412
- MuiDivider: {
413
- styleOverrides: {
414
- root: { borderColor: "rgba(255,255,255,0.08)" },
415
- },
416
- },
417
- MuiAlert: {
418
- styleOverrides: {
419
- root: { borderRadius: 8, fontSize: "0.8125rem" },
420
- },
421
- },
422
- MuiSnackbar: {
423
- defaultProps: {
424
- anchorOrigin: { vertical: "bottom", horizontal: "center" },
425
- autoHideDuration: 4000,
426
- },
427
- },
428
- MuiSkeleton: {
429
- defaultProps: { animation: "wave" },
430
- styleOverrides: {
431
- root: { backgroundColor: "rgba(255,255,255,0.06)" },
432
- },
433
- },
434
- MuiTab: {
435
- styleOverrides: {
436
- root: {
437
- textTransform: "none",
438
- fontWeight: 500,
439
- fontSize: "0.8125rem",
440
- minHeight: 40,
441
- padding: "8px 14px",
442
- color: "#e8e5de",
443
- opacity: 0.95,
444
- // Ensure icon always sits beside label (iconPosition="start")
445
- flexDirection: "row",
446
- gap: 8,
447
- "&.Mui-selected": {
448
- color: "#da7756",
449
- opacity: 1,
450
- },
451
- "&:hover": {
452
- opacity: 1,
453
- },
454
- "& .MuiTab-iconWrapper": { marginBottom: "0 !important", marginRight: 0 },
455
- },
456
- // Constrain raw SVG icons (ICONS map returns unwrapped <svg> nodes)
457
- iconWrapper: {
458
- display: "flex",
459
- alignItems: "center",
460
- justifyContent: "center",
461
- flexShrink: 0,
462
- width: 20,
463
- height: 20,
464
- "& svg": { width: "100%", height: "100%" },
465
- },
466
- },
467
- },
468
- MuiTabs: {
469
- styleOverrides: {
470
- root: { minHeight: 40 },
471
- indicator: {
472
- backgroundColor: "#da7756",
473
- height: 2,
474
- },
475
- },
476
- },
477
- MuiBottomNavigationAction: {
478
- styleOverrides: {
479
- root: {
480
- // Constrain raw SVG icons (ICONS map returns unwrapped <svg> nodes)
481
- "& svg": { width: 24, height: 24, flexShrink: 0 },
482
- },
483
- },
484
- },
485
- MuiMenu: {
486
- styleOverrides: {
487
- paper: {
488
- backgroundColor: "#23221f",
489
- border: "1px solid rgba(255,255,255,0.1)",
490
- borderRadius: 10,
491
- boxShadow: "0 8px 32px rgba(0,0,0,0.4)",
492
- },
493
- },
494
- },
495
- MuiMenuItem: {
496
- styleOverrides: {
497
- root: {
498
- fontSize: "0.8125rem",
499
- borderRadius: 6,
500
- margin: "2px 4px",
501
- padding: "6px 12px",
502
- },
503
- },
504
- },
505
- MuiLinearProgress: {
506
- styleOverrides: {
507
- root: { borderRadius: 4, height: 6 },
508
- bar: { borderRadius: 4 },
509
- },
510
- },
511
- MuiCircularProgress: {
512
- defaultProps: { size: 20, thickness: 3 },
513
- },
514
- MuiAvatar: {
515
- styleOverrides: {
516
- root: {
517
- fontSize: "0.8125rem",
518
- fontWeight: 600,
519
- backgroundColor: "#da7756",
520
- color: "#1e1d1a",
521
- },
522
- },
523
- },
524
- MuiBadge: {
525
- styleOverrides: {
526
- badge: { fontSize: "0.65rem", fontWeight: 600 },
527
- },
528
- },
529
- MuiFab: {
530
- defaultProps: { size: "small" },
531
- styleOverrides: {
532
- root: { boxShadow: "0 4px 12px rgba(0,0,0,0.3)" },
533
- },
534
- },
535
- MuiDrawer: {
536
- styleOverrides: {
537
- paper: {
538
- backgroundColor: "#1f1e1c",
539
- borderColor: "rgba(255,255,255,0.06)",
540
- },
541
- },
542
- },
543
- MuiAppBar: {
544
- defaultProps: { elevation: 0, color: "inherit" },
545
- styleOverrides: {
546
- root: {
547
- backgroundColor: "#1f1e1c",
548
- borderBottom: "1px solid rgba(255,255,255,0.06)",
549
- },
550
- },
551
- },
552
- },
553
- });
715
+ export const veTheme = createMuiTheme(buildThemeOptions(DARK_THEME_TOKENS));
716
+
717
+ function useResolvedMuiTheme() {
718
+ const [themeVersion, setThemeVersion] = useState(0);
719
+
720
+ useEffect(() => {
721
+ if (typeof document === "undefined") return undefined;
722
+
723
+ const root = document.documentElement;
724
+ const refreshTheme = () => setThemeVersion((value) => value + 1);
725
+ const observer = new MutationObserver(refreshTheme);
726
+ observer.observe(root, {
727
+ attributes: true,
728
+ attributeFilter: ["data-theme", "data-theme-lock", "data-tg-theme", "style"],
729
+ });
730
+
731
+ let mediaQuery = null;
732
+ const handleMediaChange = () => refreshTheme();
733
+ if (typeof globalThis.matchMedia === "function") {
734
+ mediaQuery = globalThis.matchMedia("(prefers-color-scheme: light)");
735
+ if (typeof mediaQuery.addEventListener === "function") {
736
+ mediaQuery.addEventListener("change", handleMediaChange);
737
+ } else if (typeof mediaQuery.addListener === "function") {
738
+ mediaQuery.addListener(handleMediaChange);
739
+ }
740
+ }
741
+
742
+ return () => {
743
+ observer.disconnect();
744
+ if (mediaQuery) {
745
+ if (typeof mediaQuery.removeEventListener === "function") {
746
+ mediaQuery.removeEventListener("change", handleMediaChange);
747
+ } else if (typeof mediaQuery.removeListener === "function") {
748
+ mediaQuery.removeListener(handleMediaChange);
749
+ }
750
+ }
751
+ };
752
+ }, []);
753
+
754
+ return useMemo(() => createMuiTheme(buildThemeOptions(resolveThemeTokens())), [themeVersion]);
755
+ }
554
756
 
555
757
  /**
556
758
  * Wrap content in the VirtEngine MUI theme.
557
759
  * Usage in htm: html`<${VeTheme}> ...children... </${VeTheme}>`
558
760
  */
559
761
  export function VeTheme({ children }) {
560
- return html`<${MuiThemeProvider} theme=${veTheme}>${children}</${MuiThemeProvider}>`;
762
+ const theme = useResolvedMuiTheme();
763
+ return html`<${MuiThemeProvider} theme=${theme}>${children}</${MuiThemeProvider}>`;
561
764
  }
562
765
 
563
766
  /**