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