@tidecloak/ui-framework 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +377 -0
  2. package/dist/index.d.mts +2739 -0
  3. package/dist/index.d.ts +2739 -0
  4. package/dist/index.js +12869 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.mjs +12703 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +54 -0
  9. package/src/components/common/ActionButton.tsx +234 -0
  10. package/src/components/common/EmptyState.tsx +140 -0
  11. package/src/components/common/LoadingSkeleton.tsx +121 -0
  12. package/src/components/common/RefreshButton.tsx +127 -0
  13. package/src/components/common/StatusBadge.tsx +177 -0
  14. package/src/components/common/index.ts +31 -0
  15. package/src/components/data-table/DataTable.tsx +201 -0
  16. package/src/components/data-table/PaginatedTable.tsx +247 -0
  17. package/src/components/data-table/index.ts +2 -0
  18. package/src/components/dialogs/CollapsibleSection.tsx +184 -0
  19. package/src/components/dialogs/ConfirmDialog.tsx +264 -0
  20. package/src/components/dialogs/DetailDialog.tsx +228 -0
  21. package/src/components/dialogs/index.ts +3 -0
  22. package/src/components/index.ts +5 -0
  23. package/src/components/pages/base/ApprovalsPageBase.tsx +680 -0
  24. package/src/components/pages/base/LogsPageBase.tsx +581 -0
  25. package/src/components/pages/base/RolesPageBase.tsx +1470 -0
  26. package/src/components/pages/base/TemplatesPageBase.tsx +761 -0
  27. package/src/components/pages/base/UsersPageBase.tsx +843 -0
  28. package/src/components/pages/base/index.ts +58 -0
  29. package/src/components/pages/connected/ApprovalsPage.tsx +797 -0
  30. package/src/components/pages/connected/LogsPage.tsx +267 -0
  31. package/src/components/pages/connected/RolesPage.tsx +525 -0
  32. package/src/components/pages/connected/TemplatesPage.tsx +181 -0
  33. package/src/components/pages/connected/UsersPage.tsx +237 -0
  34. package/src/components/pages/connected/index.ts +36 -0
  35. package/src/components/pages/index.ts +5 -0
  36. package/src/components/tabs/TabsView.tsx +300 -0
  37. package/src/components/tabs/index.ts +1 -0
  38. package/src/components/ui/index.tsx +1001 -0
  39. package/src/hooks/index.ts +3 -0
  40. package/src/hooks/useAutoRefresh.ts +119 -0
  41. package/src/hooks/usePagination.ts +152 -0
  42. package/src/hooks/useSelection.ts +81 -0
  43. package/src/index.ts +256 -0
  44. package/src/theme.ts +185 -0
  45. package/src/tide/index.ts +19 -0
  46. package/src/tide/tidePolicy.ts +270 -0
  47. package/src/types/index.ts +484 -0
  48. package/src/utils/index.ts +121 -0
@@ -0,0 +1,484 @@
1
+ // ============================================================================
2
+ // CORE TYPES - Generic data structures for the UI framework
3
+ // ============================================================================
4
+
5
+ import { ReactNode } from "react";
6
+
7
+ // ============================================================================
8
+ // BASE DATA TYPES
9
+ // ============================================================================
10
+
11
+ /**
12
+ * Base interface for all data items - requires an id
13
+ */
14
+ export interface BaseDataItem {
15
+ id: string;
16
+ }
17
+
18
+ /**
19
+ * Generic change set request structure for approval workflows
20
+ */
21
+ export interface ChangeSetRequest {
22
+ changeSetId: string;
23
+ changeSetType: string;
24
+ actionType: string;
25
+ }
26
+
27
+ /**
28
+ * Base interface for items that can be approved
29
+ */
30
+ export interface ApprovableItem extends BaseDataItem {
31
+ status: string;
32
+ retrievalInfo?: ChangeSetRequest;
33
+ }
34
+
35
+ // ============================================================================
36
+ // TABLE TYPES
37
+ // ============================================================================
38
+
39
+ /**
40
+ * Column definition for data tables
41
+ * Note: T can be any type with the properties needed for rendering
42
+ */
43
+ export interface ColumnDef<T = any> {
44
+ /** Unique key for the column */
45
+ key: string;
46
+ /** Display header */
47
+ header: string | ReactNode;
48
+ /** Cell renderer */
49
+ cell: (item: T, index: number) => ReactNode;
50
+ /** Optional sorting key (if different from key) */
51
+ sortKey?: keyof T | string;
52
+ /** Enable sorting for this column */
53
+ sortable?: boolean;
54
+ /** Responsive visibility */
55
+ responsive?: "sm" | "md" | "lg" | "xl" | "always";
56
+ /** Column width class */
57
+ width?: string;
58
+ /** Alignment */
59
+ align?: "left" | "center" | "right";
60
+ }
61
+
62
+ /**
63
+ * Table action definition
64
+ */
65
+ export interface TableAction<T extends BaseDataItem> {
66
+ /** Action identifier */
67
+ key: string;
68
+ /** Display label */
69
+ label: string;
70
+ /** Icon component */
71
+ icon?: ReactNode;
72
+ /** Action handler */
73
+ onClick: (item: T) => void | Promise<void>;
74
+ /** Visibility condition */
75
+ visible?: (item: T) => boolean;
76
+ /** Disabled condition */
77
+ disabled?: (item: T) => boolean;
78
+ /** Button variant */
79
+ variant?: "default" | "destructive" | "ghost" | "link" | "outline";
80
+ /** Color class */
81
+ colorClass?: string;
82
+ }
83
+
84
+ /**
85
+ * Bulk action definition for multi-select
86
+ */
87
+ export interface BulkAction<T extends BaseDataItem> {
88
+ key: string;
89
+ label: string;
90
+ icon?: ReactNode;
91
+ onClick: (items: T[]) => void | Promise<void>;
92
+ disabled?: (items: T[]) => boolean;
93
+ variant?: "default" | "destructive" | "ghost" | "outline";
94
+ }
95
+
96
+ /**
97
+ * Sort configuration
98
+ */
99
+ export interface SortConfig {
100
+ key: string;
101
+ direction: "asc" | "desc";
102
+ }
103
+
104
+ /**
105
+ * Pagination configuration
106
+ */
107
+ export interface PaginationConfig {
108
+ page: number;
109
+ pageSize: number;
110
+ totalItems: number;
111
+ totalPages: number;
112
+ }
113
+
114
+ // ============================================================================
115
+ // TAB TYPES
116
+ // ============================================================================
117
+
118
+ /**
119
+ * Tab definition
120
+ */
121
+ export interface TabDef {
122
+ /** Unique tab key */
123
+ key: string;
124
+ /** Display label */
125
+ label: string;
126
+ /** Optional badge count */
127
+ badge?: number | null;
128
+ /** Badge variant */
129
+ badgeVariant?: "default" | "secondary" | "destructive" | "outline";
130
+ /** Tab icon */
131
+ icon?: ReactNode;
132
+ /** Tab content */
133
+ content: ReactNode;
134
+ /** Disabled state */
135
+ disabled?: boolean;
136
+ }
137
+
138
+ // ============================================================================
139
+ // DIALOG TYPES
140
+ // ============================================================================
141
+
142
+ /**
143
+ * Dialog configuration
144
+ */
145
+ export interface DialogConfig {
146
+ /** Dialog title */
147
+ title: string;
148
+ /** Dialog description */
149
+ description?: string;
150
+ /** Dialog size */
151
+ size?: "sm" | "md" | "lg" | "xl" | "full";
152
+ }
153
+
154
+ /**
155
+ * Confirmation dialog configuration
156
+ */
157
+ export interface ConfirmDialogConfig extends DialogConfig {
158
+ /** Confirm button label */
159
+ confirmLabel?: string;
160
+ /** Cancel button label */
161
+ cancelLabel?: string;
162
+ /** Confirm button variant */
163
+ confirmVariant?: "default" | "destructive";
164
+ /** Whether action is in progress */
165
+ isLoading?: boolean;
166
+ }
167
+
168
+ /**
169
+ * Detail dialog field definition
170
+ */
171
+ export interface DetailField<T> {
172
+ /** Field key */
173
+ key: string;
174
+ /** Display label */
175
+ label: string;
176
+ /** Value renderer */
177
+ render: (item: T) => ReactNode;
178
+ /** Full width */
179
+ fullWidth?: boolean;
180
+ /** Hide condition */
181
+ hidden?: (item: T) => boolean;
182
+ }
183
+
184
+ // ============================================================================
185
+ // FORM TYPES
186
+ // ============================================================================
187
+
188
+ /**
189
+ * Form field types
190
+ */
191
+ export type FormFieldType =
192
+ | "text"
193
+ | "number"
194
+ | "boolean"
195
+ | "select"
196
+ | "textarea"
197
+ | "code"
198
+ | "password"
199
+ | "email"
200
+ | "date"
201
+ | "datetime";
202
+
203
+ /**
204
+ * Form field definition
205
+ */
206
+ export interface FormFieldDef {
207
+ /** Field key */
208
+ key: string;
209
+ /** Display label */
210
+ label: string;
211
+ /** Field type */
212
+ type: FormFieldType;
213
+ /** Help text */
214
+ helpText?: string;
215
+ /** Required field */
216
+ required?: boolean;
217
+ /** Placeholder text */
218
+ placeholder?: string;
219
+ /** Default value */
220
+ defaultValue?: unknown;
221
+ /** Options for select fields */
222
+ options?: SelectOption[];
223
+ /** Validation function */
224
+ validate?: (value: unknown) => string | null;
225
+ /** Code editor language */
226
+ codeLanguage?: string;
227
+ /** Disabled condition */
228
+ disabled?: boolean;
229
+ /** Full width */
230
+ fullWidth?: boolean;
231
+ }
232
+
233
+ /**
234
+ * Select option
235
+ */
236
+ export interface SelectOption {
237
+ label: string;
238
+ value: string | number | boolean;
239
+ description?: string;
240
+ }
241
+
242
+ /**
243
+ * Dynamic parameter definition (for policy templates etc.)
244
+ */
245
+ export interface ParameterDef {
246
+ name: string;
247
+ type: "string" | "number" | "boolean" | "select";
248
+ helpText: string;
249
+ required: boolean;
250
+ defaultValue?: unknown;
251
+ options?: string[];
252
+ }
253
+
254
+ // ============================================================================
255
+ // STATUS & BADGE TYPES
256
+ // ============================================================================
257
+
258
+ /**
259
+ * Status types with associated colors
260
+ */
261
+ export type StatusType =
262
+ | "pending"
263
+ | "approved"
264
+ | "rejected"
265
+ | "committed"
266
+ | "cancelled"
267
+ | "mixed"
268
+ | "ready"
269
+ | "error"
270
+ | "warning"
271
+ | "info"
272
+ | "success";
273
+
274
+ /**
275
+ * Status configuration
276
+ */
277
+ export interface StatusConfig {
278
+ label: string;
279
+ variant: StatusType;
280
+ icon?: ReactNode;
281
+ }
282
+
283
+ /**
284
+ * Badge configuration for custom badges
285
+ */
286
+ export interface BadgeConfig {
287
+ label: string;
288
+ colorClass?: string;
289
+ variant?: "default" | "secondary" | "destructive" | "outline";
290
+ }
291
+
292
+ // ============================================================================
293
+ // ACTION TYPES
294
+ // ============================================================================
295
+
296
+ /**
297
+ * Action result for async operations
298
+ */
299
+ export interface ActionResult {
300
+ success: boolean;
301
+ message?: string;
302
+ error?: Error;
303
+ }
304
+
305
+ /**
306
+ * Async action handler type
307
+ */
308
+ export type AsyncActionHandler<T = void, R = ActionResult> = (arg: T) => Promise<R>;
309
+
310
+ // ============================================================================
311
+ // APPROVAL WORKFLOW TYPES
312
+ // ============================================================================
313
+
314
+ /**
315
+ * Approval decision
316
+ */
317
+ export interface ApprovalDecision {
318
+ userId: string;
319
+ userName?: string;
320
+ userEmail?: string;
321
+ approved: boolean;
322
+ timestamp: string | number;
323
+ signature?: string;
324
+ }
325
+
326
+ /**
327
+ * Approval status with threshold
328
+ */
329
+ export interface ApprovalStatus {
330
+ approvalCount: number;
331
+ rejectionCount: number;
332
+ threshold: number;
333
+ decisions?: ApprovalDecision[];
334
+ }
335
+
336
+ /**
337
+ * Raw approval request for signing
338
+ */
339
+ export interface RawApprovalRequest {
340
+ changesetId: string;
341
+ changeSetDraftRequests: string; // Base64 encoded
342
+ requiresApprovalPopup?: boolean;
343
+ }
344
+
345
+ /**
346
+ * Signed approval response
347
+ */
348
+ export interface SignedApprovalResponse {
349
+ id: string;
350
+ approved?: {
351
+ request: Uint8Array;
352
+ };
353
+ denied?: boolean;
354
+ }
355
+
356
+ // ============================================================================
357
+ // HOOK TYPES
358
+ // ============================================================================
359
+
360
+ export interface AutoRefreshOptions {
361
+ /** Refresh interval in seconds */
362
+ intervalSeconds: number;
363
+ /** Whether auto-refresh is enabled */
364
+ enabled?: boolean;
365
+ /** Refresh callback */
366
+ refresh: () => void | Promise<unknown>;
367
+ /** Block refresh while condition is true */
368
+ isBlocked?: boolean;
369
+ /** Error callback - called when refresh fails */
370
+ onError?: (error: Error) => void;
371
+ /** Max consecutive failures before stopping (default: unlimited) */
372
+ maxRetries?: number;
373
+ }
374
+
375
+ export interface AutoRefreshReturn {
376
+ /** Seconds until next refresh */
377
+ secondsRemaining: number | null;
378
+ /** Trigger immediate refresh */
379
+ refreshNow: () => Promise<void>;
380
+ /** Reset the timer */
381
+ resetTimer: () => void;
382
+ /** Last error from refresh (null if no error) */
383
+ lastError: Error | null;
384
+ /** Number of consecutive failures */
385
+ failureCount: number;
386
+ /** Whether auto-refresh stopped due to max retries */
387
+ isStopped: boolean;
388
+ }
389
+
390
+ /**
391
+ * Selection hook return type
392
+ */
393
+ export interface SelectionReturn<T extends BaseDataItem> {
394
+ /** Currently selected item IDs */
395
+ selectedIds: string[];
396
+ /** Check if item is selected */
397
+ isSelected: (id: string) => boolean;
398
+ /** Toggle item selection */
399
+ toggle: (id: string) => void;
400
+ /** Select all items */
401
+ selectAll: (items: T[]) => void;
402
+ /** Clear all selections */
403
+ clearAll: () => void;
404
+ /** Toggle all items */
405
+ toggleAll: (items: T[]) => void;
406
+ /** Get selected items from a list */
407
+ getSelected: (items: T[]) => T[];
408
+ /** Whether all items are selected */
409
+ allSelected: (items: T[]) => boolean;
410
+ /** Whether some (but not all) items are selected */
411
+ someSelected: (items: T[]) => boolean;
412
+ }
413
+
414
+ /**
415
+ * Pagination hook return type
416
+ */
417
+ export interface PaginationReturn {
418
+ page: number;
419
+ pageSize: number;
420
+ setPage: (page: number) => void;
421
+ setPageSize: (size: number) => void;
422
+ nextPage: () => void;
423
+ prevPage: () => void;
424
+ canNextPage: boolean;
425
+ canPrevPage: boolean;
426
+ totalPages: number;
427
+ startIndex: number;
428
+ endIndex: number;
429
+ }
430
+
431
+ // ============================================================================
432
+ // UTILITY TYPES
433
+ // ============================================================================
434
+
435
+ /**
436
+ * Toast notification type
437
+ */
438
+ export interface ToastConfig {
439
+ title: string;
440
+ description?: string;
441
+ variant?: "default" | "destructive";
442
+ duration?: number;
443
+ }
444
+
445
+ /**
446
+ * Empty state configuration
447
+ */
448
+ export interface EmptyStateConfig {
449
+ icon?: ReactNode;
450
+ title: string;
451
+ description?: string;
452
+ action?: {
453
+ label: string;
454
+ onClick: () => void;
455
+ };
456
+ }
457
+
458
+ /**
459
+ * Loading state configuration
460
+ */
461
+ export interface LoadingStateConfig {
462
+ /** Number of skeleton rows */
463
+ rows?: number;
464
+ /** Type of skeleton */
465
+ type?: "table" | "card" | "list";
466
+ }
467
+
468
+ // ============================================================================
469
+ // PROVIDER TYPES
470
+ // ============================================================================
471
+
472
+ /**
473
+ * Framework configuration
474
+ */
475
+ export interface FrameworkConfig {
476
+ /** Toast function (from your toast library) */
477
+ toast?: (config: ToastConfig) => void;
478
+ /** Custom class name generator */
479
+ cn?: (...inputs: unknown[]) => string;
480
+ /** Default page size */
481
+ defaultPageSize?: number;
482
+ /** Default auto-refresh interval */
483
+ defaultRefreshInterval?: number;
484
+ }
@@ -0,0 +1,121 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ /**
5
+ * Merge Tailwind classes with clsx
6
+ */
7
+ export function cn(...inputs: ClassValue[]) {
8
+ return twMerge(clsx(inputs));
9
+ }
10
+
11
+ /**
12
+ * Convert base64 string to Uint8Array
13
+ */
14
+ export function base64ToBytes(base64: string): Uint8Array {
15
+ const binaryString = atob(base64);
16
+ const bytes = new Uint8Array(binaryString.length);
17
+ for (let i = 0; i < binaryString.length; i++) {
18
+ bytes[i] = binaryString.charCodeAt(i);
19
+ }
20
+ return bytes;
21
+ }
22
+
23
+ /**
24
+ * Convert Uint8Array to base64 string
25
+ */
26
+ export function bytesToBase64(bytes: Uint8Array): string {
27
+ let binary = "";
28
+ for (let i = 0; i < bytes.length; i++) {
29
+ binary += String.fromCharCode(bytes[i]);
30
+ }
31
+ return btoa(binary);
32
+ }
33
+
34
+ /**
35
+ * Format timestamp to locale string
36
+ */
37
+ export function formatTimestamp(
38
+ timestamp: string | number,
39
+ options?: Intl.DateTimeFormatOptions
40
+ ): string {
41
+ const date = typeof timestamp === "number"
42
+ ? new Date(timestamp > 1e12 ? timestamp : timestamp * 1000) // Handle both ms and seconds
43
+ : new Date(timestamp);
44
+
45
+ return date.toLocaleString("en-US", options ?? {
46
+ month: "short",
47
+ day: "numeric",
48
+ year: "numeric",
49
+ hour: "2-digit",
50
+ minute: "2-digit",
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Format date for logs (includes seconds)
56
+ */
57
+ export function formatLogTimestamp(timestampMs: number): string {
58
+ const date = new Date(timestampMs);
59
+ return date.toLocaleString("en-US", {
60
+ month: "short",
61
+ day: "numeric",
62
+ year: "numeric",
63
+ hour: "numeric",
64
+ minute: "2-digit",
65
+ second: "2-digit",
66
+ hour12: true,
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Compute optimal number of rows for viewport height
72
+ */
73
+ export function computeRowsForViewportHeight(
74
+ viewportHeight: number,
75
+ headerPx = 48,
76
+ rowPx = 44
77
+ ): number {
78
+ const availablePx = Math.max(0, viewportHeight - headerPx);
79
+ const rows = Math.floor(availablePx / rowPx);
80
+ return Math.max(5, Math.min(200, rows));
81
+ }
82
+
83
+ /**
84
+ * Delay helper for async operations
85
+ */
86
+ export function delay(ms: number): Promise<void> {
87
+ return new Promise((resolve) => setTimeout(resolve, ms));
88
+ }
89
+
90
+ /**
91
+ * Safe JSON parse
92
+ */
93
+ export function safeJsonParse<T>(json: string, fallback: T): T {
94
+ try {
95
+ return JSON.parse(json);
96
+ } catch {
97
+ return fallback;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Truncate text with ellipsis
103
+ */
104
+ export function truncate(text: string, maxLength: number): string {
105
+ if (text.length <= maxLength) return text;
106
+ return text.slice(0, maxLength - 3) + "...";
107
+ }
108
+
109
+ /**
110
+ * Check if a value is defined (not null or undefined)
111
+ */
112
+ export function isDefined<T>(value: T | null | undefined): value is T {
113
+ return value !== null && value !== undefined;
114
+ }
115
+
116
+ /**
117
+ * Generate unique ID
118
+ */
119
+ export function generateId(): string {
120
+ return Math.random().toString(36).substring(2, 9);
121
+ }