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,135 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import { cn } from "../../utils/cn";
5
+
6
+ const buttonVariants = cva(
7
+ [
8
+ "atlas-button",
9
+ "inline-flex items-center justify-center gap-2",
10
+ "whitespace-nowrap rounded-md font-medium",
11
+ "ring-offset-background transition-all duration-150",
12
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
13
+ "disabled:pointer-events-none disabled:opacity-50",
14
+ "select-none",
15
+ ],
16
+ {
17
+ variants: {
18
+ variant: {
19
+ solid:
20
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90 active:scale-[0.98]",
21
+ outline:
22
+ "border border-input bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground",
23
+ ghost:
24
+ "bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground",
25
+ soft: "bg-primary/10 text-primary hover:bg-primary/20",
26
+ link: "text-primary underline-offset-4 hover:underline p-0 h-auto",
27
+ danger:
28
+ "bg-destructive text-destructive-foreground shadow hover:bg-destructive/90",
29
+ success:
30
+ "bg-success text-success-foreground shadow hover:bg-success/90",
31
+ },
32
+ size: {
33
+ xs: "h-6 px-2 text-xs rounded",
34
+ sm: "h-8 px-3 text-sm",
35
+ md: "h-9 px-4 text-sm",
36
+ lg: "h-10 px-6 text-base",
37
+ xl: "h-12 px-8 text-base",
38
+ icon: "h-9 w-9",
39
+ },
40
+ fullWidth: {
41
+ true: "w-full",
42
+ },
43
+ loading: {
44
+ true: "cursor-wait",
45
+ },
46
+ },
47
+ defaultVariants: {
48
+ variant: "solid",
49
+ size: "md",
50
+ },
51
+ }
52
+ );
53
+
54
+ export interface ButtonProps
55
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
56
+ VariantProps<typeof buttonVariants> {
57
+ /** Render as child element (Radix Slot pattern) */
58
+ asChild?: boolean;
59
+ /** Show loading spinner */
60
+ loading?: boolean;
61
+ /** Left icon slot */
62
+ leftIcon?: React.ReactNode;
63
+ /** Right icon slot */
64
+ rightIcon?: React.ReactNode;
65
+ }
66
+
67
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
68
+ (
69
+ {
70
+ className,
71
+ variant,
72
+ size,
73
+ fullWidth,
74
+ loading,
75
+ asChild = false,
76
+ leftIcon,
77
+ rightIcon,
78
+ children,
79
+ disabled,
80
+ ...props
81
+ },
82
+ ref
83
+ ) => {
84
+ const Comp = asChild ? Slot : "button";
85
+
86
+ return (
87
+ <Comp
88
+ ref={ref}
89
+ className={cn(buttonVariants({ variant, size, fullWidth, loading, className }))}
90
+ disabled={disabled || loading === true}
91
+ aria-busy={loading}
92
+ {...props}
93
+ >
94
+ {loading && (
95
+ <svg
96
+ className="animate-spin -ml-0.5 h-4 w-4"
97
+ xmlns="http://www.w3.org/2000/svg"
98
+ fill="none"
99
+ viewBox="0 0 24 24"
100
+ aria-hidden="true"
101
+ >
102
+ <circle
103
+ className="opacity-25"
104
+ cx="12"
105
+ cy="12"
106
+ r="10"
107
+ stroke="currentColor"
108
+ strokeWidth="4"
109
+ />
110
+ <path
111
+ className="opacity-75"
112
+ fill="currentColor"
113
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
114
+ />
115
+ </svg>
116
+ )}
117
+ {!loading && leftIcon && (
118
+ <span className="shrink-0" aria-hidden="true">
119
+ {leftIcon}
120
+ </span>
121
+ )}
122
+ {children}
123
+ {rightIcon && !loading && (
124
+ <span className="shrink-0" aria-hidden="true">
125
+ {rightIcon}
126
+ </span>
127
+ )}
128
+ </Comp>
129
+ );
130
+ }
131
+ );
132
+
133
+ Button.displayName = "Button";
134
+
135
+ export { Button, buttonVariants };
@@ -0,0 +1,69 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import { cn } from "../../utils/cn";
5
+
6
+ const iconButtonVariants = cva(
7
+ [
8
+ "atlas-icon-button",
9
+ "inline-flex items-center justify-center rounded-md",
10
+ "transition-all duration-150 shrink-0",
11
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
12
+ "disabled:pointer-events-none disabled:opacity-50",
13
+ ],
14
+ {
15
+ variants: {
16
+ variant: {
17
+ solid: "bg-primary text-primary-foreground hover:bg-primary/90 active:scale-[0.96]",
18
+ outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
19
+ ghost: "bg-transparent hover:bg-accent hover:text-accent-foreground",
20
+ soft: "bg-primary/10 text-primary hover:bg-primary/20",
21
+ danger: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
22
+ },
23
+ size: {
24
+ xs: "h-6 w-6 [&>svg]:h-3 [&>svg]:w-3",
25
+ sm: "h-7 w-7 [&>svg]:h-3.5 [&>svg]:w-3.5",
26
+ md: "h-9 w-9 [&>svg]:h-4 [&>svg]:w-4",
27
+ lg: "h-10 w-10 [&>svg]:h-5 [&>svg]:w-5",
28
+ xl: "h-12 w-12 [&>svg]:h-6 [&>svg]:w-6",
29
+ },
30
+ shape: {
31
+ square: "rounded-md",
32
+ circle: "rounded-full",
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ variant: "ghost",
37
+ size: "md",
38
+ shape: "square",
39
+ },
40
+ }
41
+ );
42
+
43
+ export interface IconButtonProps
44
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
45
+ VariantProps<typeof iconButtonVariants> {
46
+ /** Accessible label — required for screen readers */
47
+ "aria-label": string;
48
+ asChild?: boolean;
49
+ icon?: React.ReactNode;
50
+ }
51
+
52
+ const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(
53
+ ({ className, variant, size, shape, asChild = false, icon, children, ...props }, ref) => {
54
+ const Comp = asChild ? Slot : "button";
55
+ return (
56
+ <Comp
57
+ ref={ref}
58
+ className={cn(iconButtonVariants({ variant, size, shape, className }))}
59
+ {...props}
60
+ >
61
+ {icon ?? children}
62
+ </Comp>
63
+ );
64
+ }
65
+ );
66
+
67
+ IconButton.displayName = "IconButton";
68
+
69
+ export { IconButton, iconButtonVariants };
@@ -0,0 +1,446 @@
1
+ // ─── Link ─────────────────────────────────────────────────────────────────
2
+
3
+ import * as React from "react";
4
+ import { Slot } from "@radix-ui/react-slot";
5
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
6
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
7
+ import * as SeparatorPrimitive from "@radix-ui/react-separator";
8
+ import { cva, type VariantProps } from "class-variance-authority";
9
+ import { cn } from "../../utils/cn";
10
+
11
+ // ─── Link ─────────────────────────────────────────────────────────────────
12
+
13
+ export interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
14
+ asChild?: boolean;
15
+ external?: boolean;
16
+ underline?: "always" | "hover" | "none";
17
+ }
18
+
19
+ const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
20
+ ({ className, asChild, external, underline = "hover", children, ...props }, ref) => {
21
+ const Comp = asChild ? Slot : "a";
22
+ const externalProps = external
23
+ ? { target: "_blank", rel: "noopener noreferrer" }
24
+ : {};
25
+
26
+ return (
27
+ <Comp
28
+ ref={ref}
29
+ className={cn(
30
+ "atlas-link text-primary transition-colors",
31
+ underline === "always" && "underline underline-offset-4",
32
+ underline === "hover" && "hover:underline underline-offset-4",
33
+ underline === "none" && "no-underline",
34
+ className
35
+ )}
36
+ {...externalProps}
37
+ {...props}
38
+ >
39
+ {children}
40
+ {external && (
41
+ <svg
42
+ className="inline-block ml-0.5 h-3 w-3 align-super"
43
+ fill="none"
44
+ stroke="currentColor"
45
+ viewBox="0 0 24 24"
46
+ aria-hidden="true"
47
+ >
48
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
49
+ d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
50
+ />
51
+ </svg>
52
+ )}
53
+ </Comp>
54
+ );
55
+ }
56
+ );
57
+ Link.displayName = "Link";
58
+
59
+ // ─── Badge ─────────────────────────────────────────────────────────────────
60
+
61
+ const badgeVariants = cva(
62
+ "atlas-badge inline-flex items-center gap-1 rounded-full font-medium transition-colors",
63
+ {
64
+ variants: {
65
+ variant: {
66
+ solid: "bg-primary text-primary-foreground",
67
+ outline: "border border-current bg-transparent",
68
+ soft: "bg-primary/10 text-primary",
69
+ neutral: "bg-muted text-muted-foreground",
70
+ },
71
+ size: {
72
+ sm: "px-1.5 py-0.5 text-[10px]",
73
+ md: "px-2 py-0.5 text-xs",
74
+ lg: "px-2.5 py-1 text-sm",
75
+ },
76
+ color: {
77
+ primary: "",
78
+ success: "",
79
+ warning: "",
80
+ danger: "",
81
+ info: "",
82
+ neutral: "",
83
+ },
84
+ },
85
+ compoundVariants: [
86
+ { variant: "solid", color: "success", className: "bg-success text-success-foreground" },
87
+ { variant: "solid", color: "warning", className: "bg-warning text-warning-foreground" },
88
+ { variant: "solid", color: "danger", className: "bg-destructive text-destructive-foreground" },
89
+ { variant: "solid", color: "info", className: "bg-info text-info-foreground" },
90
+ { variant: "soft", color: "success", className: "bg-success/10 text-success" },
91
+ { variant: "soft", color: "warning", className: "bg-warning/10 text-warning" },
92
+ { variant: "soft", color: "danger", className: "bg-destructive/10 text-destructive" },
93
+ { variant: "soft", color: "info", className: "bg-info/10 text-info" },
94
+ ],
95
+ defaultVariants: {
96
+ variant: "soft",
97
+ size: "md",
98
+ color: "primary",
99
+ },
100
+ }
101
+ );
102
+
103
+ export interface BadgeProps
104
+ extends Omit<React.HTMLAttributes<HTMLSpanElement>, "color">,
105
+ VariantProps<typeof badgeVariants> {
106
+ dot?: boolean;
107
+ }
108
+
109
+ const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
110
+ ({ className, variant, size, color, dot, children, ...props }, ref) => (
111
+ <span ref={ref} className={cn(badgeVariants({ variant, size, color, className }))} {...props}>
112
+ {dot && (
113
+ <span className="h-1.5 w-1.5 rounded-full bg-current" aria-hidden="true" />
114
+ )}
115
+ {children}
116
+ </span>
117
+ )
118
+ );
119
+ Badge.displayName = "Badge";
120
+
121
+ // ─── Avatar ────────────────────────────────────────────────────────────────
122
+
123
+ const avatarSizes = {
124
+ xs: "h-6 w-6 text-[10px]",
125
+ sm: "h-8 w-8 text-xs",
126
+ md: "h-10 w-10 text-sm",
127
+ lg: "h-12 w-12 text-base",
128
+ xl: "h-16 w-16 text-lg",
129
+ "2xl": "h-20 w-20 text-xl",
130
+ };
131
+
132
+ export interface AvatarProps
133
+ extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> {
134
+ src?: string;
135
+ alt?: string;
136
+ fallback?: string;
137
+ size?: keyof typeof avatarSizes;
138
+ shape?: "circle" | "square";
139
+ status?: "online" | "offline" | "busy" | "away";
140
+ }
141
+
142
+ const Avatar = React.forwardRef<
143
+ React.ElementRef<typeof AvatarPrimitive.Root>,
144
+ AvatarProps
145
+ >(({ className, src, alt, fallback, size = "md", shape = "circle", status, ...props }, ref) => (
146
+ <div className="atlas-avatar relative inline-flex shrink-0">
147
+ <AvatarPrimitive.Root
148
+ ref={ref}
149
+ className={cn(
150
+ "relative flex shrink-0 overflow-hidden",
151
+ avatarSizes[size],
152
+ shape === "circle" ? "rounded-full" : "rounded-md",
153
+ className
154
+ )}
155
+ {...props}
156
+ >
157
+ <AvatarPrimitive.Image
158
+ src={src}
159
+ alt={alt}
160
+ className="aspect-square h-full w-full object-cover"
161
+ />
162
+ <AvatarPrimitive.Fallback
163
+ className={cn(
164
+ "flex h-full w-full items-center justify-center",
165
+ "bg-muted font-medium text-muted-foreground uppercase"
166
+ )}
167
+ >
168
+ {fallback ?? (alt ? alt.slice(0, 2) : "?")}
169
+ </AvatarPrimitive.Fallback>
170
+ </AvatarPrimitive.Root>
171
+ {status && (
172
+ <span
173
+ aria-label={`Status: ${status}`}
174
+ className={cn(
175
+ "absolute bottom-0 right-0 block rounded-full ring-2 ring-background",
176
+ size === "xs" || size === "sm" ? "h-2 w-2" : "h-2.5 w-2.5",
177
+ status === "online" && "bg-success",
178
+ status === "offline" && "bg-muted-foreground",
179
+ status === "busy" && "bg-destructive",
180
+ status === "away" && "bg-warning"
181
+ )}
182
+ />
183
+ )}
184
+ </div>
185
+ ));
186
+ Avatar.displayName = "Avatar";
187
+
188
+ // ─── AvatarGroup ──────────────────────────────────────────────────────────
189
+
190
+ export interface AvatarGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "size"> {
191
+ max?: number;
192
+ size?: AvatarProps["size"];
193
+ spacing?: "tight" | "normal" | "loose";
194
+ }
195
+
196
+ const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
197
+ ({ className, children, max, size = "md", spacing = "tight", ...props }, ref) => {
198
+ const validChildren = React.Children.toArray(children).filter(React.isValidElement);
199
+ const visible = max ? validChildren.slice(0, max) : validChildren;
200
+ const overflow = max ? validChildren.length - max : 0;
201
+
202
+ const spacingMap = { tight: "-space-x-2", normal: "-space-x-1", loose: "space-x-1" };
203
+
204
+ return (
205
+ <div
206
+ ref={ref}
207
+ className={cn("atlas-avatar-group flex items-center", spacingMap[spacing], className)}
208
+ {...props}
209
+ >
210
+ {visible.map((child, i) =>
211
+ React.cloneElement(child as React.ReactElement<AvatarProps>, {
212
+ key: i,
213
+ size,
214
+ className: cn(
215
+ "ring-2 ring-background",
216
+ (child as React.ReactElement<AvatarProps>).props.className
217
+ ),
218
+ })
219
+ )}
220
+ {overflow > 0 && (
221
+ <span
222
+ className={cn(
223
+ "atlas-avatar relative flex shrink-0 items-center justify-center rounded-full",
224
+ "bg-muted text-muted-foreground font-medium ring-2 ring-background",
225
+ avatarSizes[size]
226
+ )}
227
+ aria-label={`${overflow} more`}
228
+ >
229
+ +{overflow}
230
+ </span>
231
+ )}
232
+ </div>
233
+ );
234
+ }
235
+ );
236
+ AvatarGroup.displayName = "AvatarGroup";
237
+
238
+ // ─── Divider ──────────────────────────────────────────────────────────────
239
+
240
+ export interface DividerProps
241
+ extends React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> {
242
+ label?: React.ReactNode;
243
+ labelPosition?: "left" | "center" | "right";
244
+ }
245
+
246
+ const Divider = React.forwardRef<
247
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
248
+ DividerProps
249
+ >(
250
+ ({ className, orientation = "horizontal", label, labelPosition = "center", ...props }, ref) => {
251
+ if (!label) {
252
+ return (
253
+ <SeparatorPrimitive.Root
254
+ ref={ref}
255
+ orientation={orientation}
256
+ className={cn(
257
+ "atlas-divider shrink-0 bg-border",
258
+ orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
259
+ className
260
+ )}
261
+ {...props}
262
+ />
263
+ );
264
+ }
265
+
266
+ return (
267
+ <div
268
+ className={cn(
269
+ "atlas-divider relative flex items-center gap-3 w-full",
270
+ className
271
+ )}
272
+ role="separator"
273
+ >
274
+ {labelPosition !== "left" && <span className="flex-1 bg-border h-px" />}
275
+ <span className="text-xs text-muted-foreground whitespace-nowrap">{label}</span>
276
+ {labelPosition !== "right" && <span className="flex-1 bg-border h-px" />}
277
+ </div>
278
+ );
279
+ }
280
+ );
281
+ Divider.displayName = "Divider";
282
+
283
+ // ─── Tag ──────────────────────────────────────────────────────────────────
284
+
285
+ export interface TagProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "color" | "size"> {
286
+ closable?: boolean;
287
+ onClose?: () => void;
288
+ icon?: React.ReactNode;
289
+ size?: "sm" | "md" | "lg";
290
+ variant?: "solid" | "outline" | "soft";
291
+ color?: "primary" | "success" | "warning" | "danger" | "neutral";
292
+ }
293
+
294
+ const Tag = React.forwardRef<HTMLSpanElement, TagProps>(
295
+ ({ className, closable, onClose, icon, size = "md", variant = "soft", color = "neutral", children, ...props }, ref) => (
296
+ <span
297
+ ref={ref}
298
+ className={cn(
299
+ "atlas-tag inline-flex items-center gap-1 rounded font-medium",
300
+ size === "sm" && "px-1.5 py-0.5 text-[10px]",
301
+ size === "md" && "px-2 py-1 text-xs",
302
+ size === "lg" && "px-3 py-1.5 text-sm",
303
+ variant === "soft" && color === "neutral" && "bg-muted text-muted-foreground",
304
+ variant === "soft" && color === "primary" && "bg-primary/10 text-primary",
305
+ variant === "soft" && color === "success" && "bg-success/10 text-success",
306
+ variant === "soft" && color === "warning" && "bg-warning/10 text-warning",
307
+ variant === "soft" && color === "danger" && "bg-destructive/10 text-destructive",
308
+ variant === "outline" && "border border-current bg-transparent",
309
+ variant === "solid" && color === "neutral" && "bg-muted-foreground text-background",
310
+ className
311
+ )}
312
+ {...props}
313
+ >
314
+ {icon && <span className="shrink-0" aria-hidden="true">{icon}</span>}
315
+ {children}
316
+ {closable && (
317
+ <button
318
+ type="button"
319
+ onClick={onClose}
320
+ className="ml-0.5 rounded-full hover:bg-black/10 dark:hover:bg-white/10 p-0.5 transition-colors"
321
+ aria-label="Remove tag"
322
+ >
323
+ <svg className="h-2.5 w-2.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
324
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
325
+ </svg>
326
+ </button>
327
+ )}
328
+ </span>
329
+ )
330
+ );
331
+ Tag.displayName = "Tag";
332
+
333
+ // ─── Chip ──────────────────────────────────────────────────────────────────
334
+
335
+ export interface ChipProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
336
+ selected?: boolean;
337
+ avatar?: React.ReactNode;
338
+ icon?: React.ReactNode;
339
+ closable?: boolean;
340
+ onClose?: (e: React.MouseEvent) => void;
341
+ size?: "sm" | "md" | "lg";
342
+ }
343
+
344
+ const Chip = React.forwardRef<HTMLButtonElement, ChipProps>(
345
+ ({ className, selected, avatar, icon, closable, onClose, size = "md", children, ...props }, ref) => (
346
+ <button
347
+ ref={ref}
348
+ type="button"
349
+ className={cn(
350
+ "atlas-chip inline-flex items-center gap-1.5 rounded-full font-medium",
351
+ "border transition-all duration-150 cursor-pointer",
352
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
353
+ "disabled:pointer-events-none disabled:opacity-50",
354
+ size === "sm" && "h-6 px-2 text-xs",
355
+ size === "md" && "h-8 px-3 text-sm",
356
+ size === "lg" && "h-9 px-4 text-sm",
357
+ selected
358
+ ? "bg-primary text-primary-foreground border-primary"
359
+ : "bg-background text-foreground border-border hover:bg-accent",
360
+ className
361
+ )}
362
+ aria-pressed={selected}
363
+ {...props}
364
+ >
365
+ {avatar && <span className="shrink-0 -ml-0.5">{avatar}</span>}
366
+ {icon && !avatar && <span className="shrink-0 [&>svg]:h-3.5 [&>svg]:w-3.5" aria-hidden="true">{icon}</span>}
367
+ {children}
368
+ {closable && (
369
+ <span
370
+ role="button"
371
+ tabIndex={0}
372
+ onClick={(e) => { e.stopPropagation(); onClose?.(e); }}
373
+ className="shrink-0 -mr-0.5 rounded-full hover:bg-black/10 dark:hover:bg-white/10 p-0.5"
374
+ aria-label="Remove"
375
+ >
376
+ <svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
377
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
378
+ </svg>
379
+ </span>
380
+ )}
381
+ </button>
382
+ )
383
+ );
384
+ Chip.displayName = "Chip";
385
+
386
+ // ─── Tooltip ──────────────────────────────────────────────────────────────
387
+
388
+ const TooltipProvider = TooltipPrimitive.Provider;
389
+
390
+ const TooltipRoot = TooltipPrimitive.Root;
391
+
392
+ const TooltipTrigger = TooltipPrimitive.Trigger;
393
+
394
+ const TooltipContent = React.forwardRef<
395
+ React.ElementRef<typeof TooltipPrimitive.Content>,
396
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
397
+ >(({ className, sideOffset = 4, ...props }, ref) => (
398
+ <TooltipPrimitive.Portal>
399
+ <TooltipPrimitive.Content
400
+ ref={ref}
401
+ sideOffset={sideOffset}
402
+ className={cn(
403
+ "atlas-tooltip z-50 overflow-hidden rounded-md",
404
+ "bg-foreground px-3 py-1.5 text-xs text-background shadow-md",
405
+ "animate-in fade-in-0 zoom-in-95",
406
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
407
+ "data-[side=bottom]:slide-in-from-top-2",
408
+ "data-[side=left]:slide-in-from-right-2",
409
+ "data-[side=right]:slide-in-from-left-2",
410
+ "data-[side=top]:slide-in-from-bottom-2",
411
+ className
412
+ )}
413
+ {...props}
414
+ />
415
+ </TooltipPrimitive.Portal>
416
+ ));
417
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
418
+
419
+ export interface TooltipProps {
420
+ content: React.ReactNode;
421
+ children: React.ReactNode;
422
+ side?: "top" | "right" | "bottom" | "left";
423
+ delayDuration?: number;
424
+ className?: string;
425
+ }
426
+
427
+ const Tooltip = ({ content, children, side = "top", delayDuration = 300, className }: TooltipProps) => (
428
+ <TooltipProvider>
429
+ <TooltipRoot delayDuration={delayDuration}>
430
+ <TooltipTrigger asChild>{children}</TooltipTrigger>
431
+ <TooltipContent side={side} className={className}>{content}</TooltipContent>
432
+ </TooltipRoot>
433
+ </TooltipProvider>
434
+ );
435
+ Tooltip.displayName = "Tooltip";
436
+
437
+ export {
438
+ Link,
439
+ Badge, badgeVariants,
440
+ Avatar,
441
+ AvatarGroup,
442
+ Divider,
443
+ Tag,
444
+ Chip,
445
+ Tooltip, TooltipProvider, TooltipRoot, TooltipTrigger, TooltipContent,
446
+ };