atlasui-lib 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.
Files changed (41) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/LICENSE +21 -0
  3. package/README.md +253 -0
  4. package/dist/cli/index.js +364 -0
  5. package/dist/index.d.mts +1027 -0
  6. package/dist/index.d.ts +1027 -0
  7. package/dist/index.js +3954 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +3733 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/dist/provider.d.mts +15 -0
  12. package/dist/provider.d.ts +15 -0
  13. package/dist/provider.js +816 -0
  14. package/dist/provider.js.map +1 -0
  15. package/dist/provider.mjs +780 -0
  16. package/dist/provider.mjs.map +1 -0
  17. package/dist/tailwind.d.ts +25 -0
  18. package/dist/tailwind.js +129 -0
  19. package/package.json +138 -0
  20. package/src/cli/index.ts +301 -0
  21. package/src/cli/registry.ts +139 -0
  22. package/src/components/advanced-forms/index.tsx +567 -0
  23. package/src/components/basic/Button.tsx +135 -0
  24. package/src/components/basic/IconButton.tsx +69 -0
  25. package/src/components/basic/index.tsx +446 -0
  26. package/src/components/data-display/index.tsx +608 -0
  27. package/src/components/feedback/index.tsx +554 -0
  28. package/src/components/forms/index.tsx +476 -0
  29. package/src/components/layout/index.tsx +296 -0
  30. package/src/components/media/index.tsx +437 -0
  31. package/src/components/navigation/index.tsx +484 -0
  32. package/src/components/overlay/index.tsx +473 -0
  33. package/src/components/utility/index.tsx +411 -0
  34. package/src/hooks/index.ts +271 -0
  35. package/src/hooks/use-toast.tsx +74 -0
  36. package/src/index.ts +353 -0
  37. package/src/provider.tsx +54 -0
  38. package/src/styles/atlas.css +252 -0
  39. package/src/tailwind.ts +124 -0
  40. package/src/types/index.ts +95 -0
  41. package/src/utils/cn.ts +66 -0
@@ -0,0 +1,608 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "../../utils/cn";
4
+
5
+ // ─── Card ──────────────────────────────────────────────────────────────────
6
+
7
+ const cardVariants = cva(
8
+ "atlas-card rounded-xl border bg-card text-card-foreground",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "border-border shadow-sm",
13
+ outline: "border-border shadow-none",
14
+ elevated: "border-transparent shadow-lg",
15
+ ghost: "border-transparent shadow-none bg-transparent",
16
+ filled: "border-transparent bg-muted",
17
+ },
18
+ interactive: {
19
+ true: "cursor-pointer transition-shadow hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 active:shadow-sm",
20
+ },
21
+ },
22
+ defaultVariants: { variant: "default" },
23
+ }
24
+ );
25
+
26
+ export interface CardProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof cardVariants> {}
27
+
28
+ const Card = React.forwardRef<HTMLDivElement, CardProps>(
29
+ ({ className, variant, interactive, ...props }, ref) => (
30
+ <div ref={ref} className={cn(cardVariants({ variant, interactive, className }))} {...props} />
31
+ )
32
+ );
33
+ Card.displayName = "Card";
34
+
35
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
36
+ ({ className, ...props }, ref) => (
37
+ <div ref={ref} className={cn("flex flex-col gap-1.5 p-6", className)} {...props} />
38
+ )
39
+ );
40
+ CardHeader.displayName = "CardHeader";
41
+
42
+ const CardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
43
+ ({ className, ...props }, ref) => (
44
+ <h3 ref={ref} className={cn("text-lg font-semibold leading-tight tracking-tight", className)} {...props} />
45
+ )
46
+ );
47
+ CardTitle.displayName = "CardTitle";
48
+
49
+ const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
50
+ ({ className, ...props }, ref) => (
51
+ <p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
52
+ )
53
+ );
54
+ CardDescription.displayName = "CardDescription";
55
+
56
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
57
+ ({ className, ...props }, ref) => (
58
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
59
+ )
60
+ );
61
+ CardContent.displayName = "CardContent";
62
+
63
+ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
64
+ ({ className, ...props }, ref) => (
65
+ <div ref={ref} className={cn("flex items-center p-6 pt-0 gap-2", className)} {...props} />
66
+ )
67
+ );
68
+ CardFooter.displayName = "CardFooter";
69
+
70
+ // ─── Table ─────────────────────────────────────────────────────────────────
71
+
72
+ const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
73
+ ({ className, ...props }, ref) => (
74
+ <div className="atlas-table relative w-full overflow-auto">
75
+ <table ref={ref} className={cn("w-full caption-bottom text-sm", className)} {...props} />
76
+ </div>
77
+ )
78
+ );
79
+ Table.displayName = "Table";
80
+
81
+ const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
82
+ ({ className, ...props }, ref) => (
83
+ <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
84
+ )
85
+ );
86
+ TableHeader.displayName = "TableHeader";
87
+
88
+ const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
89
+ ({ className, ...props }, ref) => (
90
+ <tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
91
+ )
92
+ );
93
+ TableBody.displayName = "TableBody";
94
+
95
+ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
96
+ ({ className, ...props }, ref) => (
97
+ <tr ref={ref} className={cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className)} {...props} />
98
+ )
99
+ );
100
+ TableRow.displayName = "TableRow";
101
+
102
+ const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(
103
+ ({ className, ...props }, ref) => (
104
+ <th ref={ref} className={cn("h-10 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0", className)} {...props} />
105
+ )
106
+ );
107
+ TableHead.displayName = "TableHead";
108
+
109
+ const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(
110
+ ({ className, ...props }, ref) => (
111
+ <td ref={ref} className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)} {...props} />
112
+ )
113
+ );
114
+ TableCell.displayName = "TableCell";
115
+
116
+ const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
117
+ ({ className, ...props }, ref) => (
118
+ <caption ref={ref} className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />
119
+ )
120
+ );
121
+ TableCaption.displayName = "TableCaption";
122
+
123
+ // ─── DataTable ────────────────────────────────────────────────────────────
124
+
125
+ export interface DataTableColumn<T> {
126
+ key: keyof T | string;
127
+ header: React.ReactNode;
128
+ cell?: (row: T, index: number) => React.ReactNode;
129
+ sortable?: boolean;
130
+ width?: string | number;
131
+ align?: "left" | "center" | "right";
132
+ }
133
+
134
+ export interface DataTableProps<T extends Record<string, unknown>> {
135
+ data: T[];
136
+ columns: DataTableColumn<T>[];
137
+ loading?: boolean;
138
+ emptyText?: string;
139
+ onSort?: (key: string, direction: "asc" | "desc") => void;
140
+ striped?: boolean;
141
+ bordered?: boolean;
142
+ className?: string;
143
+ caption?: string;
144
+ }
145
+
146
+ function DataTable<T extends Record<string, unknown>>({
147
+ data,
148
+ columns,
149
+ loading,
150
+ emptyText = "No data available",
151
+ onSort,
152
+ striped,
153
+ bordered,
154
+ className,
155
+ caption,
156
+ }: DataTableProps<T>) {
157
+ const [sortKey, setSortKey] = React.useState<string | null>(null);
158
+ const [sortDir, setSortDir] = React.useState<"asc" | "desc">("asc");
159
+
160
+ const handleSort = (key: string) => {
161
+ const newDir = sortKey === key && sortDir === "asc" ? "desc" : "asc";
162
+ setSortKey(key);
163
+ setSortDir(newDir);
164
+ onSort?.(key, newDir);
165
+ };
166
+
167
+ return (
168
+ <div className={cn("atlas-data-table relative w-full overflow-auto rounded-md", bordered && "border border-border", className)}>
169
+ <table className="w-full caption-bottom text-sm">
170
+ {caption && <TableCaption>{caption}</TableCaption>}
171
+ <thead className="border-b bg-muted/50">
172
+ <tr>
173
+ {columns.map((col, i) => (
174
+ <th
175
+ key={i}
176
+ style={{ width: col.width }}
177
+ className={cn(
178
+ "h-10 px-4 font-medium text-muted-foreground",
179
+ col.align === "center" && "text-center",
180
+ col.align === "right" && "text-right",
181
+ col.sortable && "cursor-pointer select-none hover:text-foreground",
182
+ )}
183
+ onClick={() => col.sortable && handleSort(String(col.key))}
184
+ aria-sort={
185
+ sortKey === col.key
186
+ ? sortDir === "asc" ? "ascending" : "descending"
187
+ : col.sortable ? "none" : undefined
188
+ }
189
+ >
190
+ <span className="flex items-center gap-1">
191
+ {col.header}
192
+ {col.sortable && sortKey === String(col.key) && (
193
+ <svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
194
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
195
+ d={sortDir === "asc" ? "M5 15l7-7 7 7" : "M19 9l-7 7-7-7"}
196
+ />
197
+ </svg>
198
+ )}
199
+ </span>
200
+ </th>
201
+ ))}
202
+ </tr>
203
+ </thead>
204
+ <tbody>
205
+ {loading ? (
206
+ <tr>
207
+ <td colSpan={columns.length} className="h-24 text-center">
208
+ <svg className="mx-auto h-5 w-5 animate-spin text-muted-foreground" fill="none" viewBox="0 0 24 24">
209
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
210
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
211
+ </svg>
212
+ </td>
213
+ </tr>
214
+ ) : data.length === 0 ? (
215
+ <tr>
216
+ <td colSpan={columns.length} className="h-24 text-center text-muted-foreground">{emptyText}</td>
217
+ </tr>
218
+ ) : (
219
+ data.map((row, i) => (
220
+ <tr
221
+ key={i}
222
+ className={cn(
223
+ "border-b transition-colors hover:bg-muted/50",
224
+ striped && i % 2 !== 0 && "bg-muted/20"
225
+ )}
226
+ >
227
+ {columns.map((col, j) => (
228
+ <td
229
+ key={j}
230
+ className={cn(
231
+ "p-4 align-middle",
232
+ col.align === "center" && "text-center",
233
+ col.align === "right" && "text-right",
234
+ )}
235
+ >
236
+ {col.cell
237
+ ? col.cell(row, i)
238
+ : String(row[col.key as keyof T] ?? "")}
239
+ </td>
240
+ ))}
241
+ </tr>
242
+ ))
243
+ )}
244
+ </tbody>
245
+ </table>
246
+ </div>
247
+ );
248
+ }
249
+ DataTable.displayName = "DataTable";
250
+
251
+ // ─── List & ListItem ─────────────────────────────────────────────────────
252
+
253
+ export interface ListProps extends Omit<React.HTMLAttributes<HTMLUListElement>, "color"> {
254
+ variant?: "simple" | "bordered" | "divided";
255
+ spacing?: "none" | "sm" | "md" | "lg";
256
+ }
257
+
258
+ const List = React.forwardRef<HTMLUListElement, ListProps>(
259
+ ({ className, variant = "simple", spacing = "none", ...props }, ref) => (
260
+ <ul
261
+ ref={ref}
262
+ className={cn(
263
+ "atlas-list w-full",
264
+ variant === "bordered" && "rounded-md border border-border divide-y divide-border",
265
+ variant === "divided" && "divide-y divide-border",
266
+ spacing === "sm" && "space-y-1",
267
+ spacing === "md" && "space-y-2",
268
+ spacing === "lg" && "space-y-3",
269
+ className
270
+ )}
271
+ {...props}
272
+ />
273
+ )
274
+ );
275
+ List.displayName = "List";
276
+
277
+ export interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
278
+ icon?: React.ReactNode;
279
+ extra?: React.ReactNode;
280
+ active?: boolean;
281
+ }
282
+
283
+ const ListItem = React.forwardRef<HTMLLIElement, ListItemProps>(
284
+ ({ className, icon, extra, active, children, ...props }, ref) => (
285
+ <li
286
+ ref={ref}
287
+ className={cn(
288
+ "atlas-list-item flex items-center gap-3 px-4 py-3",
289
+ active && "bg-accent text-accent-foreground",
290
+ className
291
+ )}
292
+ {...props}
293
+ >
294
+ {icon && <span className="shrink-0 text-muted-foreground [&>svg]:h-4 [&>svg]:w-4" aria-hidden="true">{icon}</span>}
295
+ <span className="flex-1 min-w-0">{children}</span>
296
+ {extra && <span className="shrink-0">{extra}</span>}
297
+ </li>
298
+ )
299
+ );
300
+ ListItem.displayName = "ListItem";
301
+
302
+ // ─── Statistic ────────────────────────────────────────────────────────────
303
+
304
+ export interface StatisticProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "prefix"> {
305
+ label: React.ReactNode;
306
+ value: React.ReactNode;
307
+ prefix?: React.ReactNode;
308
+ suffix?: React.ReactNode;
309
+ trend?: { value: number; label?: string };
310
+ loading?: boolean;
311
+ }
312
+
313
+ const Statistic = React.forwardRef<HTMLDivElement, StatisticProps>(
314
+ ({ className, label, value, prefix, suffix, trend, loading, ...props }, ref) => (
315
+ <div ref={ref} className={cn("atlas-statistic", className)} {...props}>
316
+ <p className="text-sm font-medium text-muted-foreground">{label}</p>
317
+ <div className="mt-1 flex items-end gap-2">
318
+ <span className="text-3xl font-bold tracking-tight">
319
+ {loading ? (
320
+ <span className="inline-block h-8 w-24 animate-pulse rounded bg-muted" />
321
+ ) : (
322
+ <>{prefix}{value}{suffix}</>
323
+ )}
324
+ </span>
325
+ {trend && !loading && (
326
+ <span className={cn(
327
+ "mb-1 flex items-center gap-0.5 text-sm font-medium",
328
+ trend.value > 0 ? "text-success" : trend.value < 0 ? "text-destructive" : "text-muted-foreground"
329
+ )}>
330
+ {trend.value !== 0 && (
331
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
332
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
333
+ d={trend.value > 0 ? "M7 17l9-9M7 7h10v10" : "M7 7l9 9M17 7H7v10"}
334
+ />
335
+ </svg>
336
+ )}
337
+ {Math.abs(trend.value)}%
338
+ {trend.label && <span className="font-normal text-muted-foreground ml-1">{trend.label}</span>}
339
+ </span>
340
+ )}
341
+ </div>
342
+ </div>
343
+ )
344
+ );
345
+ Statistic.displayName = "Statistic";
346
+
347
+ // ─── Timeline ─────────────────────────────────────────────────────────────
348
+
349
+ export interface TimelineEvent {
350
+ title: React.ReactNode;
351
+ description?: React.ReactNode;
352
+ time?: React.ReactNode;
353
+ icon?: React.ReactNode;
354
+ color?: "default" | "primary" | "success" | "warning" | "danger";
355
+ }
356
+
357
+ export interface TimelineProps extends React.HTMLAttributes<HTMLOListElement> {
358
+ events: TimelineEvent[];
359
+ }
360
+
361
+ const Timeline = React.forwardRef<HTMLOListElement, TimelineProps>(
362
+ ({ className, events, ...props }, ref) => (
363
+ <ol ref={ref} className={cn("atlas-timeline relative flex flex-col", className)} {...props}>
364
+ {events.map((event, i) => (
365
+ <li key={i} className="relative flex gap-4 pb-8 last:pb-0">
366
+ <div className="relative flex flex-col items-center">
367
+ <div className={cn(
368
+ "z-10 flex h-8 w-8 items-center justify-center rounded-full border-2 shrink-0",
369
+ "border-background shadow-sm [&>svg]:h-3.5 [&>svg]:w-3.5",
370
+ event.color === "primary" ? "bg-primary text-primary-foreground" :
371
+ event.color === "success" ? "bg-success text-success-foreground" :
372
+ event.color === "warning" ? "bg-warning text-warning-foreground" :
373
+ event.color === "danger" ? "bg-destructive text-destructive-foreground" :
374
+ "bg-muted text-muted-foreground"
375
+ )}>
376
+ {event.icon ?? <span className="h-2 w-2 rounded-full bg-current" />}
377
+ </div>
378
+ {i < events.length - 1 && (
379
+ <div className="mt-1 w-px flex-1 bg-border" aria-hidden="true" />
380
+ )}
381
+ </div>
382
+ <div className="flex-1 pt-0.5 pb-4 last:pb-0">
383
+ <div className="flex items-start justify-between gap-2">
384
+ <p className="font-medium text-sm">{event.title}</p>
385
+ {event.time && <span className="text-xs text-muted-foreground whitespace-nowrap shrink-0">{event.time}</span>}
386
+ </div>
387
+ {event.description && <p className="mt-1 text-sm text-muted-foreground">{event.description}</p>}
388
+ </div>
389
+ </li>
390
+ ))}
391
+ </ol>
392
+ )
393
+ );
394
+ Timeline.displayName = "Timeline";
395
+
396
+ // ─── Calendar ─────────────────────────────────────────────────────────────
397
+
398
+ export interface CalendarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
399
+ value?: Date;
400
+ onChange?: (date: Date) => void;
401
+ minDate?: Date;
402
+ maxDate?: Date;
403
+ highlightedDates?: Date[];
404
+ }
405
+
406
+ const DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
407
+ const MONTHS = ["January","February","March","April","May","June","July","August","September","October","November","December"];
408
+
409
+ const Calendar = React.forwardRef<HTMLDivElement, CalendarProps>(
410
+ ({ className, value, onChange, minDate, maxDate, highlightedDates = [], ...props }, ref) => {
411
+ const today = new Date();
412
+ const [viewDate, setViewDate] = React.useState(value ?? today);
413
+ const year = viewDate.getFullYear();
414
+ const month = viewDate.getMonth();
415
+
416
+ const firstDay = new Date(year, month, 1).getDay();
417
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
418
+
419
+ const cells: (Date | null)[] = [
420
+ ...Array.from({ length: firstDay }, (): null => null),
421
+ ...Array.from({ length: daysInMonth }, (_, i) => new Date(year, month, i + 1)),
422
+ ];
423
+
424
+ const isSameDay = (a: Date, b: Date) =>
425
+ a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
426
+
427
+ return (
428
+ <div ref={ref} className={cn("atlas-calendar w-fit rounded-lg border border-border bg-background p-3 shadow-sm", className)} {...props}>
429
+ <div className="flex items-center justify-between mb-3">
430
+ <button
431
+ type="button"
432
+ onClick={() => setViewDate(new Date(year, month - 1))}
433
+ className="h-7 w-7 flex items-center justify-center rounded-md hover:bg-accent transition-colors"
434
+ aria-label="Previous month"
435
+ >
436
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
437
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
438
+ </svg>
439
+ </button>
440
+ <span className="text-sm font-semibold">{MONTHS[month]} {year}</span>
441
+ <button
442
+ type="button"
443
+ onClick={() => setViewDate(new Date(year, month + 1))}
444
+ className="h-7 w-7 flex items-center justify-center rounded-md hover:bg-accent transition-colors"
445
+ aria-label="Next month"
446
+ >
447
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
448
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
449
+ </svg>
450
+ </button>
451
+ </div>
452
+ <div className="grid grid-cols-7 gap-px">
453
+ {DAYS.map((d) => (
454
+ <div key={d} className="h-8 flex items-center justify-center text-xs font-medium text-muted-foreground">{d}</div>
455
+ ))}
456
+ {cells.map((date, i) => {
457
+ if (!date) return <div key={`empty-${i}`} />;
458
+ const isSelected = value ? isSameDay(date, value) : false;
459
+ const isToday = isSameDay(date, today);
460
+ const isHighlighted = highlightedDates.some((d) => isSameDay(d, date));
461
+ const isDisabled =
462
+ (minDate && date < minDate) || (maxDate && date > maxDate);
463
+
464
+ return (
465
+ <button
466
+ key={date.toISOString()}
467
+ type="button"
468
+ disabled={isDisabled}
469
+ onClick={() => onChange?.(date)}
470
+ aria-label={date.toLocaleDateString()}
471
+ aria-pressed={isSelected}
472
+ className={cn(
473
+ "h-8 w-8 text-xs rounded-md flex items-center justify-center transition-colors font-medium relative",
474
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
475
+ "disabled:pointer-events-none disabled:opacity-30",
476
+ isSelected && "bg-primary text-primary-foreground hover:bg-primary/90",
477
+ !isSelected && isToday && "border border-primary text-primary",
478
+ !isSelected && !isToday && "hover:bg-accent",
479
+ )}
480
+ >
481
+ {date.getDate()}
482
+ {isHighlighted && !isSelected && (
483
+ <span className="absolute bottom-1 left-1/2 -translate-x-1/2 h-1 w-1 rounded-full bg-primary" />
484
+ )}
485
+ </button>
486
+ );
487
+ })}
488
+ </div>
489
+ </div>
490
+ );
491
+ }
492
+ );
493
+ Calendar.displayName = "Calendar";
494
+
495
+ // ─── CodeBlock ────────────────────────────────────────────────────────────
496
+
497
+ export interface CodeBlockProps extends React.HTMLAttributes<HTMLDivElement> {
498
+ code: string;
499
+ language?: string;
500
+ showLineNumbers?: boolean;
501
+ caption?: string;
502
+ onCopy?: () => void;
503
+ }
504
+
505
+ const CodeBlock = React.forwardRef<HTMLDivElement, CodeBlockProps>(
506
+ ({ className, code, language, showLineNumbers, caption, onCopy, ...props }, ref) => {
507
+ const [copied, setCopied] = React.useState(false);
508
+
509
+ const handleCopy = async () => {
510
+ await navigator.clipboard.writeText(code);
511
+ setCopied(true);
512
+ onCopy?.();
513
+ setTimeout(() => setCopied(false), 2000);
514
+ };
515
+
516
+ const lines = code.split("\n");
517
+
518
+ return (
519
+ <div ref={ref} className={cn("atlas-code-block relative rounded-lg border border-border bg-muted/50 overflow-hidden", className)} {...props}>
520
+ <div className="flex items-center justify-between px-4 py-2 border-b border-border bg-muted/80">
521
+ {language && <span className="text-xs font-medium text-muted-foreground">{language}</span>}
522
+ {caption && <span className="text-xs text-muted-foreground">{caption}</span>}
523
+ <button
524
+ type="button"
525
+ onClick={handleCopy}
526
+ aria-label={copied ? "Copied!" : "Copy code"}
527
+ className="ml-auto flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-muted-foreground hover:text-foreground hover:bg-background transition-colors"
528
+ >
529
+ {copied ? (
530
+ <>
531
+ <svg className="h-3.5 w-3.5 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24">
532
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
533
+ </svg>
534
+ Copied
535
+ </>
536
+ ) : (
537
+ <>
538
+ <svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
539
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
540
+ d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
541
+ />
542
+ </svg>
543
+ Copy
544
+ </>
545
+ )}
546
+ </button>
547
+ </div>
548
+ <pre className="overflow-x-auto p-4 text-sm">
549
+ <code className={`language-${language}`}>
550
+ {showLineNumbers
551
+ ? lines.map((line, i) => (
552
+ <span key={i} className="block">
553
+ <span className="mr-4 select-none text-muted-foreground/50 text-xs w-5 inline-block text-right">{i + 1}</span>
554
+ {line}
555
+ </span>
556
+ ))
557
+ : code}
558
+ </code>
559
+ </pre>
560
+ </div>
561
+ );
562
+ }
563
+ );
564
+ CodeBlock.displayName = "CodeBlock";
565
+
566
+ // ─── Chart (placeholder - integrates with recharts/chartjs) ───────────────
567
+
568
+ export interface ChartProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
569
+ title?: string;
570
+ description?: string;
571
+ loading?: boolean;
572
+ empty?: boolean;
573
+ }
574
+
575
+ const Chart = React.forwardRef<HTMLDivElement, ChartProps>(
576
+ ({ className, title, description, loading, empty, children, ...props }, ref) => (
577
+ <div ref={ref} className={cn("atlas-chart", className)} {...props}>
578
+ {(title || description) && (
579
+ <div className="mb-4">
580
+ {title && <h3 className="text-base font-semibold">{title}</h3>}
581
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
582
+ </div>
583
+ )}
584
+ {loading ? (
585
+ <div className="h-64 w-full animate-pulse rounded-lg bg-muted" />
586
+ ) : empty ? (
587
+ <div className="h-64 w-full flex items-center justify-center text-muted-foreground text-sm border border-dashed border-border rounded-lg">
588
+ No chart data available
589
+ </div>
590
+ ) : (
591
+ children
592
+ )}
593
+ </div>
594
+ )
595
+ );
596
+ Chart.displayName = "Chart";
597
+
598
+ export {
599
+ Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter,
600
+ Table, TableHeader, TableBody, TableRow, TableHead, TableCell, TableCaption,
601
+ DataTable,
602
+ List, ListItem,
603
+ Statistic,
604
+ Timeline,
605
+ Calendar,
606
+ CodeBlock,
607
+ Chart,
608
+ };