@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,1001 @@
1
+ /**
2
+ * Default UI Components
3
+ *
4
+ * These are simple, styled components that work out of the box.
5
+ * Users can override them by passing their own components prop.
6
+ */
7
+
8
+ import React, { useEffect } from "react";
9
+
10
+ // Inject CSS keyframes for animations
11
+ const KEYFRAMES_ID = "tidecloak-ui-keyframes";
12
+ const KEYFRAMES_CSS = `
13
+ @keyframes spin {
14
+ from { transform: rotate(0deg); }
15
+ to { transform: rotate(360deg); }
16
+ }
17
+ @keyframes pulse {
18
+ 0%, 100% { opacity: 1; }
19
+ 50% { opacity: 0.5; }
20
+ }
21
+ `;
22
+
23
+ function injectKeyframes() {
24
+ if (typeof document === "undefined") return;
25
+ if (document.getElementById(KEYFRAMES_ID)) return;
26
+ const style = document.createElement("style");
27
+ style.id = KEYFRAMES_ID;
28
+ style.textContent = KEYFRAMES_CSS;
29
+ document.head.appendChild(style);
30
+ }
31
+
32
+ // Inject keyframes on module load (client-side only)
33
+ if (typeof window !== "undefined") {
34
+ injectKeyframes();
35
+ }
36
+
37
+ // Shared styles
38
+ const baseButtonStyle: React.CSSProperties = {
39
+ display: "inline-flex",
40
+ alignItems: "center",
41
+ justifyContent: "center",
42
+ gap: "0.5rem",
43
+ borderRadius: "0.375rem",
44
+ fontSize: "0.875rem",
45
+ fontWeight: 500,
46
+ cursor: "pointer",
47
+ border: "1px solid transparent",
48
+ transition: "all 0.2s",
49
+ padding: "0.5rem 1rem",
50
+ };
51
+
52
+ // Card
53
+ export function Card({ children, className, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
54
+ return (
55
+ <div
56
+ style={{
57
+ backgroundColor: "#fff",
58
+ borderRadius: "0.5rem",
59
+ border: "1px solid #e5e7eb",
60
+ boxShadow: "0 1px 3px 0 rgba(0, 0, 0, 0.1)",
61
+ ...style,
62
+ }}
63
+ {...props}
64
+ >
65
+ {children}
66
+ </div>
67
+ );
68
+ }
69
+
70
+ export function CardContent({ children, className, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
71
+ return (
72
+ <div style={{ padding: "1.5rem", ...style }} {...props}>
73
+ {children}
74
+ </div>
75
+ );
76
+ }
77
+
78
+ export function CardHeader({ children, className, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
79
+ return (
80
+ <div style={{ padding: "1.5rem 1.5rem 0", ...style }} {...props}>
81
+ {children}
82
+ </div>
83
+ );
84
+ }
85
+
86
+ export function CardTitle({ children, className, style, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
87
+ return (
88
+ <h3 style={{ fontSize: "1.125rem", fontWeight: 600, margin: 0, ...style }} {...props}>
89
+ {children}
90
+ </h3>
91
+ );
92
+ }
93
+
94
+ export function CardDescription({ children, className, style, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
95
+ return (
96
+ <p style={{ fontSize: "0.875rem", color: "#6b7280", margin: "0.25rem 0 0", ...style }} {...props}>
97
+ {children}
98
+ </p>
99
+ );
100
+ }
101
+
102
+ // Button
103
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
104
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
105
+ size?: "default" | "sm" | "lg" | "icon";
106
+ }
107
+
108
+ export function Button({ children, variant = "default", size = "default", style, ...props }: ButtonProps) {
109
+ const variantStyles: Record<string, React.CSSProperties> = {
110
+ default: { backgroundColor: "#1f2937", color: "#fff", border: "1px solid #1f2937" },
111
+ destructive: { backgroundColor: "#dc2626", color: "#fff", border: "1px solid #dc2626" },
112
+ outline: { backgroundColor: "transparent", color: "#1f2937", border: "1px solid #d1d5db" },
113
+ secondary: { backgroundColor: "#f3f4f6", color: "#1f2937", border: "1px solid #e5e7eb" },
114
+ ghost: { backgroundColor: "transparent", color: "#1f2937", border: "1px solid transparent" },
115
+ link: { backgroundColor: "transparent", color: "#2563eb", border: "none", textDecoration: "underline" },
116
+ };
117
+
118
+ const sizeStyles: Record<string, React.CSSProperties> = {
119
+ default: { height: "2.5rem", padding: "0.5rem 1rem" },
120
+ sm: { height: "2rem", padding: "0.25rem 0.75rem", fontSize: "0.75rem" },
121
+ lg: { height: "3rem", padding: "0.75rem 2rem" },
122
+ icon: { height: "2.5rem", width: "2.5rem", padding: "0" },
123
+ };
124
+
125
+ return (
126
+ <button
127
+ style={{
128
+ ...baseButtonStyle,
129
+ ...variantStyles[variant],
130
+ ...sizeStyles[size],
131
+ ...style,
132
+ }}
133
+ {...props}
134
+ >
135
+ {children}
136
+ </button>
137
+ );
138
+ }
139
+
140
+ // Badge
141
+ interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
142
+ variant?: "default" | "secondary" | "destructive" | "outline";
143
+ }
144
+
145
+ export function Badge({ children, variant = "default", style, ...props }: BadgeProps) {
146
+ const variantStyles: Record<string, React.CSSProperties> = {
147
+ default: { backgroundColor: "#1f2937", color: "#fff" },
148
+ secondary: { backgroundColor: "#f3f4f6", color: "#1f2937" },
149
+ destructive: { backgroundColor: "#fef2f2", color: "#dc2626" },
150
+ outline: { backgroundColor: "transparent", color: "#1f2937", border: "1px solid #d1d5db" },
151
+ };
152
+
153
+ return (
154
+ <span
155
+ style={{
156
+ display: "inline-flex",
157
+ alignItems: "center",
158
+ borderRadius: "9999px",
159
+ padding: "0.125rem 0.625rem",
160
+ fontSize: "0.75rem",
161
+ fontWeight: 500,
162
+ ...variantStyles[variant],
163
+ ...style,
164
+ }}
165
+ {...props}
166
+ >
167
+ {children}
168
+ </span>
169
+ );
170
+ }
171
+
172
+ // Input
173
+ export function Input({ className, style, ...props }: React.InputHTMLAttributes<HTMLInputElement>) {
174
+ return (
175
+ <input
176
+ style={{
177
+ display: "flex",
178
+ width: "100%",
179
+ height: "2.5rem",
180
+ borderRadius: "0.375rem",
181
+ border: "1px solid #d1d5db",
182
+ backgroundColor: "#fff",
183
+ padding: "0.5rem 0.75rem",
184
+ fontSize: "0.875rem",
185
+ outline: "none",
186
+ boxSizing: "border-box",
187
+ ...style,
188
+ }}
189
+ {...props}
190
+ />
191
+ );
192
+ }
193
+
194
+ // Label
195
+ export function Label({ children, className, style, ...props }: React.LabelHTMLAttributes<HTMLLabelElement>) {
196
+ return (
197
+ <label
198
+ style={{
199
+ fontSize: "0.875rem",
200
+ fontWeight: 500,
201
+ color: "#1f2937",
202
+ ...style,
203
+ }}
204
+ {...props}
205
+ >
206
+ {children}
207
+ </label>
208
+ );
209
+ }
210
+
211
+ // Textarea
212
+ export function Textarea({ className, style, ...props }: React.TextareaHTMLAttributes<HTMLTextAreaElement>) {
213
+ return (
214
+ <textarea
215
+ style={{
216
+ display: "flex",
217
+ width: "100%",
218
+ minHeight: "5rem",
219
+ borderRadius: "0.375rem",
220
+ border: "1px solid #d1d5db",
221
+ backgroundColor: "#fff",
222
+ padding: "0.5rem 0.75rem",
223
+ fontSize: "0.875rem",
224
+ outline: "none",
225
+ resize: "vertical",
226
+ boxSizing: "border-box",
227
+ ...style,
228
+ }}
229
+ {...props}
230
+ />
231
+ );
232
+ }
233
+
234
+ // Checkbox
235
+ interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
236
+ onCheckedChange?: (checked: boolean) => void;
237
+ }
238
+
239
+ export function Checkbox({ onCheckedChange, onChange, style, ...props }: CheckboxProps) {
240
+ return (
241
+ <input
242
+ type="checkbox"
243
+ style={{
244
+ width: "1rem",
245
+ height: "1rem",
246
+ cursor: "pointer",
247
+ ...style,
248
+ }}
249
+ onChange={(e) => {
250
+ onChange?.(e);
251
+ onCheckedChange?.(e.target.checked);
252
+ }}
253
+ {...props}
254
+ />
255
+ );
256
+ }
257
+
258
+ // Select components - with proper open/close state management
259
+ const SelectContext = React.createContext<{
260
+ value: string;
261
+ onValueChange: (value: string) => void;
262
+ open: boolean;
263
+ setOpen: (open: boolean) => void;
264
+ }>({ value: "", onValueChange: () => {}, open: false, setOpen: () => {} });
265
+
266
+ export function Select({ children, onValueChange, value = "", ...props }: {
267
+ children: React.ReactNode;
268
+ onValueChange?: (value: string) => void;
269
+ value?: string;
270
+ }) {
271
+ const [open, setOpen] = React.useState(false);
272
+ const containerRef = React.useRef<HTMLDivElement>(null);
273
+
274
+ // Close on click outside
275
+ useEffect(() => {
276
+ if (!open) return;
277
+
278
+ const handleClickOutside = (event: MouseEvent) => {
279
+ if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
280
+ setOpen(false);
281
+ }
282
+ };
283
+
284
+ document.addEventListener("mousedown", handleClickOutside);
285
+ return () => document.removeEventListener("mousedown", handleClickOutside);
286
+ }, [open]);
287
+
288
+ const handleValueChange = (newValue: string) => {
289
+ onValueChange?.(newValue);
290
+ setOpen(false);
291
+ };
292
+
293
+ return (
294
+ <SelectContext.Provider value={{ value, onValueChange: handleValueChange, open, setOpen }}>
295
+ <div ref={containerRef} style={{ position: "relative" }}>{children}</div>
296
+ </SelectContext.Provider>
297
+ );
298
+ }
299
+
300
+ export function SelectTrigger({ children, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
301
+ const { open, setOpen } = React.useContext(SelectContext);
302
+
303
+ return (
304
+ <div
305
+ style={{
306
+ display: "flex",
307
+ alignItems: "center",
308
+ justifyContent: "space-between",
309
+ height: "2.5rem",
310
+ width: "100%",
311
+ borderRadius: "0.375rem",
312
+ border: "1px solid #d1d5db",
313
+ backgroundColor: "#fff",
314
+ padding: "0.5rem 0.75rem",
315
+ fontSize: "0.875rem",
316
+ cursor: "pointer",
317
+ ...style,
318
+ }}
319
+ onClick={() => setOpen(!open)}
320
+ {...props}
321
+ >
322
+ {children}
323
+ <svg
324
+ style={{
325
+ width: "1rem",
326
+ height: "1rem",
327
+ marginLeft: "0.5rem",
328
+ color: "#6b7280",
329
+ transform: open ? "rotate(180deg)" : "rotate(0deg)",
330
+ transition: "transform 0.2s",
331
+ }}
332
+ fill="none"
333
+ stroke="currentColor"
334
+ viewBox="0 0 24 24"
335
+ >
336
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
337
+ </svg>
338
+ </div>
339
+ );
340
+ }
341
+
342
+ export function SelectValue({ placeholder }: { placeholder?: string }) {
343
+ const { value } = React.useContext(SelectContext);
344
+ return <span style={{ color: value ? "#1f2937" : "#9ca3af" }}>{value || placeholder}</span>;
345
+ }
346
+
347
+ export function SelectContent({ children, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
348
+ const { open } = React.useContext(SelectContext);
349
+
350
+ if (!open) return null;
351
+
352
+ return (
353
+ <div
354
+ style={{
355
+ position: "absolute",
356
+ top: "100%",
357
+ left: 0,
358
+ right: 0,
359
+ marginTop: "0.25rem",
360
+ backgroundColor: "#fff",
361
+ border: "1px solid #d1d5db",
362
+ borderRadius: "0.375rem",
363
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
364
+ zIndex: 50,
365
+ maxHeight: "15rem",
366
+ overflow: "auto",
367
+ ...style,
368
+ }}
369
+ {...props}
370
+ >
371
+ {children}
372
+ </div>
373
+ );
374
+ }
375
+
376
+ export function SelectItem({ children, value, style, ...props }: React.HTMLAttributes<HTMLDivElement> & { value: string }) {
377
+ const { value: selectedValue, onValueChange } = React.useContext(SelectContext);
378
+ const isSelected = selectedValue === value;
379
+
380
+ return (
381
+ <div
382
+ style={{
383
+ padding: "0.5rem 0.75rem",
384
+ cursor: "pointer",
385
+ fontSize: "0.875rem",
386
+ backgroundColor: isSelected ? "#f3f4f6" : "transparent",
387
+ ...style,
388
+ }}
389
+ onClick={() => onValueChange(value)}
390
+ onMouseEnter={(e) => {
391
+ if (!isSelected) e.currentTarget.style.backgroundColor = "#f9fafb";
392
+ }}
393
+ onMouseLeave={(e) => {
394
+ if (!isSelected) e.currentTarget.style.backgroundColor = "transparent";
395
+ }}
396
+ {...props}
397
+ >
398
+ {children}
399
+ </div>
400
+ );
401
+ }
402
+
403
+ // Skeleton
404
+ export function Skeleton({ className, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
405
+ return (
406
+ <div
407
+ style={{
408
+ backgroundColor: "#e5e7eb",
409
+ borderRadius: "0.375rem",
410
+ animation: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
411
+ ...style,
412
+ }}
413
+ {...props}
414
+ />
415
+ );
416
+ }
417
+
418
+ // Table components
419
+ export function Table({ children, style, ...props }: React.HTMLAttributes<HTMLTableElement>) {
420
+ return (
421
+ <div style={{ width: "100%", overflow: "auto" }}>
422
+ <table style={{ width: "100%", borderCollapse: "collapse", fontSize: "0.875rem", ...style }} {...props}>
423
+ {children}
424
+ </table>
425
+ </div>
426
+ );
427
+ }
428
+
429
+ export function TableHeader({ children, style, ...props }: React.HTMLAttributes<HTMLTableSectionElement>) {
430
+ return (
431
+ <thead style={{ borderBottom: "1px solid #e5e7eb", ...style }} {...props}>
432
+ {children}
433
+ </thead>
434
+ );
435
+ }
436
+
437
+ export function TableBody({ children, style, ...props }: React.HTMLAttributes<HTMLTableSectionElement>) {
438
+ return <tbody style={style} {...props}>{children}</tbody>;
439
+ }
440
+
441
+ export function TableRow({ children, style, ...props }: React.HTMLAttributes<HTMLTableRowElement>) {
442
+ return (
443
+ <tr style={{ borderBottom: "1px solid #e5e7eb", ...style }} {...props}>
444
+ {children}
445
+ </tr>
446
+ );
447
+ }
448
+
449
+ export function TableHead({ children, style, ...props }: React.ThHTMLAttributes<HTMLTableCellElement>) {
450
+ return (
451
+ <th
452
+ style={{
453
+ height: "3rem",
454
+ padding: "0 1rem",
455
+ textAlign: "left",
456
+ fontWeight: 500,
457
+ color: "#6b7280",
458
+ ...style,
459
+ }}
460
+ {...props}
461
+ >
462
+ {children}
463
+ </th>
464
+ );
465
+ }
466
+
467
+ export function TableCell({ children, style, ...props }: React.TdHTMLAttributes<HTMLTableCellElement>) {
468
+ return (
469
+ <td style={{ padding: "1rem", ...style }} {...props}>
470
+ {children}
471
+ </td>
472
+ );
473
+ }
474
+
475
+ // Dialog components
476
+ interface DialogProps {
477
+ open?: boolean;
478
+ onOpenChange?: (open: boolean) => void;
479
+ children: React.ReactNode;
480
+ }
481
+
482
+ export function Dialog({ open, onOpenChange, children }: DialogProps) {
483
+ if (!open) return null;
484
+
485
+ return (
486
+ <div
487
+ style={{
488
+ position: "fixed",
489
+ inset: 0,
490
+ zIndex: 50,
491
+ display: "flex",
492
+ alignItems: "center",
493
+ justifyContent: "center",
494
+ }}
495
+ >
496
+ <div
497
+ style={{
498
+ position: "fixed",
499
+ inset: 0,
500
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
501
+ }}
502
+ onClick={() => onOpenChange?.(false)}
503
+ />
504
+ <div style={{ position: "relative", zIndex: 51 }}>{children}</div>
505
+ </div>
506
+ );
507
+ }
508
+
509
+ export function DialogContent({ children, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
510
+ return (
511
+ <div
512
+ style={{
513
+ backgroundColor: "#fff",
514
+ borderRadius: "0.5rem",
515
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
516
+ padding: "1.5rem",
517
+ width: "100%",
518
+ maxWidth: "32rem",
519
+ maxHeight: "85vh",
520
+ overflow: "auto",
521
+ ...style,
522
+ }}
523
+ {...props}
524
+ >
525
+ {children}
526
+ </div>
527
+ );
528
+ }
529
+
530
+ export function DialogHeader({ children, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
531
+ return (
532
+ <div style={{ marginBottom: "1rem", ...style }} {...props}>
533
+ {children}
534
+ </div>
535
+ );
536
+ }
537
+
538
+ export function DialogTitle({ children, style, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
539
+ return (
540
+ <h2 style={{ fontSize: "1.125rem", fontWeight: 600, margin: 0, ...style }} {...props}>
541
+ {children}
542
+ </h2>
543
+ );
544
+ }
545
+
546
+ export function DialogDescription({ children, style, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
547
+ return (
548
+ <p style={{ fontSize: "0.875rem", color: "#6b7280", margin: "0.25rem 0 0", ...style }} {...props}>
549
+ {children}
550
+ </p>
551
+ );
552
+ }
553
+
554
+ export function DialogFooter({ children, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
555
+ return (
556
+ <div
557
+ style={{
558
+ display: "flex",
559
+ justifyContent: "flex-end",
560
+ gap: "0.5rem",
561
+ marginTop: "1.5rem",
562
+ ...style,
563
+ }}
564
+ {...props}
565
+ >
566
+ {children}
567
+ </div>
568
+ );
569
+ }
570
+
571
+ // Tabs components
572
+ interface TabsProps {
573
+ value?: string;
574
+ defaultValue?: string;
575
+ onValueChange?: (value: string) => void;
576
+ children: React.ReactNode;
577
+ }
578
+
579
+ const TabsContext = React.createContext<{
580
+ value: string;
581
+ onValueChange: (value: string) => void;
582
+ }>({ value: "", onValueChange: () => {} });
583
+
584
+ export function Tabs({ value, defaultValue = "", onValueChange, children }: TabsProps) {
585
+ const [internalValue, setInternalValue] = React.useState(defaultValue);
586
+ const currentValue = value ?? internalValue;
587
+
588
+ const handleChange = (newValue: string) => {
589
+ setInternalValue(newValue);
590
+ onValueChange?.(newValue);
591
+ };
592
+
593
+ return (
594
+ <TabsContext.Provider value={{ value: currentValue, onValueChange: handleChange }}>
595
+ <div>{children}</div>
596
+ </TabsContext.Provider>
597
+ );
598
+ }
599
+
600
+ export function TabsList({ children, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
601
+ return (
602
+ <div
603
+ style={{
604
+ display: "inline-flex",
605
+ backgroundColor: "#f3f4f6",
606
+ borderRadius: "0.5rem",
607
+ padding: "0.25rem",
608
+ gap: "0.25rem",
609
+ ...style,
610
+ }}
611
+ {...props}
612
+ >
613
+ {children}
614
+ </div>
615
+ );
616
+ }
617
+
618
+ export function TabsTrigger({ children, value, style, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement> & { value: string }) {
619
+ const { value: currentValue, onValueChange } = React.useContext(TabsContext);
620
+ const isActive = currentValue === value;
621
+
622
+ return (
623
+ <button
624
+ style={{
625
+ display: "inline-flex",
626
+ alignItems: "center",
627
+ justifyContent: "center",
628
+ padding: "0.5rem 0.75rem",
629
+ fontSize: "0.875rem",
630
+ fontWeight: 500,
631
+ borderRadius: "0.375rem",
632
+ border: "none",
633
+ cursor: "pointer",
634
+ backgroundColor: isActive ? "#fff" : "transparent",
635
+ color: isActive ? "#1f2937" : "#6b7280",
636
+ boxShadow: isActive ? "0 1px 2px 0 rgba(0, 0, 0, 0.05)" : "none",
637
+ ...style,
638
+ }}
639
+ onClick={() => onValueChange(value)}
640
+ {...props}
641
+ >
642
+ {children}
643
+ </button>
644
+ );
645
+ }
646
+
647
+ export function TabsContent({ children, value, style, ...props }: React.HTMLAttributes<HTMLDivElement> & { value: string }) {
648
+ const { value: currentValue } = React.useContext(TabsContext);
649
+
650
+ if (currentValue !== value) return null;
651
+
652
+ return (
653
+ <div style={{ marginTop: "1rem", ...style }} {...props}>
654
+ {children}
655
+ </div>
656
+ );
657
+ }
658
+
659
+ // AlertDialog (uses same base as Dialog)
660
+ export function AlertDialog({ open, onOpenChange, children }: {
661
+ open?: boolean;
662
+ onOpenChange?: (open: boolean) => void;
663
+ children: React.ReactNode;
664
+ }) {
665
+ if (!open) return null;
666
+
667
+ return (
668
+ <div
669
+ style={{
670
+ position: "fixed",
671
+ inset: 0,
672
+ zIndex: 50,
673
+ display: "flex",
674
+ alignItems: "center",
675
+ justifyContent: "center",
676
+ }}
677
+ >
678
+ <div
679
+ style={{
680
+ position: "fixed",
681
+ inset: 0,
682
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
683
+ }}
684
+ onClick={() => onOpenChange?.(false)}
685
+ />
686
+ <div style={{ position: "relative", zIndex: 51 }}>{children}</div>
687
+ </div>
688
+ );
689
+ }
690
+
691
+ export function AlertDialogContent({ children, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
692
+ return (
693
+ <div
694
+ style={{
695
+ backgroundColor: "#fff",
696
+ borderRadius: "0.5rem",
697
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
698
+ padding: "1.5rem",
699
+ width: "100%",
700
+ maxWidth: "32rem",
701
+ ...style,
702
+ }}
703
+ onClick={(e) => e.stopPropagation()}
704
+ {...props}
705
+ >
706
+ {children}
707
+ </div>
708
+ );
709
+ }
710
+
711
+ export function AlertDialogHeader({ children, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
712
+ return (
713
+ <div style={{ marginBottom: "1rem", ...style }} {...props}>
714
+ {children}
715
+ </div>
716
+ );
717
+ }
718
+
719
+ export function AlertDialogTitle({ children, style, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
720
+ return (
721
+ <h2 style={{ fontSize: "1.125rem", fontWeight: 600, margin: 0, ...style }} {...props}>
722
+ {children}
723
+ </h2>
724
+ );
725
+ }
726
+
727
+ export function AlertDialogDescription({ children, style, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
728
+ return (
729
+ <p style={{ fontSize: "0.875rem", color: "#6b7280", margin: "0.25rem 0 0", ...style }} {...props}>
730
+ {children}
731
+ </p>
732
+ );
733
+ }
734
+
735
+ export function AlertDialogFooter({ children, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
736
+ return (
737
+ <div
738
+ style={{
739
+ display: "flex",
740
+ justifyContent: "flex-end",
741
+ gap: "0.5rem",
742
+ marginTop: "1.5rem",
743
+ ...style,
744
+ }}
745
+ {...props}
746
+ >
747
+ {children}
748
+ </div>
749
+ );
750
+ }
751
+
752
+ export function AlertDialogCancel({ children, style, onClick, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
753
+ return (
754
+ <button
755
+ style={{
756
+ ...baseButtonStyle,
757
+ backgroundColor: "transparent",
758
+ color: "#1f2937",
759
+ border: "1px solid #d1d5db",
760
+ ...style,
761
+ }}
762
+ onClick={onClick}
763
+ {...props}
764
+ >
765
+ {children}
766
+ </button>
767
+ );
768
+ }
769
+
770
+ export function AlertDialogAction({ children, style, onClick, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
771
+ return (
772
+ <button
773
+ style={{
774
+ ...baseButtonStyle,
775
+ backgroundColor: "#dc2626",
776
+ color: "#fff",
777
+ border: "1px solid #dc2626",
778
+ ...style,
779
+ }}
780
+ onClick={onClick}
781
+ {...props}
782
+ >
783
+ {children}
784
+ </button>
785
+ );
786
+ }
787
+
788
+ // Switch
789
+ interface SwitchProps {
790
+ checked?: boolean;
791
+ onCheckedChange?: (checked: boolean) => void;
792
+ disabled?: boolean;
793
+ style?: React.CSSProperties;
794
+ }
795
+
796
+ export function Switch({ checked, onCheckedChange, disabled, style }: SwitchProps) {
797
+ return (
798
+ <button
799
+ type="button"
800
+ role="switch"
801
+ aria-checked={checked}
802
+ disabled={disabled}
803
+ onClick={() => onCheckedChange?.(!checked)}
804
+ style={{
805
+ width: "2.75rem",
806
+ height: "1.5rem",
807
+ borderRadius: "9999px",
808
+ backgroundColor: checked ? "#1f2937" : "#d1d5db",
809
+ position: "relative",
810
+ cursor: disabled ? "not-allowed" : "pointer",
811
+ opacity: disabled ? 0.5 : 1,
812
+ border: "none",
813
+ padding: 0,
814
+ transition: "background-color 0.2s",
815
+ ...style,
816
+ }}
817
+ >
818
+ <span
819
+ style={{
820
+ position: "absolute",
821
+ top: "2px",
822
+ left: checked ? "calc(100% - 1.25rem - 2px)" : "2px",
823
+ width: "1.25rem",
824
+ height: "1.25rem",
825
+ borderRadius: "9999px",
826
+ backgroundColor: "#fff",
827
+ transition: "left 0.2s",
828
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)",
829
+ }}
830
+ />
831
+ </button>
832
+ );
833
+ }
834
+
835
+ // ScrollArea (simple wrapper)
836
+ export function ScrollArea({ children, className, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
837
+ return (
838
+ <div
839
+ style={{
840
+ overflow: "auto",
841
+ ...style,
842
+ }}
843
+ {...props}
844
+ >
845
+ {children}
846
+ </div>
847
+ );
848
+ }
849
+
850
+ // Alert
851
+ interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
852
+ variant?: "default" | "destructive";
853
+ }
854
+
855
+ export function Alert({ children, variant = "default", style, ...props }: AlertProps) {
856
+ const variantStyles: Record<string, React.CSSProperties> = {
857
+ default: {
858
+ backgroundColor: "#f3f4f6",
859
+ borderColor: "#e5e7eb",
860
+ color: "#1f2937",
861
+ },
862
+ destructive: {
863
+ backgroundColor: "#fef2f2",
864
+ borderColor: "#fecaca",
865
+ color: "#dc2626",
866
+ },
867
+ };
868
+
869
+ return (
870
+ <div
871
+ role="alert"
872
+ style={{
873
+ position: "relative",
874
+ width: "100%",
875
+ borderRadius: "0.375rem",
876
+ border: "1px solid",
877
+ padding: "1rem",
878
+ ...variantStyles[variant],
879
+ ...style,
880
+ }}
881
+ {...props}
882
+ >
883
+ {children}
884
+ </div>
885
+ );
886
+ }
887
+
888
+ export function AlertDescription({ children, style, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
889
+ return (
890
+ <p
891
+ style={{
892
+ fontSize: "0.875rem",
893
+ lineHeight: 1.5,
894
+ margin: 0,
895
+ ...style,
896
+ }}
897
+ {...props}
898
+ >
899
+ {children}
900
+ </p>
901
+ );
902
+ }
903
+
904
+ // CodeEditor (simple textarea-based editor)
905
+ interface CodeEditorProps {
906
+ value?: string;
907
+ onChange?: (value: string) => void;
908
+ height?: string;
909
+ style?: React.CSSProperties;
910
+ }
911
+
912
+ export function CodeEditor({ value, onChange, height = "300px", style }: CodeEditorProps) {
913
+ return (
914
+ <textarea
915
+ value={value}
916
+ onChange={(e) => onChange?.(e.target.value)}
917
+ style={{
918
+ width: "100%",
919
+ height,
920
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
921
+ fontSize: "0.875rem",
922
+ lineHeight: 1.5,
923
+ padding: "0.75rem",
924
+ backgroundColor: "#1f2937",
925
+ color: "#e5e7eb",
926
+ border: "1px solid #374151",
927
+ borderRadius: "0.375rem",
928
+ resize: "vertical",
929
+ boxSizing: "border-box",
930
+ ...style,
931
+ }}
932
+ spellCheck={false}
933
+ />
934
+ );
935
+ }
936
+
937
+ // Separator
938
+ export function Separator({ className, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {
939
+ return (
940
+ <div
941
+ style={{
942
+ height: "1px",
943
+ width: "100%",
944
+ backgroundColor: "#e5e7eb",
945
+ ...style,
946
+ }}
947
+ {...props}
948
+ />
949
+ );
950
+ }
951
+
952
+ // Export all as default components object
953
+ export const defaultComponents = {
954
+ Card,
955
+ CardContent,
956
+ CardHeader,
957
+ CardTitle,
958
+ CardDescription,
959
+ Button,
960
+ Badge,
961
+ Input,
962
+ Label,
963
+ Textarea,
964
+ Checkbox,
965
+ Select,
966
+ SelectTrigger,
967
+ SelectValue,
968
+ SelectContent,
969
+ SelectItem,
970
+ Skeleton,
971
+ Switch,
972
+ ScrollArea,
973
+ Alert,
974
+ AlertDescription,
975
+ CodeEditor,
976
+ Table,
977
+ TableHeader,
978
+ TableBody,
979
+ TableRow,
980
+ TableHead,
981
+ TableCell,
982
+ Dialog,
983
+ DialogContent,
984
+ DialogHeader,
985
+ DialogTitle,
986
+ DialogDescription,
987
+ DialogFooter,
988
+ AlertDialog,
989
+ AlertDialogContent,
990
+ AlertDialogHeader,
991
+ AlertDialogTitle,
992
+ AlertDialogDescription,
993
+ AlertDialogFooter,
994
+ AlertDialogCancel,
995
+ AlertDialogAction,
996
+ Separator,
997
+ Tabs,
998
+ TabsList,
999
+ TabsTrigger,
1000
+ TabsContent,
1001
+ };