@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.
@@ -0,0 +1,555 @@
1
+ import React, { ReactNode, CSSProperties } from 'react';
2
+ import { IconButtonProps } from '@mui/material';
3
+
4
+ /**
5
+ * Menu item configuration
6
+ */
7
+ interface MenuItem {
8
+ /** Unique identifier */
9
+ id: string;
10
+ /** Display label */
11
+ label: string;
12
+ /** Optional icon element */
13
+ icon?: ReactNode;
14
+ /** Route path (for router mode) */
15
+ path?: string;
16
+ /** Component to render (for non-router mode) */
17
+ component?: ReactNode;
18
+ /** Optional submenu items */
19
+ children?: MenuItem[];
20
+ }
21
+ interface SidebarLayoutProps {
22
+ /** Array of menu items */
23
+ menuItems: MenuItem[];
24
+ /** Width of the sidebar */
25
+ sidebarWidth?: string | number;
26
+ /** Additional CSS class for container */
27
+ className?: string;
28
+ /** Default selected menu item ID (for non-router mode) */
29
+ defaultSelected?: string;
30
+ /** Callback when menu changes */
31
+ onMenuChange?: (id: string) => void;
32
+ /** Custom styles for container */
33
+ style?: CSSProperties;
34
+ /** Custom styles for sidebar */
35
+ sidebarStyle?: CSSProperties;
36
+ /** Custom styles for body */
37
+ bodyStyle?: CSSProperties;
38
+ /** Default expanded submenu IDs */
39
+ defaultExpanded?: string[];
40
+ /** If true, wraps SidebarLayout in BrowserRouter and enables router-based navigation */
41
+ isRouter?: boolean;
42
+ }
43
+ /**
44
+ * SidebarLayout Component
45
+ *
46
+ * A flexible sidebar-body layout component built with Material UI that supports
47
+ * both router-based and state-based navigation with nested submenu support.
48
+ *
49
+ * **Uses Material UI Components:**
50
+ * - Drawer (permanent variant)
51
+ * - List, ListItem, ListItemButton
52
+ * - ListItemIcon, ListItemText
53
+ * - Collapse (for submenus)
54
+ * - Box for layout
55
+ *
56
+ * **Features:**
57
+ * - Nested submenu support with expand/collapse
58
+ * - Material UI icons (ExpandMore/ExpandLess)
59
+ * - Recursive rendering for deep nesting
60
+ * - Automatic indentation based on depth
61
+ *
62
+ * **Router Mode** (when react-router-dom is available):
63
+ * - Uses NavLink for navigation
64
+ * - Renders child routes via Outlet
65
+ * - Supports URL-based navigation
66
+ * - Browser back/forward works
67
+ *
68
+ * **State Mode** (fallback):
69
+ * - Uses internal state
70
+ * - Renders components from menuItems
71
+ * - Simple navigation without routing
72
+ *
73
+ * @example
74
+ * // With nested submenus
75
+ * <SidebarLayout
76
+ * menuItems={[
77
+ * {
78
+ * id: 'products',
79
+ * label: 'Products',
80
+ * icon: <ShoppingCartIcon />,
81
+ * children: [
82
+ * { id: 'all-products', label: 'All Products', path: '/products/all' },
83
+ * { id: 'categories', label: 'Categories', path: '/products/categories' }
84
+ * ]
85
+ * },
86
+ * { id: 'dashboard', label: 'Dashboard', path: '/dashboard', icon: <DashboardIcon /> }
87
+ * ]}
88
+ * defaultExpanded={['products']}
89
+ * />
90
+ *
91
+ * @example
92
+ * // With React Router
93
+ * <Routes>
94
+ * <Route path="/" element={
95
+ * <SidebarLayout menuItems={[
96
+ * { id: 'dashboard', label: 'Dashboard', path: '/dashboard', icon: <DashboardIcon /> },
97
+ * { id: 'settings', label: 'Settings', path: '/settings', icon: <SettingsIcon /> }
98
+ * ]} />
99
+ * }>
100
+ * <Route path="dashboard" element={<Dashboard />} />
101
+ * <Route path="settings" element={<Settings />} />
102
+ * </Route>
103
+ * </Routes>
104
+ *
105
+ * @example
106
+ * // Without React Router (State-based)
107
+ * <SidebarLayout
108
+ * menuItems={[
109
+ * { id: 'dashboard', label: 'Dashboard', component: <Dashboard /> },
110
+ * { id: 'settings', label: 'Settings', component: <Settings /> }
111
+ * ]}
112
+ * defaultSelected="dashboard"
113
+ * />
114
+ */
115
+ declare const SidebarLayout: React.FC<SidebarLayoutProps>;
116
+
117
+ /**
118
+ * Theme mode options
119
+ * - 'light': Always use light theme
120
+ * - 'dark': Always use dark theme
121
+ * - 'system': Follow system preference (prefers-color-scheme)
122
+ */
123
+ type ThemeMode = 'light' | 'dark' | 'system';
124
+ /**
125
+ * Resolved theme mode (actual theme being displayed)
126
+ */
127
+ type ResolvedThemeMode = 'light' | 'dark';
128
+ /**
129
+ * Theme interface with comprehensive design tokens
130
+ */
131
+ interface Theme {
132
+ colors: {
133
+ primary: string;
134
+ primaryHover: string;
135
+ primaryActive: string;
136
+ secondary: string;
137
+ secondaryHover: string;
138
+ secondaryActive: string;
139
+ background: string;
140
+ backgroundSecondary: string;
141
+ backgroundTertiary: string;
142
+ surface: string;
143
+ surfaceHover: string;
144
+ text: string;
145
+ textSecondary: string;
146
+ textTertiary: string;
147
+ textDisabled: string;
148
+ error: string;
149
+ errorHover: string;
150
+ errorBackground: string;
151
+ success: string;
152
+ successHover: string;
153
+ successBackground: string;
154
+ warning: string;
155
+ warningHover: string;
156
+ warningBackground: string;
157
+ info: string;
158
+ infoHover: string;
159
+ infoBackground: string;
160
+ border: string;
161
+ borderHover: string;
162
+ borderFocus: string;
163
+ divider: string;
164
+ overlay: string;
165
+ };
166
+ spacing: {
167
+ xs: string;
168
+ sm: string;
169
+ md: string;
170
+ lg: string;
171
+ xl: string;
172
+ '2xl': string;
173
+ '3xl': string;
174
+ };
175
+ borderRadius: {
176
+ none: string;
177
+ sm: string;
178
+ md: string;
179
+ lg: string;
180
+ xl: string;
181
+ '2xl': string;
182
+ full: string;
183
+ };
184
+ fontSize: {
185
+ xs: string;
186
+ sm: string;
187
+ md: string;
188
+ lg: string;
189
+ xl: string;
190
+ '2xl': string;
191
+ '3xl': string;
192
+ '4xl': string;
193
+ };
194
+ fontWeight: {
195
+ light: string;
196
+ normal: string;
197
+ medium: string;
198
+ semibold: string;
199
+ bold: string;
200
+ };
201
+ lineHeight: {
202
+ tight: string;
203
+ normal: string;
204
+ relaxed: string;
205
+ };
206
+ elevation: {
207
+ none: string;
208
+ sm: string;
209
+ md: string;
210
+ lg: string;
211
+ xl: string;
212
+ };
213
+ transition: {
214
+ fast: string;
215
+ normal: string;
216
+ slow: string;
217
+ };
218
+ zIndex: {
219
+ dropdown: number;
220
+ sticky: number;
221
+ fixed: number;
222
+ modalBackdrop: number;
223
+ modal: number;
224
+ popover: number;
225
+ tooltip: number;
226
+ };
227
+ }
228
+ /**
229
+ * Theme context value
230
+ */
231
+ interface ThemeContextValue {
232
+ /** Current theme object */
233
+ theme: Theme;
234
+ /** Selected theme mode (can be 'system') */
235
+ themeMode: ThemeMode;
236
+ /** Resolved theme mode (actual theme being displayed: 'light' or 'dark') */
237
+ resolvedThemeMode: ResolvedThemeMode;
238
+ /** Set custom theme object */
239
+ setTheme: (theme: Theme) => void;
240
+ /** Set theme mode */
241
+ setThemeMode: (mode: ThemeMode) => void;
242
+ /** Toggle between light and dark (skips system) */
243
+ toggleTheme: () => void;
244
+ }
245
+
246
+ interface ThemeProviderProps {
247
+ children: ReactNode;
248
+ /** Custom theme object (overrides default light/dark themes) */
249
+ initialTheme?: Theme;
250
+ /** Initial theme mode */
251
+ initialMode?: ThemeMode;
252
+ /** localStorage key for persisting theme preference */
253
+ storageKey?: string;
254
+ /** Enable CSS variables injection */
255
+ enableCssVariables?: boolean;
256
+ }
257
+ declare const ThemeProvider: React.FC<ThemeProviderProps>;
258
+
259
+ declare const useTheme: () => ThemeContextValue;
260
+
261
+ interface ThemeToggleProps extends Omit<IconButtonProps, 'onClick'> {
262
+ /**
263
+ * Tooltip text for light mode
264
+ * @default "Switch to dark mode"
265
+ */
266
+ lightModeTooltip?: string;
267
+ /**
268
+ * Tooltip text for dark mode
269
+ * @default "Switch to light mode"
270
+ */
271
+ darkModeTooltip?: string;
272
+ /**
273
+ * Whether to show tooltip
274
+ * @default true
275
+ */
276
+ showTooltip?: boolean;
277
+ }
278
+ /**
279
+ * A round button component for toggling between light and dark themes
280
+ * Note: Toggling skips 'system' mode and switches directly between light and dark
281
+ */
282
+ declare const ThemeToggle: React.FC<ThemeToggleProps>;
283
+
284
+ /**
285
+ * Light theme configuration
286
+ * Colors are WCAG AA compliant with minimum contrast ratio of 4.5:1 for normal text
287
+ * and 3:1 for large text
288
+ */
289
+ declare const lightTheme: Theme;
290
+
291
+ /**
292
+ * Dark theme configuration
293
+ * Colors are WCAG AA compliant with minimum contrast ratio of 4.5:1 for normal text
294
+ * and 3:1 for large text on dark backgrounds
295
+ */
296
+ declare const darkTheme: Theme;
297
+
298
+ /**
299
+ * Design Tokens - Theme-agnostic values
300
+ * These tokens serve as the foundation for light and dark themes
301
+ * Following Material Design 3 principles
302
+ */
303
+ /**
304
+ * Spacing scale based on 4px grid system
305
+ */
306
+ declare const spacing: {
307
+ readonly xs: "0.25rem";
308
+ readonly sm: "0.5rem";
309
+ readonly md: "1rem";
310
+ readonly lg: "1.5rem";
311
+ readonly xl: "2rem";
312
+ readonly '2xl': "3rem";
313
+ readonly '3xl': "4rem";
314
+ };
315
+ /**
316
+ * Border radius tokens
317
+ */
318
+ declare const borderRadius: {
319
+ readonly none: "0";
320
+ readonly sm: "0.25rem";
321
+ readonly md: "0.375rem";
322
+ readonly lg: "0.5rem";
323
+ readonly xl: "0.75rem";
324
+ readonly '2xl': "1rem";
325
+ readonly full: "9999px";
326
+ };
327
+ /**
328
+ * Typography scale
329
+ */
330
+ declare const fontSize: {
331
+ readonly xs: "0.75rem";
332
+ readonly sm: "0.875rem";
333
+ readonly md: "1rem";
334
+ readonly lg: "1.125rem";
335
+ readonly xl: "1.25rem";
336
+ readonly '2xl': "1.5rem";
337
+ readonly '3xl': "1.875rem";
338
+ readonly '4xl': "2.25rem";
339
+ };
340
+ /**
341
+ * Font weights
342
+ */
343
+ declare const fontWeight: {
344
+ readonly light: "300";
345
+ readonly normal: "400";
346
+ readonly medium: "500";
347
+ readonly semibold: "600";
348
+ readonly bold: "700";
349
+ };
350
+ /**
351
+ * Line heights
352
+ */
353
+ declare const lineHeight: {
354
+ readonly tight: "1.25";
355
+ readonly normal: "1.5";
356
+ readonly relaxed: "1.75";
357
+ };
358
+ /**
359
+ * Elevation (box shadows) for depth perception
360
+ */
361
+ declare const elevation: {
362
+ readonly none: "none";
363
+ readonly sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)";
364
+ readonly md: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)";
365
+ readonly lg: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)";
366
+ readonly xl: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)";
367
+ };
368
+ /**
369
+ * Transition durations
370
+ */
371
+ declare const transition: {
372
+ readonly fast: "150ms";
373
+ readonly normal: "250ms";
374
+ readonly slow: "350ms";
375
+ };
376
+ /**
377
+ * Z-index scale for layering
378
+ */
379
+ declare const zIndex: {
380
+ readonly dropdown: 1000;
381
+ readonly sticky: 1020;
382
+ readonly fixed: 1030;
383
+ readonly modalBackdrop: 1040;
384
+ readonly modal: 1050;
385
+ readonly popover: 1060;
386
+ readonly tooltip: 1070;
387
+ };
388
+
389
+ declare const tokens_borderRadius: typeof borderRadius;
390
+ declare const tokens_elevation: typeof elevation;
391
+ declare const tokens_fontSize: typeof fontSize;
392
+ declare const tokens_fontWeight: typeof fontWeight;
393
+ declare const tokens_lineHeight: typeof lineHeight;
394
+ declare const tokens_spacing: typeof spacing;
395
+ declare const tokens_transition: typeof transition;
396
+ declare const tokens_zIndex: typeof zIndex;
397
+ declare namespace tokens {
398
+ export { tokens_borderRadius as borderRadius, tokens_elevation as elevation, tokens_fontSize as fontSize, tokens_fontWeight as fontWeight, tokens_lineHeight as lineHeight, tokens_spacing as spacing, tokens_transition as transition, tokens_zIndex as zIndex };
399
+ }
400
+
401
+ interface HttpClientConfig {
402
+ baseURL?: string;
403
+ timeout?: number;
404
+ headers?: Record<string, string>;
405
+ }
406
+ interface RequestConfig extends Omit<RequestInit, 'body'> {
407
+ params?: Record<string, string | number | boolean>;
408
+ data?: any;
409
+ timeout?: number;
410
+ }
411
+ interface HttpResponse<T = any> {
412
+ data: T;
413
+ status: number;
414
+ statusText: string;
415
+ headers: Headers;
416
+ }
417
+
418
+ declare class HttpClient {
419
+ private baseURL;
420
+ private timeout;
421
+ private defaultHeaders;
422
+ constructor(config?: HttpClientConfig);
423
+ private buildURL;
424
+ private request;
425
+ get<T = any>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
426
+ post<T = any>(url: string, data?: any, config?: RequestConfig): Promise<HttpResponse<T>>;
427
+ put<T = any>(url: string, data?: any, config?: RequestConfig): Promise<HttpResponse<T>>;
428
+ patch<T = any>(url: string, data?: any, config?: RequestConfig): Promise<HttpResponse<T>>;
429
+ delete<T = any>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
430
+ }
431
+
432
+ /**
433
+ * Merges class names together
434
+ * Useful for conditional className composition
435
+ */
436
+ declare function cn(...classes: (string | undefined | null | false)[]): string;
437
+
438
+ /**
439
+ * Creates a debounced function that delays invoking func until after wait milliseconds
440
+ * have elapsed since the last time the debounced function was invoked
441
+ */
442
+ declare function debounce<T extends (...args: any[]) => any>(func: T, wait: number): (...args: Parameters<T>) => void;
443
+
444
+ /**
445
+ * Creates a throttled function that only invokes func at most once per every wait milliseconds
446
+ */
447
+ declare function throttle<T extends (...args: any[]) => any>(func: T, wait: number): (...args: Parameters<T>) => void;
448
+
449
+ /**
450
+ * Formats a date to a readable string
451
+ */
452
+ declare function formatDate(date: Date | string | number, options?: Intl.DateTimeFormatOptions): string;
453
+
454
+ /**
455
+ * LocalStorage utility for safely storing and retrieving data
456
+ */
457
+ declare class LocalStorage {
458
+ /**
459
+ * Get an item from localStorage
460
+ * @param key - The key to retrieve
461
+ * @returns The value or null if not found or error occurs
462
+ */
463
+ static getItem<T = string>(key: string): T | null;
464
+ /**
465
+ * Set an item in localStorage
466
+ * @param key - The key to store
467
+ * @param value - The value to store
468
+ * @returns true if successful, false otherwise
469
+ */
470
+ static setItem<T>(key: string, value: T): boolean;
471
+ /**
472
+ * Remove an item from localStorage
473
+ * @param key - The key to remove
474
+ * @returns true if successful, false otherwise
475
+ */
476
+ static removeItem(key: string): boolean;
477
+ /**
478
+ * Clear all items from localStorage
479
+ * @returns true if successful, false otherwise
480
+ */
481
+ static clear(): boolean;
482
+ /**
483
+ * Check if a key exists in localStorage
484
+ * @param key - The key to check
485
+ * @returns true if key exists, false otherwise
486
+ */
487
+ static hasItem(key: string): boolean;
488
+ /**
489
+ * Get all keys from localStorage
490
+ * @returns Array of keys
491
+ */
492
+ static keys(): string[];
493
+ }
494
+
495
+ /**
496
+ * Context State Interface
497
+ */
498
+ interface AuthContextType {
499
+ isAuthenticated: boolean;
500
+ isLoading: boolean;
501
+ }
502
+ interface AuthProviderProps {
503
+ /** Name of the cookie containing the JWT token. Defaults to "authorization" */
504
+ cookieName?: string;
505
+ /** URL to redirect the user to if authentication fails */
506
+ redirectUrl: string;
507
+ /** Components to render if deeply authenticated */
508
+ children: ReactNode;
509
+ /** Custom fallback logic to fire upon failed auth (overrides redirectUrl behavior) */
510
+ onAuthFail?: () => void;
511
+ /** Custom callback fired strictly on successful auth */
512
+ onAuthSuccess?: () => void;
513
+ /** Optional loading element while checking cookies asynchronously */
514
+ loadingElement?: ReactNode;
515
+ }
516
+ /**
517
+ * AuthProvider verifies a JWT embedded in document.cookie.
518
+ * Checks for existence, decodes the signature, and evaluates the `exp` claim.
519
+ * Redirects heavily dependent or kicks custom callbacks if failed.
520
+ */
521
+ declare const AuthProvider: React.FC<AuthProviderProps>;
522
+ /**
523
+ * React Hook for easily accessing the authenticated JWT context
524
+ */
525
+ declare const useAuth: () => AuthContextType;
526
+
527
+ /**
528
+ * A utility class to manage user permissions using the browser's localStorage.
529
+ * Employs an internal memory cache to prevent repeated synchronous parsing.
530
+ */
531
+ declare class PermissionManager {
532
+ private static cachedPermissions;
533
+ /**
534
+ * Saves an array of permission strings to local storage.
535
+ * @param permissions Array of permission strings.
536
+ */
537
+ static setPermissions(permissions: string[]): void;
538
+ /**
539
+ * Retrieves the currently stored permissions from local storage or memory cache.
540
+ * @returns Array of permission strings, or an empty array if none exist.
541
+ */
542
+ static getPermissions(): string[];
543
+ /**
544
+ * Clears all stored permissions from memory and local storage.
545
+ */
546
+ static clearPermissions(): void;
547
+ /**
548
+ * Checks whether the given permission string exists in the currently stored permissions.
549
+ * @param permission The permission string to check for.
550
+ * @returns True if the permission exists, false otherwise.
551
+ */
552
+ static hasPermission(permission: string): boolean;
553
+ }
554
+
555
+ export { type AuthContextType, AuthProvider, type AuthProviderProps, HttpClient, type HttpClientConfig, type HttpResponse, LocalStorage, type MenuItem, PermissionManager, type RequestConfig, type ResolvedThemeMode, SidebarLayout, type SidebarLayoutProps, type Theme, type ThemeContextValue, type ThemeMode, ThemeProvider, ThemeToggle, borderRadius, cn, darkTheme, debounce, elevation, fontSize, fontWeight, formatDate, lightTheme, lineHeight, spacing, throttle, tokens, transition, useAuth, useTheme, zIndex };