veloria-ui 0.1.2

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 +206 -0
  2. package/LICENSE +21 -0
  3. package/README.md +253 -0
  4. package/dist/cli/index.js +511 -0
  5. package/dist/index.d.mts +1317 -0
  6. package/dist/index.d.ts +1317 -0
  7. package/dist/index.js +5373 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +5130 -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 +1197 -0
  14. package/dist/provider.js.map +1 -0
  15. package/dist/provider.mjs +1161 -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 +303 -0
  21. package/src/cli/registry.ts +139 -0
  22. package/src/components/advanced-forms/index.tsx +975 -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 +1158 -0
  27. package/src/components/feedback/index.tsx +1051 -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 +566 -0
  34. package/src/hooks/index.ts +602 -0
  35. package/src/hooks/use-toast.tsx +74 -0
  36. package/src/index.ts +396 -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,484 @@
1
+ import * as React from "react";
2
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
3
+ import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
4
+ import * as TabsPrimitive from "@radix-ui/react-tabs";
5
+ import { cva } from "class-variance-authority";
6
+ import { cn } from "../../utils/cn";
7
+
8
+ // ─── Navbar ────────────────────────────────────────────────────────────────
9
+
10
+ export interface NavbarProps extends React.HTMLAttributes<HTMLElement> {
11
+ sticky?: boolean;
12
+ bordered?: boolean;
13
+ blurred?: boolean;
14
+ }
15
+
16
+ const Navbar = React.forwardRef<HTMLElement, NavbarProps>(
17
+ ({ className, sticky, bordered, blurred, ...props }, ref) => (
18
+ <header
19
+ ref={ref}
20
+ className={cn(
21
+ "atlas-navbar z-40 w-full",
22
+ sticky && "sticky top-0",
23
+ bordered && "border-b border-border",
24
+ blurred && "backdrop-blur-md bg-background/80 supports-[backdrop-filter]:bg-background/60",
25
+ !blurred && "bg-background",
26
+ className
27
+ )}
28
+ {...props}
29
+ />
30
+ )
31
+ );
32
+ Navbar.displayName = "Navbar";
33
+
34
+ // ─── Sidebar ──────────────────────────────────────────────────────────────
35
+
36
+ export interface SidebarProps extends React.HTMLAttributes<HTMLElement> {
37
+ collapsible?: boolean;
38
+ collapsed?: boolean;
39
+ width?: string;
40
+ side?: "left" | "right";
41
+ }
42
+
43
+ const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(
44
+ ({ className, collapsed, width = "240px", side = "left", style, ...props }, ref) => (
45
+ <aside
46
+ ref={ref}
47
+ aria-label="Sidebar navigation"
48
+ className={cn(
49
+ "atlas-sidebar relative flex flex-col border-r border-border bg-background",
50
+ "transition-[width] duration-300 ease-in-out overflow-hidden shrink-0",
51
+ collapsed && "!w-0 border-transparent",
52
+ className
53
+ )}
54
+ style={{ width: collapsed ? 0 : width, ...style }}
55
+ {...props}
56
+ />
57
+ )
58
+ );
59
+ Sidebar.displayName = "Sidebar";
60
+
61
+ // ─── Menu ──────────────────────────────────────────────────────────────────
62
+
63
+ export interface MenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
64
+ icon?: React.ReactNode;
65
+ active?: boolean;
66
+ disabled?: boolean;
67
+ badge?: React.ReactNode;
68
+ as?: React.ElementType;
69
+ href?: string;
70
+ }
71
+
72
+ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
73
+ ({ className, icon, active, disabled, badge, children, as: Comp = "div", ...props }, ref) => (
74
+ <Comp
75
+ ref={ref}
76
+ role="menuitem"
77
+ aria-current={active ? "page" : undefined}
78
+ aria-disabled={disabled}
79
+ className={cn(
80
+ "atlas-menu-item flex items-center gap-3 px-3 py-2 rounded-md text-sm",
81
+ "transition-colors duration-150 cursor-pointer select-none",
82
+ active
83
+ ? "bg-accent text-accent-foreground font-medium"
84
+ : "text-foreground hover:bg-accent hover:text-accent-foreground",
85
+ disabled && "opacity-50 pointer-events-none",
86
+ className
87
+ )}
88
+ {...props}
89
+ >
90
+ {icon && <span className="shrink-0 [&>svg]:h-4 [&>svg]:w-4" aria-hidden="true">{icon}</span>}
91
+ <span className="flex-1 truncate">{children}</span>
92
+ {badge && <span className="shrink-0">{badge}</span>}
93
+ </Comp>
94
+ )
95
+ );
96
+ MenuItem.displayName = "MenuItem";
97
+
98
+ const Menu = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
99
+ ({ className, ...props }, ref) => (
100
+ <div
101
+ ref={ref}
102
+ role="menu"
103
+ className={cn("atlas-menu flex flex-col gap-0.5 p-1", className)}
104
+ {...props}
105
+ />
106
+ )
107
+ );
108
+ Menu.displayName = "Menu";
109
+
110
+ // ─── DropdownMenu ─────────────────────────────────────────────────────────
111
+
112
+ const DropdownMenu = DropdownMenuPrimitive.Root;
113
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
114
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group;
115
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
116
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
117
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
118
+
119
+ const DropdownMenuContent = React.forwardRef<
120
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
121
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
122
+ >(({ className, sideOffset = 4, ...props }, ref) => (
123
+ <DropdownMenuPrimitive.Portal>
124
+ <DropdownMenuPrimitive.Content
125
+ ref={ref}
126
+ sideOffset={sideOffset}
127
+ className={cn(
128
+ "atlas-dropdown-content z-50 min-w-[8rem] overflow-hidden rounded-md border border-border",
129
+ "bg-popover p-1 text-popover-foreground shadow-md",
130
+ "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out",
131
+ "data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
132
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
133
+ className
134
+ )}
135
+ {...props}
136
+ />
137
+ </DropdownMenuPrimitive.Portal>
138
+ ));
139
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
140
+
141
+ const DropdownMenuItem = React.forwardRef<
142
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
143
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
144
+ inset?: boolean;
145
+ destructive?: boolean;
146
+ }
147
+ >(({ className, inset, destructive, ...props }, ref) => (
148
+ <DropdownMenuPrimitive.Item
149
+ ref={ref}
150
+ className={cn(
151
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm",
152
+ "outline-none transition-colors gap-2",
153
+ "focus:bg-accent focus:text-accent-foreground",
154
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
155
+ inset && "pl-8",
156
+ destructive && "text-destructive focus:text-destructive",
157
+ className
158
+ )}
159
+ {...props}
160
+ />
161
+ ));
162
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
163
+
164
+ const DropdownMenuSeparator = React.forwardRef<
165
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
166
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
167
+ >(({ className, ...props }, ref) => (
168
+ <DropdownMenuPrimitive.Separator
169
+ ref={ref}
170
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
171
+ {...props}
172
+ />
173
+ ));
174
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
175
+
176
+ const DropdownMenuLabel = React.forwardRef<
177
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
178
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { inset?: boolean }
179
+ >(({ className, inset, ...props }, ref) => (
180
+ <DropdownMenuPrimitive.Label
181
+ ref={ref}
182
+ className={cn("px-2 py-1.5 text-xs font-semibold text-muted-foreground", inset && "pl-8", className)}
183
+ {...props}
184
+ />
185
+ ));
186
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
187
+
188
+ // ─── Breadcrumb ───────────────────────────────────────────────────────────
189
+
190
+ export interface BreadcrumbItem {
191
+ label: React.ReactNode;
192
+ href?: string;
193
+ current?: boolean;
194
+ }
195
+
196
+ export interface BreadcrumbProps extends React.HTMLAttributes<HTMLElement> {
197
+ items: BreadcrumbItem[];
198
+ separator?: React.ReactNode;
199
+ }
200
+
201
+ const Breadcrumb = React.forwardRef<HTMLElement, BreadcrumbProps>(
202
+ ({ className, items, separator, ...props }, ref) => (
203
+ <nav ref={ref} aria-label="Breadcrumb" className={cn("atlas-breadcrumb", className)} {...props}>
204
+ <ol className="flex flex-wrap items-center gap-1 text-sm text-muted-foreground">
205
+ {items.map((item, i) => (
206
+ <li key={i} className="flex items-center gap-1">
207
+ {i > 0 && (
208
+ <span aria-hidden="true" className="text-muted-foreground/50">
209
+ {separator ?? (
210
+ <svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
211
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
212
+ </svg>
213
+ )}
214
+ </span>
215
+ )}
216
+ {item.href && !item.current ? (
217
+ <a href={item.href} className="hover:text-foreground transition-colors">
218
+ {item.label}
219
+ </a>
220
+ ) : (
221
+ <span
222
+ className={cn(item.current && "text-foreground font-medium")}
223
+ aria-current={item.current ? "page" : undefined}
224
+ >
225
+ {item.label}
226
+ </span>
227
+ )}
228
+ </li>
229
+ ))}
230
+ </ol>
231
+ </nav>
232
+ )
233
+ );
234
+ Breadcrumb.displayName = "Breadcrumb";
235
+
236
+ // ─── Pagination ───────────────────────────────────────────────────────────
237
+
238
+ export interface PaginationProps extends React.HTMLAttributes<HTMLElement> {
239
+ total: number;
240
+ page: number;
241
+ pageSize?: number;
242
+ onPageChange?: (page: number) => void;
243
+ siblingCount?: number;
244
+ showEdges?: boolean;
245
+ }
246
+
247
+ const Pagination = React.forwardRef<HTMLElement, PaginationProps>(
248
+ ({ className, total, page, pageSize = 10, onPageChange, siblingCount = 1, showEdges = true, ...props }, ref) => {
249
+ const totalPages = Math.ceil(total / pageSize);
250
+
251
+ const getPageNumbers = () => {
252
+ const pages: (number | "...")[] = [];
253
+ const delta = siblingCount;
254
+
255
+ for (let i = 1; i <= totalPages; i++) {
256
+ if (
257
+ i === 1 ||
258
+ i === totalPages ||
259
+ (i >= page - delta && i <= page + delta)
260
+ ) {
261
+ pages.push(i);
262
+ } else if (pages[pages.length - 1] !== "...") {
263
+ pages.push("...");
264
+ }
265
+ }
266
+ return pages;
267
+ };
268
+
269
+ if (totalPages <= 1) return null;
270
+
271
+ return (
272
+ <nav ref={ref} aria-label="Pagination" className={cn("atlas-pagination flex items-center gap-1", className)} {...props}>
273
+ <button
274
+ onClick={() => onPageChange?.(page - 1)}
275
+ disabled={page <= 1}
276
+ className="h-8 w-8 flex items-center justify-center rounded-md border border-border text-sm hover:bg-accent disabled:opacity-50 disabled:pointer-events-none"
277
+ aria-label="Previous page"
278
+ >
279
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
280
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
281
+ </svg>
282
+ </button>
283
+ {getPageNumbers().map((p, i) =>
284
+ p === "..." ? (
285
+ <span key={`ellipsis-${i}`} className="px-1 text-sm text-muted-foreground">…</span>
286
+ ) : (
287
+ <button
288
+ key={p}
289
+ onClick={() => onPageChange?.(p as number)}
290
+ aria-current={p === page ? "page" : undefined}
291
+ className={cn(
292
+ "h-8 min-w-[2rem] px-2 flex items-center justify-center rounded-md text-sm font-medium transition-colors",
293
+ p === page
294
+ ? "bg-primary text-primary-foreground"
295
+ : "border border-border hover:bg-accent"
296
+ )}
297
+ >
298
+ {p}
299
+ </button>
300
+ )
301
+ )}
302
+ <button
303
+ onClick={() => onPageChange?.(page + 1)}
304
+ disabled={page >= totalPages}
305
+ className="h-8 w-8 flex items-center justify-center rounded-md border border-border text-sm hover:bg-accent disabled:opacity-50 disabled:pointer-events-none"
306
+ aria-label="Next page"
307
+ >
308
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
309
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7 7 7" />
310
+ </svg>
311
+ </button>
312
+ </nav>
313
+ );
314
+ }
315
+ );
316
+ Pagination.displayName = "Pagination";
317
+
318
+ // ─── Tabs ─────────────────────────────────────────────────────────────────
319
+
320
+ const Tabs = TabsPrimitive.Root;
321
+
322
+ const TabsList = React.forwardRef<
323
+ React.ElementRef<typeof TabsPrimitive.List>,
324
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> & {
325
+ variant?: "line" | "pills" | "enclosed";
326
+ }
327
+ >(({ className, variant = "line", ...props }, ref) => (
328
+ <TabsPrimitive.List
329
+ ref={ref}
330
+ className={cn(
331
+ "atlas-tabs-list inline-flex items-center",
332
+ variant === "line" && "border-b border-border gap-0 w-full",
333
+ variant === "pills" && "gap-1 bg-muted p-1 rounded-lg",
334
+ variant === "enclosed" && "border border-border rounded-t-lg gap-0",
335
+ className
336
+ )}
337
+ {...props}
338
+ />
339
+ ));
340
+ TabsList.displayName = TabsPrimitive.List.displayName;
341
+
342
+ const TabsTrigger = React.forwardRef<
343
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
344
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> & {
345
+ variant?: "line" | "pills" | "enclosed";
346
+ }
347
+ >(({ className, variant = "line", ...props }, ref) => (
348
+ <TabsPrimitive.Trigger
349
+ ref={ref}
350
+ className={cn(
351
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium",
352
+ "transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
353
+ "disabled:pointer-events-none disabled:opacity-50",
354
+ variant === "line" && [
355
+ "px-4 py-2.5 border-b-2 border-transparent -mb-px",
356
+ "text-muted-foreground hover:text-foreground",
357
+ "data-[state=active]:border-primary data-[state=active]:text-foreground",
358
+ ],
359
+ variant === "pills" && [
360
+ "px-3 py-1.5 rounded-md text-muted-foreground",
361
+ "data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
362
+ ],
363
+ variant === "enclosed" && [
364
+ "px-4 py-2 border-r last:border-r-0 border-border",
365
+ "text-muted-foreground hover:text-foreground bg-muted",
366
+ "data-[state=active]:bg-background data-[state=active]:text-foreground",
367
+ ],
368
+ className
369
+ )}
370
+ {...props}
371
+ />
372
+ ));
373
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
374
+
375
+ const TabsContent = React.forwardRef<
376
+ React.ElementRef<typeof TabsPrimitive.Content>,
377
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
378
+ >(({ className, ...props }, ref) => (
379
+ <TabsPrimitive.Content
380
+ ref={ref}
381
+ className={cn(
382
+ "atlas-tabs-content mt-4",
383
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
384
+ className
385
+ )}
386
+ {...props}
387
+ />
388
+ ));
389
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
390
+
391
+ // ─── Stepper ──────────────────────────────────────────────────────────────
392
+
393
+ export interface StepperStep {
394
+ title: string;
395
+ description?: string;
396
+ icon?: React.ReactNode;
397
+ }
398
+
399
+ export interface StepperProps extends React.HTMLAttributes<HTMLDivElement> {
400
+ steps: StepperStep[];
401
+ current: number;
402
+ orientation?: "horizontal" | "vertical";
403
+ }
404
+
405
+ const Stepper = React.forwardRef<HTMLDivElement, StepperProps>(
406
+ ({ className, steps, current, orientation = "horizontal", ...props }, ref) => (
407
+ <div
408
+ ref={ref}
409
+ className={cn(
410
+ "atlas-stepper flex",
411
+ orientation === "horizontal" ? "flex-row items-center" : "flex-col",
412
+ className
413
+ )}
414
+ role="list"
415
+ aria-label="Progress steps"
416
+ {...props}
417
+ >
418
+ {steps.map((step, i) => {
419
+ const status = i < current ? "complete" : i === current ? "current" : "upcoming";
420
+
421
+ return (
422
+ <React.Fragment key={i}>
423
+ <div
424
+ role="listitem"
425
+ aria-current={status === "current" ? "step" : undefined}
426
+ className={cn(
427
+ "flex items-center gap-3",
428
+ orientation === "vertical" && "flex-col items-start",
429
+ )}
430
+ >
431
+ <div className={cn(
432
+ "flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-sm font-semibold",
433
+ "transition-colors border-2",
434
+ status === "complete" && "bg-primary border-primary text-primary-foreground",
435
+ status === "current" && "border-primary text-primary bg-primary/10",
436
+ status === "upcoming" && "border-border text-muted-foreground",
437
+ )}>
438
+ {status === "complete" ? (
439
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
440
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
441
+ </svg>
442
+ ) : (step.icon ?? <span>{i + 1}</span>)}
443
+ </div>
444
+ <div className={orientation === "horizontal" ? "hidden sm:block" : ""}>
445
+ <p className={cn("text-sm font-medium", status === "upcoming" && "text-muted-foreground")}>
446
+ {step.title}
447
+ </p>
448
+ {step.description && (
449
+ <p className="text-xs text-muted-foreground">{step.description}</p>
450
+ )}
451
+ </div>
452
+ </div>
453
+ {i < steps.length - 1 && (
454
+ <div className={cn(
455
+ "transition-colors",
456
+ orientation === "horizontal"
457
+ ? "flex-1 h-px mx-3"
458
+ : "ml-4 w-px h-8",
459
+ i < current ? "bg-primary" : "bg-border"
460
+ )} aria-hidden="true" />
461
+ )}
462
+ </React.Fragment>
463
+ );
464
+ })}
465
+ </div>
466
+ )
467
+ );
468
+ Stepper.displayName = "Stepper";
469
+
470
+ // ─── CommandPalette ─────────────────────────────────────────────────────
471
+ // Thin wrapper - full implementation in overlay/CommandDialog
472
+ export { Stepper as CommandPalette } from "./index"; // Placeholder, see CommandDialog
473
+
474
+ export {
475
+ Navbar, Sidebar,
476
+ Menu, MenuItem,
477
+ DropdownMenu, DropdownMenuTrigger, DropdownMenuContent,
478
+ DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel,
479
+ DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuRadioGroup,
480
+ Breadcrumb,
481
+ Pagination,
482
+ Tabs, TabsList, TabsTrigger, TabsContent,
483
+ Stepper,
484
+ };