@gofreego/tsutils 0.1.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,1153 @@
1
+ import { createContext, useState, useMemo, useEffect, useCallback, useContext } from 'react';
2
+ import { createTheme, ThemeProvider as ThemeProvider$1, CssBaseline, IconButton, Tooltip, Box, Drawer, List, Typography, ListItem, ListItemButton, ListItemIcon, ListItemText, Collapse } from '@mui/material';
3
+ import { Brightness4, Brightness7, ExpandLess, ExpandMore } from '@mui/icons-material';
4
+ import { BrowserRouter, useLocation, Routes, Route, Outlet, NavLink } from 'react-router-dom';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+
7
+ var __defProp = Object.defineProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var RouterMenuItem = ({ item, depth = 0, expanded, onToggle }) => {
13
+ const hasChildren = item.children && item.children.length > 0;
14
+ const isExpanded = expanded.has(item.id);
15
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
16
+ /* @__PURE__ */ jsx(ListItem, { disablePadding: true, children: /* @__PURE__ */ jsxs(
17
+ ListItemButton,
18
+ {
19
+ component: hasChildren ? "div" : NavLink,
20
+ to: !hasChildren ? item.path || `/${item.id}` : void 0,
21
+ onClick: hasChildren ? () => onToggle(item.id) : void 0,
22
+ sx: {
23
+ pl: 2 + depth * 2,
24
+ "&.active": {
25
+ backgroundColor: "action.selected",
26
+ color: "primary.main",
27
+ borderLeft: 3,
28
+ borderColor: "primary.main",
29
+ fontWeight: "medium",
30
+ "& .MuiListItemIcon-root": {
31
+ color: "primary.main"
32
+ }
33
+ },
34
+ "&:hover": {
35
+ backgroundColor: "action.hover"
36
+ },
37
+ transition: "all 0.15s"
38
+ },
39
+ children: [
40
+ item.icon && /* @__PURE__ */ jsx(ListItemIcon, { sx: { minWidth: 40 }, children: item.icon }),
41
+ /* @__PURE__ */ jsx(
42
+ ListItemText,
43
+ {
44
+ primary: item.label,
45
+ primaryTypographyProps: {
46
+ fontSize: "0.875rem"
47
+ }
48
+ }
49
+ ),
50
+ hasChildren && /* @__PURE__ */ jsx(IconButton, { size: "small", sx: { p: 0 }, children: isExpanded ? /* @__PURE__ */ jsx(ExpandLess, {}) : /* @__PURE__ */ jsx(ExpandMore, {}) })
51
+ ]
52
+ }
53
+ ) }),
54
+ hasChildren && /* @__PURE__ */ jsx(Collapse, { in: isExpanded, timeout: "auto", unmountOnExit: true, children: /* @__PURE__ */ jsx(List, { component: "div", disablePadding: true, children: item.children.map((child) => /* @__PURE__ */ jsx(
55
+ RouterMenuItem,
56
+ {
57
+ item: child,
58
+ depth: depth + 1,
59
+ expanded,
60
+ onToggle
61
+ },
62
+ child.id
63
+ )) }) })
64
+ ] });
65
+ };
66
+ var flattenMenuRoutes = (items) => {
67
+ const routes = [];
68
+ const flatten = (menuItems) => {
69
+ for (const item of menuItems) {
70
+ if (item.path && item.component) {
71
+ routes.push({ path: item.path, component: item.component });
72
+ }
73
+ if (item.children) {
74
+ flatten(item.children);
75
+ }
76
+ }
77
+ };
78
+ flatten(items);
79
+ return routes;
80
+ };
81
+ var SidebarLayoutRouterInner = ({
82
+ menuItems,
83
+ sidebarWidth = 250,
84
+ className = "",
85
+ onMenuChange,
86
+ style,
87
+ sidebarStyle,
88
+ bodyStyle,
89
+ defaultExpanded = []
90
+ }) => {
91
+ const location = useLocation();
92
+ const [expanded, setExpanded] = useState(new Set(defaultExpanded));
93
+ const handleToggle = (id) => {
94
+ setExpanded((prev) => {
95
+ const next = new Set(prev);
96
+ if (next.has(id)) {
97
+ next.delete(id);
98
+ } else {
99
+ next.add(id);
100
+ }
101
+ return next;
102
+ });
103
+ };
104
+ useEffect(() => {
105
+ if (onMenuChange) {
106
+ const findActiveItem = (items) => {
107
+ for (const item of items) {
108
+ if (item.path && location.pathname === item.path) {
109
+ return item;
110
+ }
111
+ if (item.children) {
112
+ const found = findActiveItem(item.children);
113
+ if (found) return found;
114
+ }
115
+ }
116
+ return void 0;
117
+ };
118
+ const activeItem = findActiveItem(menuItems);
119
+ if (activeItem) {
120
+ onMenuChange(activeItem.id);
121
+ }
122
+ }
123
+ }, [location.pathname, menuItems, onMenuChange]);
124
+ const hasComponents = menuItems.some(
125
+ (item) => item.component || item.children?.some((child) => child.component)
126
+ );
127
+ const routes = hasComponents ? flattenMenuRoutes(menuItems) : [];
128
+ return /* @__PURE__ */ jsxs(
129
+ Box,
130
+ {
131
+ className,
132
+ sx: {
133
+ display: "flex",
134
+ height: "100%",
135
+ width: "100%",
136
+ ...style
137
+ },
138
+ children: [
139
+ /* @__PURE__ */ jsx(
140
+ Drawer,
141
+ {
142
+ variant: "permanent",
143
+ sx: {
144
+ width: sidebarWidth,
145
+ flexShrink: 0,
146
+ "& .MuiDrawer-paper": {
147
+ width: sidebarWidth,
148
+ boxSizing: "border-box",
149
+ position: "relative",
150
+ borderRight: "1px solid",
151
+ borderColor: "divider",
152
+ ...sidebarStyle
153
+ }
154
+ },
155
+ children: /* @__PURE__ */ jsx(List, { sx: { p: 0 }, children: menuItems.map((item) => /* @__PURE__ */ jsx(
156
+ RouterMenuItem,
157
+ {
158
+ item,
159
+ expanded,
160
+ onToggle: handleToggle
161
+ },
162
+ item.id
163
+ )) })
164
+ }
165
+ ),
166
+ /* @__PURE__ */ jsx(
167
+ Box,
168
+ {
169
+ component: "main",
170
+ sx: {
171
+ flexGrow: 1,
172
+ overflow: "auto",
173
+ ...bodyStyle
174
+ },
175
+ children: hasComponents ? /* @__PURE__ */ jsx(Routes, { children: routes.map(({ path, component }) => /* @__PURE__ */ jsx(
176
+ Route,
177
+ {
178
+ path,
179
+ element: component
180
+ },
181
+ path
182
+ )) }) : /* @__PURE__ */ jsx(Outlet, {})
183
+ }
184
+ )
185
+ ]
186
+ }
187
+ );
188
+ };
189
+ var SidebarLayoutWithRouter = (props) => {
190
+ return /* @__PURE__ */ jsx(BrowserRouter, { children: /* @__PURE__ */ jsx(SidebarLayoutRouterInner, { ...props }) });
191
+ };
192
+ var StateMenuItem = ({ item, selectedId, depth = 0, expanded, onToggle, onClick }) => {
193
+ const hasChildren = item.children && item.children.length > 0;
194
+ const isExpanded = expanded.has(item.id);
195
+ const isActive = item.id === selectedId;
196
+ const handleClick = () => {
197
+ if (hasChildren) {
198
+ onToggle(item.id);
199
+ } else {
200
+ onClick(item.id);
201
+ }
202
+ };
203
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
204
+ /* @__PURE__ */ jsx(ListItem, { disablePadding: true, children: /* @__PURE__ */ jsxs(
205
+ ListItemButton,
206
+ {
207
+ selected: isActive,
208
+ onClick: handleClick,
209
+ sx: {
210
+ pl: 2 + depth * 2,
211
+ "&.Mui-selected": {
212
+ backgroundColor: "action.selected",
213
+ color: "primary.main",
214
+ borderLeft: 3,
215
+ borderColor: "primary.main",
216
+ fontWeight: "medium",
217
+ "& .MuiListItemIcon-root": {
218
+ color: "primary.main"
219
+ },
220
+ "&:hover": {
221
+ backgroundColor: "action.selected"
222
+ }
223
+ },
224
+ "&:hover": {
225
+ backgroundColor: "action.hover"
226
+ },
227
+ transition: "all 0.15s"
228
+ },
229
+ children: [
230
+ item.icon && /* @__PURE__ */ jsx(ListItemIcon, { sx: { minWidth: 40 }, children: item.icon }),
231
+ /* @__PURE__ */ jsx(
232
+ ListItemText,
233
+ {
234
+ primary: item.label,
235
+ primaryTypographyProps: {
236
+ fontSize: "0.875rem"
237
+ }
238
+ }
239
+ ),
240
+ hasChildren && /* @__PURE__ */ jsx(IconButton, { size: "small", sx: { p: 0 }, children: isExpanded ? /* @__PURE__ */ jsx(ExpandLess, {}) : /* @__PURE__ */ jsx(ExpandMore, {}) })
241
+ ]
242
+ }
243
+ ) }),
244
+ hasChildren && /* @__PURE__ */ jsx(Collapse, { in: isExpanded, timeout: "auto", unmountOnExit: true, children: /* @__PURE__ */ jsx(List, { component: "div", disablePadding: true, children: item.children.map((child) => /* @__PURE__ */ jsx(
245
+ StateMenuItem,
246
+ {
247
+ item: child,
248
+ selectedId,
249
+ depth: depth + 1,
250
+ expanded,
251
+ onToggle,
252
+ onClick
253
+ },
254
+ child.id
255
+ )) }) })
256
+ ] });
257
+ };
258
+ var SidebarLayoutWithState = ({
259
+ menuItems,
260
+ sidebarWidth = 250,
261
+ className = "",
262
+ defaultSelected,
263
+ onMenuChange,
264
+ style,
265
+ sidebarStyle,
266
+ bodyStyle,
267
+ defaultExpanded = []
268
+ }) => {
269
+ const [selectedId, setSelectedId] = useState(
270
+ defaultSelected || menuItems[0]?.id
271
+ );
272
+ const [expanded, setExpanded] = useState(new Set(defaultExpanded));
273
+ const handleToggle = (id) => {
274
+ setExpanded((prev) => {
275
+ const next = new Set(prev);
276
+ if (next.has(id)) {
277
+ next.delete(id);
278
+ } else {
279
+ next.add(id);
280
+ }
281
+ return next;
282
+ });
283
+ };
284
+ const handleMenuClick = (id) => {
285
+ setSelectedId(id);
286
+ if (onMenuChange) {
287
+ onMenuChange(id);
288
+ }
289
+ };
290
+ const findActiveItem = (items, id) => {
291
+ for (const item of items) {
292
+ if (item.id === id) return item;
293
+ if (item.children) {
294
+ const found = findActiveItem(item.children, id);
295
+ if (found) return found;
296
+ }
297
+ }
298
+ return void 0;
299
+ };
300
+ const activeItem = findActiveItem(menuItems, selectedId);
301
+ return /* @__PURE__ */ jsxs(
302
+ Box,
303
+ {
304
+ className,
305
+ sx: {
306
+ display: "flex",
307
+ height: "100%",
308
+ width: "100%",
309
+ ...style
310
+ },
311
+ children: [
312
+ /* @__PURE__ */ jsx(
313
+ Drawer,
314
+ {
315
+ variant: "permanent",
316
+ sx: {
317
+ width: sidebarWidth,
318
+ flexShrink: 0,
319
+ "& .MuiDrawer-paper": {
320
+ width: sidebarWidth,
321
+ boxSizing: "border-box",
322
+ position: "relative",
323
+ borderRight: "1px solid",
324
+ borderColor: "divider",
325
+ ...sidebarStyle
326
+ }
327
+ },
328
+ children: /* @__PURE__ */ jsx(List, { sx: { p: 0 }, children: menuItems.map((item) => /* @__PURE__ */ jsx(
329
+ StateMenuItem,
330
+ {
331
+ item,
332
+ selectedId,
333
+ expanded,
334
+ onToggle: handleToggle,
335
+ onClick: handleMenuClick
336
+ },
337
+ item.id
338
+ )) })
339
+ }
340
+ ),
341
+ /* @__PURE__ */ jsx(
342
+ Box,
343
+ {
344
+ component: "main",
345
+ sx: {
346
+ flexGrow: 1,
347
+ overflow: "auto",
348
+ p: 3,
349
+ ...bodyStyle
350
+ },
351
+ children: activeItem?.component || /* @__PURE__ */ jsx(
352
+ Typography,
353
+ {
354
+ variant: "body2",
355
+ color: "text.secondary",
356
+ sx: { textAlign: "center", mt: 4 },
357
+ children: "No content available for this menu item"
358
+ }
359
+ )
360
+ }
361
+ )
362
+ ]
363
+ }
364
+ );
365
+ };
366
+ var SidebarLayout = (props) => {
367
+ if (props.isRouter) {
368
+ return /* @__PURE__ */ jsx(SidebarLayoutWithRouter, { ...props });
369
+ }
370
+ return /* @__PURE__ */ jsx(SidebarLayoutWithState, { ...props });
371
+ };
372
+
373
+ // src/theme/tokens.ts
374
+ var tokens_exports = {};
375
+ __export(tokens_exports, {
376
+ borderRadius: () => borderRadius,
377
+ elevation: () => elevation,
378
+ fontSize: () => fontSize,
379
+ fontWeight: () => fontWeight,
380
+ lineHeight: () => lineHeight,
381
+ spacing: () => spacing,
382
+ transition: () => transition,
383
+ zIndex: () => zIndex
384
+ });
385
+ var spacing = {
386
+ xs: "0.25rem",
387
+ // 4px
388
+ sm: "0.5rem",
389
+ // 8px
390
+ md: "1rem",
391
+ // 16px
392
+ lg: "1.5rem",
393
+ // 24px
394
+ xl: "2rem",
395
+ // 32px
396
+ "2xl": "3rem",
397
+ // 48px
398
+ "3xl": "4rem"
399
+ // 64px
400
+ };
401
+ var borderRadius = {
402
+ none: "0",
403
+ sm: "0.25rem",
404
+ // 4px
405
+ md: "0.375rem",
406
+ // 6px
407
+ lg: "0.5rem",
408
+ // 8px
409
+ xl: "0.75rem",
410
+ // 12px
411
+ "2xl": "1rem",
412
+ // 16px
413
+ full: "9999px"
414
+ };
415
+ var fontSize = {
416
+ xs: "0.75rem",
417
+ // 12px
418
+ sm: "0.875rem",
419
+ // 14px
420
+ md: "1rem",
421
+ // 16px
422
+ lg: "1.125rem",
423
+ // 18px
424
+ xl: "1.25rem",
425
+ // 20px
426
+ "2xl": "1.5rem",
427
+ // 24px
428
+ "3xl": "1.875rem",
429
+ // 30px
430
+ "4xl": "2.25rem"
431
+ // 36px
432
+ };
433
+ var fontWeight = {
434
+ light: "300",
435
+ normal: "400",
436
+ medium: "500",
437
+ semibold: "600",
438
+ bold: "700"
439
+ };
440
+ var lineHeight = {
441
+ tight: "1.25",
442
+ normal: "1.5",
443
+ relaxed: "1.75"
444
+ };
445
+ var elevation = {
446
+ none: "none",
447
+ sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
448
+ md: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
449
+ lg: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
450
+ xl: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)"
451
+ };
452
+ var transition = {
453
+ fast: "150ms",
454
+ normal: "250ms",
455
+ slow: "350ms"
456
+ };
457
+ var zIndex = {
458
+ dropdown: 1e3,
459
+ sticky: 1020,
460
+ fixed: 1030,
461
+ modalBackdrop: 1040,
462
+ modal: 1050,
463
+ popover: 1060,
464
+ tooltip: 1070
465
+ };
466
+
467
+ // src/theme/light.ts
468
+ var lightTheme = {
469
+ colors: {
470
+ // Primary - Vibrant Indigo (Works beautifully on light backgrounds)
471
+ primary: "#6366f1",
472
+ // Indigo 500
473
+ primaryHover: "#4f46e5",
474
+ // Indigo 600
475
+ primaryActive: "#4338ca",
476
+ // Indigo 700
477
+ // Secondary - Gray 600 (WCAG AAA compliant: 7.66:1 on white)
478
+ secondary: "#4b5563",
479
+ secondaryHover: "#374151",
480
+ secondaryActive: "#1f2937",
481
+ // Background colors
482
+ background: "#ffffff",
483
+ backgroundSecondary: "#f9fafb",
484
+ backgroundTertiary: "#f3f4f6",
485
+ // Surface colors (for cards, modals, etc.)
486
+ surface: "#ffffff",
487
+ surfaceHover: "#f9fafb",
488
+ // Text colors (WCAG AAA compliant)
489
+ text: "#111827",
490
+ // Gray 900 (16.26:1 on white)
491
+ textSecondary: "#4b5563",
492
+ // Gray 600 (7.66:1 on white)
493
+ textTertiary: "#6b7280",
494
+ // Gray 500 (4.92:1 on white - AA compliant)
495
+ textDisabled: "#9ca3af",
496
+ // Gray 400
497
+ // Semantic colors
498
+ error: "#dc2626",
499
+ // Red 600 (6.05:1 on white - AAA compliant)
500
+ errorHover: "#b91c1c",
501
+ errorBackground: "#fef2f2",
502
+ success: "#16a34a",
503
+ // Green 600 (5.15:1 on white - AAA compliant)
504
+ successHover: "#15803d",
505
+ successBackground: "#f0fdf4",
506
+ warning: "#d97706",
507
+ // Amber 600 (5.33:1 on white - AAA compliant)
508
+ warningHover: "#b45309",
509
+ warningBackground: "#fffbeb",
510
+ info: "#2563eb",
511
+ // Blue 600 (6.64:1 on white - AAA compliant)
512
+ infoHover: "#1d4ed8",
513
+ infoBackground: "#eff6ff",
514
+ // Border colors
515
+ border: "#e5e7eb",
516
+ // Gray 200
517
+ borderHover: "#d1d5db",
518
+ // Gray 300
519
+ borderFocus: "#6366f1",
520
+ // Primary
521
+ // Divider
522
+ divider: "#e5e7eb",
523
+ // Overlay (for modals, dropdowns)
524
+ overlay: "rgba(0, 0, 0, 0.5)"
525
+ },
526
+ spacing,
527
+ borderRadius,
528
+ fontSize,
529
+ fontWeight,
530
+ lineHeight,
531
+ elevation,
532
+ transition,
533
+ zIndex
534
+ };
535
+
536
+ // src/theme/dark.ts
537
+ var darkTheme = {
538
+ colors: {
539
+ // Primary - Vibrant Indigo (Striking on VSCode Black)
540
+ primary: "#818cf8",
541
+ // Indigo 400
542
+ primaryHover: "#6366f1",
543
+ // Indigo 500
544
+ primaryActive: "#4f46e5",
545
+ // Indigo 600
546
+ // Secondary - VSCode Gray
547
+ secondary: "#858585",
548
+ secondaryHover: "#a6a6a6",
549
+ secondaryActive: "#cccccc",
550
+ // Background colors
551
+ background: "#1e1e1e",
552
+ // Main editor/workspace background
553
+ backgroundSecondary: "#252526",
554
+ // Sidebar, panels
555
+ backgroundTertiary: "#2d2d2d",
556
+ // Highlights, smaller surfaces
557
+ // Surface colors (for cards, modals, etc.)
558
+ surface: "#252526",
559
+ surfaceHover: "#2a2d2e",
560
+ // Text colors
561
+ text: "#cccccc",
562
+ // Primary text
563
+ textSecondary: "#999999",
564
+ // Secondary text, comments
565
+ textTertiary: "#6b6b6b",
566
+ // Disabled/tertiary
567
+ textDisabled: "#4d4d4d",
568
+ // Disabled
569
+ // Semantic colors
570
+ error: "#f48771",
571
+ errorHover: "#fca5a5",
572
+ errorBackground: "#5a1d1d",
573
+ success: "#89d185",
574
+ successHover: "#86efac",
575
+ successBackground: "#1e401e",
576
+ warning: "#cca700",
577
+ warningHover: "#d7ba7d",
578
+ warningBackground: "#5c4d00",
579
+ info: "#75beff",
580
+ infoHover: "#9cdcfe",
581
+ infoBackground: "#0a3254",
582
+ // Border colors
583
+ border: "#3c3c3c",
584
+ // VSCode borders
585
+ borderHover: "#444444",
586
+ borderFocus: "#818cf8",
587
+ // Focus border
588
+ // Divider
589
+ divider: "#333333",
590
+ // Overlay (for modals, dropdowns)
591
+ overlay: "rgba(0, 0, 0, 0.4)"
592
+ },
593
+ spacing,
594
+ borderRadius,
595
+ fontSize,
596
+ fontWeight,
597
+ lineHeight,
598
+ elevation,
599
+ transition,
600
+ zIndex
601
+ };
602
+
603
+ // src/utils/localStorage.ts
604
+ var LocalStorage = class {
605
+ /**
606
+ * Get an item from localStorage
607
+ * @param key - The key to retrieve
608
+ * @returns The value or null if not found or error occurs
609
+ */
610
+ static getItem(key) {
611
+ try {
612
+ if (typeof window === "undefined") {
613
+ return null;
614
+ }
615
+ const item = window.localStorage.getItem(key);
616
+ if (item === null) {
617
+ return null;
618
+ }
619
+ try {
620
+ return JSON.parse(item);
621
+ } catch {
622
+ return item;
623
+ }
624
+ } catch (error) {
625
+ console.error(`Error getting item from localStorage: ${error}`);
626
+ return null;
627
+ }
628
+ }
629
+ /**
630
+ * Set an item in localStorage
631
+ * @param key - The key to store
632
+ * @param value - The value to store
633
+ * @returns true if successful, false otherwise
634
+ */
635
+ static setItem(key, value) {
636
+ try {
637
+ if (typeof window === "undefined") {
638
+ return false;
639
+ }
640
+ const serializedValue = typeof value === "string" ? value : JSON.stringify(value);
641
+ window.localStorage.setItem(key, serializedValue);
642
+ return true;
643
+ } catch (error) {
644
+ console.error(`Error setting item in localStorage: ${error}`);
645
+ return false;
646
+ }
647
+ }
648
+ /**
649
+ * Remove an item from localStorage
650
+ * @param key - The key to remove
651
+ * @returns true if successful, false otherwise
652
+ */
653
+ static removeItem(key) {
654
+ try {
655
+ if (typeof window === "undefined") {
656
+ return false;
657
+ }
658
+ window.localStorage.removeItem(key);
659
+ return true;
660
+ } catch (error) {
661
+ console.error(`Error removing item from localStorage: ${error}`);
662
+ return false;
663
+ }
664
+ }
665
+ /**
666
+ * Clear all items from localStorage
667
+ * @returns true if successful, false otherwise
668
+ */
669
+ static clear() {
670
+ try {
671
+ if (typeof window === "undefined") {
672
+ return false;
673
+ }
674
+ window.localStorage.clear();
675
+ return true;
676
+ } catch (error) {
677
+ console.error(`Error clearing localStorage: ${error}`);
678
+ return false;
679
+ }
680
+ }
681
+ /**
682
+ * Check if a key exists in localStorage
683
+ * @param key - The key to check
684
+ * @returns true if key exists, false otherwise
685
+ */
686
+ static hasItem(key) {
687
+ try {
688
+ if (typeof window === "undefined") {
689
+ return false;
690
+ }
691
+ return window.localStorage.getItem(key) !== null;
692
+ } catch (error) {
693
+ console.error(`Error checking item in localStorage: ${error}`);
694
+ return false;
695
+ }
696
+ }
697
+ /**
698
+ * Get all keys from localStorage
699
+ * @returns Array of keys
700
+ */
701
+ static keys() {
702
+ try {
703
+ if (typeof window === "undefined") {
704
+ return [];
705
+ }
706
+ return Object.keys(window.localStorage);
707
+ } catch (error) {
708
+ console.error(`Error getting keys from localStorage: ${error}`);
709
+ return [];
710
+ }
711
+ }
712
+ };
713
+ var ThemeContext = createContext(void 0);
714
+ var THEME_STORAGE_KEY = "app-theme-mode";
715
+ var getSystemTheme = () => {
716
+ if (typeof window === "undefined") return "light";
717
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
718
+ };
719
+ var applyCssVariables = (theme, resolvedMode) => {
720
+ if (typeof document === "undefined") return;
721
+ const root = document.documentElement;
722
+ root.setAttribute("data-theme", resolvedMode);
723
+ Object.entries(theme.colors).forEach(([key, value]) => {
724
+ root.style.setProperty(`--color-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`, value);
725
+ });
726
+ Object.entries(theme.spacing).forEach(([key, value]) => {
727
+ root.style.setProperty(`--spacing-${key}`, value);
728
+ });
729
+ Object.entries(theme.borderRadius).forEach(([key, value]) => {
730
+ root.style.setProperty(`--radius-${key}`, value);
731
+ });
732
+ Object.entries(theme.fontSize).forEach(([key, value]) => {
733
+ root.style.setProperty(`--text-${key}`, value);
734
+ });
735
+ Object.entries(theme.fontWeight).forEach(([key, value]) => {
736
+ root.style.setProperty(`--font-${key}`, value);
737
+ });
738
+ Object.entries(theme.lineHeight).forEach(([key, value]) => {
739
+ root.style.setProperty(`--leading-${key}`, value);
740
+ });
741
+ Object.entries(theme.elevation).forEach(([key, value]) => {
742
+ root.style.setProperty(`--shadow-${key}`, value);
743
+ });
744
+ Object.entries(theme.transition).forEach(([key, value]) => {
745
+ root.style.setProperty(`--transition-${key}`, value);
746
+ });
747
+ };
748
+ var ThemeProvider = ({
749
+ children,
750
+ initialTheme,
751
+ initialMode = "system",
752
+ storageKey = THEME_STORAGE_KEY,
753
+ enableCssVariables = true
754
+ }) => {
755
+ const getInitialMode = () => {
756
+ const savedMode = LocalStorage.getItem(storageKey);
757
+ return savedMode || initialMode;
758
+ };
759
+ const [themeMode, setThemeModeState] = useState(getInitialMode);
760
+ const [systemTheme, setSystemTheme] = useState(getSystemTheme);
761
+ const [customTheme, setCustomTheme] = useState(initialTheme);
762
+ const resolvedThemeMode = useMemo(() => {
763
+ if (themeMode === "system") return systemTheme;
764
+ return themeMode;
765
+ }, [themeMode, systemTheme]);
766
+ const theme = useMemo(() => {
767
+ if (customTheme) return customTheme;
768
+ return resolvedThemeMode === "light" ? lightTheme : darkTheme;
769
+ }, [customTheme, resolvedThemeMode]);
770
+ useEffect(() => {
771
+ if (typeof window === "undefined") return;
772
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
773
+ const handleChange = (e) => {
774
+ setSystemTheme(e.matches ? "dark" : "light");
775
+ };
776
+ if (mediaQuery.addEventListener) {
777
+ mediaQuery.addEventListener("change", handleChange);
778
+ return () => mediaQuery.removeEventListener("change", handleChange);
779
+ } else if (mediaQuery.addListener) {
780
+ mediaQuery.addListener(handleChange);
781
+ return () => mediaQuery.removeListener(handleChange);
782
+ }
783
+ }, []);
784
+ useEffect(() => {
785
+ if (enableCssVariables) {
786
+ applyCssVariables(theme, resolvedThemeMode);
787
+ }
788
+ }, [theme, resolvedThemeMode, enableCssVariables]);
789
+ useEffect(() => {
790
+ LocalStorage.setItem(storageKey, themeMode);
791
+ }, [themeMode, storageKey]);
792
+ const setThemeMode = useCallback((mode) => {
793
+ setThemeModeState(mode);
794
+ }, []);
795
+ const setTheme = useCallback((newTheme) => {
796
+ setCustomTheme(newTheme);
797
+ }, []);
798
+ const toggleTheme = useCallback(() => {
799
+ const newMode = resolvedThemeMode === "light" ? "dark" : "light";
800
+ setThemeModeState(newMode);
801
+ }, [resolvedThemeMode]);
802
+ const contextValue = useMemo(() => ({
803
+ theme,
804
+ themeMode,
805
+ resolvedThemeMode,
806
+ setTheme,
807
+ setThemeMode,
808
+ toggleTheme
809
+ }), [theme, themeMode, resolvedThemeMode, setTheme, setThemeMode, toggleTheme]);
810
+ const muiTheme = useMemo(() => createTheme({
811
+ palette: {
812
+ mode: resolvedThemeMode,
813
+ primary: {
814
+ main: theme.colors.primary
815
+ },
816
+ secondary: {
817
+ main: theme.colors.secondary
818
+ },
819
+ error: {
820
+ main: theme.colors.error
821
+ },
822
+ success: {
823
+ main: theme.colors.success
824
+ },
825
+ background: {
826
+ default: theme.colors.background,
827
+ paper: theme.colors.backgroundSecondary
828
+ },
829
+ text: {
830
+ primary: theme.colors.text,
831
+ secondary: theme.colors.textSecondary
832
+ }
833
+ }
834
+ }), [resolvedThemeMode, theme]);
835
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(ThemeProvider$1, { theme: muiTheme, children: [
836
+ /* @__PURE__ */ jsx(CssBaseline, {}),
837
+ children
838
+ ] }) });
839
+ };
840
+ var useTheme = () => {
841
+ const context = useContext(ThemeContext);
842
+ if (!context) {
843
+ throw new Error("useTheme must be used within a ThemeProvider");
844
+ }
845
+ return context;
846
+ };
847
+ var ThemeToggle = ({
848
+ lightModeTooltip = "Switch to dark mode",
849
+ darkModeTooltip = "Switch to light mode",
850
+ showTooltip = true,
851
+ sx,
852
+ ...props
853
+ }) => {
854
+ const { resolvedThemeMode, toggleTheme } = useTheme();
855
+ const button = /* @__PURE__ */ jsx(
856
+ IconButton,
857
+ {
858
+ onClick: toggleTheme,
859
+ color: "inherit",
860
+ sx: {
861
+ borderRadius: "50%",
862
+ ...sx
863
+ },
864
+ ...props,
865
+ children: resolvedThemeMode === "light" ? /* @__PURE__ */ jsx(Brightness4, {}) : /* @__PURE__ */ jsx(Brightness7, {})
866
+ }
867
+ );
868
+ if (!showTooltip) {
869
+ return button;
870
+ }
871
+ return /* @__PURE__ */ jsx(
872
+ Tooltip,
873
+ {
874
+ title: resolvedThemeMode === "light" ? lightModeTooltip : darkModeTooltip,
875
+ arrow: true,
876
+ children: button
877
+ }
878
+ );
879
+ };
880
+
881
+ // src/http/HttpClient.ts
882
+ var HttpClient = class {
883
+ constructor(config = {}) {
884
+ this.baseURL = config.baseURL || "";
885
+ this.timeout = config.timeout || 3e4;
886
+ this.defaultHeaders = config.headers || {};
887
+ }
888
+ buildURL(url, params) {
889
+ const fullURL = url.startsWith("http") ? url : `${this.baseURL}${url}`;
890
+ if (!params) return fullURL;
891
+ const searchParams = new URLSearchParams();
892
+ Object.entries(params).forEach(([key, value]) => {
893
+ searchParams.append(key, String(value));
894
+ });
895
+ return `${fullURL}?${searchParams.toString()}`;
896
+ }
897
+ async request(url, config = {}) {
898
+ const { params, data, timeout = this.timeout, headers = {}, ...restConfig } = config;
899
+ const controller = new AbortController();
900
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
901
+ try {
902
+ const response = await fetch(this.buildURL(url, params), {
903
+ ...restConfig,
904
+ headers: {
905
+ "Content-Type": "application/json",
906
+ ...this.defaultHeaders,
907
+ ...headers
908
+ },
909
+ body: data ? JSON.stringify(data) : void 0,
910
+ signal: controller.signal
911
+ });
912
+ clearTimeout(timeoutId);
913
+ if (!response.ok) {
914
+ const error = new Error(`HTTP Error: ${response.statusText}`);
915
+ error.status = response.status;
916
+ error.statusText = response.statusText;
917
+ try {
918
+ error.data = await response.json();
919
+ } catch {
920
+ error.data = await response.text();
921
+ }
922
+ throw error;
923
+ }
924
+ const responseData = await response.json();
925
+ return {
926
+ data: responseData,
927
+ status: response.status,
928
+ statusText: response.statusText,
929
+ headers: response.headers
930
+ };
931
+ } catch (error) {
932
+ clearTimeout(timeoutId);
933
+ if (error instanceof Error && error.name === "AbortError") {
934
+ throw new Error("Request timeout");
935
+ }
936
+ throw error;
937
+ }
938
+ }
939
+ get(url, config) {
940
+ return this.request(url, { ...config, method: "GET" });
941
+ }
942
+ post(url, data, config) {
943
+ return this.request(url, { ...config, data, method: "POST" });
944
+ }
945
+ put(url, data, config) {
946
+ return this.request(url, { ...config, data, method: "PUT" });
947
+ }
948
+ patch(url, data, config) {
949
+ return this.request(url, { ...config, data, method: "PATCH" });
950
+ }
951
+ delete(url, config) {
952
+ return this.request(url, { ...config, method: "DELETE" });
953
+ }
954
+ };
955
+
956
+ // src/utils/cn.ts
957
+ function cn(...classes) {
958
+ return classes.filter(Boolean).join(" ");
959
+ }
960
+
961
+ // src/utils/debounce.ts
962
+ function debounce(func, wait) {
963
+ let timeoutId = null;
964
+ return function(...args) {
965
+ if (timeoutId) {
966
+ clearTimeout(timeoutId);
967
+ }
968
+ timeoutId = setTimeout(() => {
969
+ func.apply(this, args);
970
+ }, wait);
971
+ };
972
+ }
973
+
974
+ // src/utils/throttle.ts
975
+ function throttle(func, wait) {
976
+ let lastCall = 0;
977
+ return function(...args) {
978
+ const now = Date.now();
979
+ if (now - lastCall >= wait) {
980
+ lastCall = now;
981
+ func.apply(this, args);
982
+ }
983
+ };
984
+ }
985
+
986
+ // src/utils/formatDate.ts
987
+ function formatDate(date, options = {
988
+ year: "numeric",
989
+ month: "long",
990
+ day: "numeric"
991
+ }) {
992
+ const dateObj = typeof date === "string" || typeof date === "number" ? new Date(date) : date;
993
+ return new Intl.DateTimeFormat("en-US", options).format(dateObj);
994
+ }
995
+ function getCookie(name) {
996
+ if (typeof document === "undefined") return null;
997
+ const value = `; ${document.cookie}`;
998
+ const parts = value.split(`; ${name}=`);
999
+ if (parts.length === 2) return parts.pop()?.split(";").shift() || null;
1000
+ return null;
1001
+ }
1002
+ function decodeJWT(token) {
1003
+ try {
1004
+ const base64Url = token.split(".")[1];
1005
+ if (!base64Url) return null;
1006
+ const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
1007
+ const jsonPayload = decodeURIComponent(
1008
+ window.atob(base64).split("").map(function(c) {
1009
+ return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
1010
+ }).join("")
1011
+ );
1012
+ return JSON.parse(jsonPayload);
1013
+ } catch (e) {
1014
+ return null;
1015
+ }
1016
+ }
1017
+ var AuthContext = createContext({
1018
+ isAuthenticated: false,
1019
+ isLoading: true
1020
+ });
1021
+ var AuthProvider = ({
1022
+ cookieName = "authorization",
1023
+ redirectUrl,
1024
+ children,
1025
+ onAuthFail,
1026
+ onAuthSuccess,
1027
+ loadingElement = null
1028
+ }) => {
1029
+ const [authState, setAuthState] = useState({
1030
+ isAuthenticated: false,
1031
+ isLoading: true
1032
+ // starts loading until cookie check completes
1033
+ });
1034
+ const [authFailed, setAuthFailed] = useState(false);
1035
+ useEffect(() => {
1036
+ const verifyAuth = () => {
1037
+ const token = getCookie(cookieName);
1038
+ if (!token) {
1039
+ handleFail();
1040
+ return;
1041
+ }
1042
+ const decoded = decodeJWT(token);
1043
+ if (!decoded) {
1044
+ handleFail();
1045
+ return;
1046
+ }
1047
+ if (decoded.exp) {
1048
+ const expirationDate = new Date(decoded.exp * 1e3);
1049
+ if (expirationDate < /* @__PURE__ */ new Date()) {
1050
+ handleFail();
1051
+ return;
1052
+ }
1053
+ }
1054
+ setAuthState({
1055
+ isAuthenticated: true,
1056
+ isLoading: false
1057
+ });
1058
+ if (onAuthSuccess) {
1059
+ onAuthSuccess();
1060
+ }
1061
+ };
1062
+ verifyAuth();
1063
+ }, [cookieName]);
1064
+ const handleFail = () => {
1065
+ setAuthState({
1066
+ isAuthenticated: false,
1067
+ isLoading: false
1068
+ });
1069
+ setAuthFailed(true);
1070
+ if (onAuthFail) {
1071
+ onAuthFail();
1072
+ } else if (redirectUrl && typeof window !== "undefined") {
1073
+ window.location.href = redirectUrl;
1074
+ }
1075
+ };
1076
+ if (authState.isLoading || authFailed && !onAuthFail) {
1077
+ return /* @__PURE__ */ jsx(Fragment, { children: loadingElement });
1078
+ }
1079
+ if (authFailed && onAuthFail) {
1080
+ return /* @__PURE__ */ jsx(Fragment, { children });
1081
+ }
1082
+ return /* @__PURE__ */ jsx(AuthContext.Provider, { value: authState, children });
1083
+ };
1084
+ var useAuth = () => {
1085
+ return useContext(AuthContext);
1086
+ };
1087
+
1088
+ // src/auth/PermissionManager.ts
1089
+ var PERMISSIONS_STORAGE_KEY = "auth_permissions";
1090
+ var PermissionManager = class {
1091
+ /**
1092
+ * Saves an array of permission strings to local storage.
1093
+ * @param permissions Array of permission strings.
1094
+ */
1095
+ static setPermissions(permissions) {
1096
+ this.cachedPermissions = new Set(permissions);
1097
+ if (typeof window === "undefined") return;
1098
+ try {
1099
+ localStorage.setItem(PERMISSIONS_STORAGE_KEY, JSON.stringify(permissions));
1100
+ } catch (error) {
1101
+ console.error("Failed to save permissions to localStorage", error);
1102
+ }
1103
+ }
1104
+ /**
1105
+ * Retrieves the currently stored permissions from local storage or memory cache.
1106
+ * @returns Array of permission strings, or an empty array if none exist.
1107
+ */
1108
+ static getPermissions() {
1109
+ if (this.cachedPermissions !== null) {
1110
+ return Array.from(this.cachedPermissions);
1111
+ }
1112
+ if (typeof window === "undefined") return [];
1113
+ try {
1114
+ const stored = localStorage.getItem(PERMISSIONS_STORAGE_KEY);
1115
+ if (!stored) {
1116
+ this.cachedPermissions = /* @__PURE__ */ new Set();
1117
+ return [];
1118
+ }
1119
+ const parsed = JSON.parse(stored);
1120
+ const permissionsArray = Array.isArray(parsed) ? parsed : [];
1121
+ this.cachedPermissions = new Set(permissionsArray);
1122
+ return permissionsArray;
1123
+ } catch (error) {
1124
+ console.error("Failed to parse permissions from localStorage", error);
1125
+ this.cachedPermissions = /* @__PURE__ */ new Set();
1126
+ return [];
1127
+ }
1128
+ }
1129
+ /**
1130
+ * Clears all stored permissions from memory and local storage.
1131
+ */
1132
+ static clearPermissions() {
1133
+ this.cachedPermissions = null;
1134
+ if (typeof window === "undefined") return;
1135
+ localStorage.removeItem(PERMISSIONS_STORAGE_KEY);
1136
+ }
1137
+ /**
1138
+ * Checks whether the given permission string exists in the currently stored permissions.
1139
+ * @param permission The permission string to check for.
1140
+ * @returns True if the permission exists, false otherwise.
1141
+ */
1142
+ static hasPermission(permission) {
1143
+ if (this.cachedPermissions === null) {
1144
+ this.getPermissions();
1145
+ }
1146
+ return this.cachedPermissions.has(permission);
1147
+ }
1148
+ };
1149
+ PermissionManager.cachedPermissions = null;
1150
+
1151
+ export { AuthProvider, HttpClient, LocalStorage, PermissionManager, SidebarLayout, ThemeProvider, ThemeToggle, borderRadius, cn, darkTheme, debounce, elevation, fontSize, fontWeight, formatDate, lightTheme, lineHeight, spacing, throttle, tokens_exports as tokens, transition, useAuth, useTheme, zIndex };
1152
+ //# sourceMappingURL=index.mjs.map
1153
+ //# sourceMappingURL=index.mjs.map