@srcroot/ui 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 (44) hide show
  1. package/README.md +151 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +640 -0
  4. package/package.json +43 -0
  5. package/registry/accordion.tsx +158 -0
  6. package/registry/alert-dialog.tsx +206 -0
  7. package/registry/alert.tsx +73 -0
  8. package/registry/aspect-ratio.tsx +44 -0
  9. package/registry/avatar.tsx +94 -0
  10. package/registry/badge.tsx +68 -0
  11. package/registry/breadcrumb.tsx +151 -0
  12. package/registry/button-group.tsx +84 -0
  13. package/registry/button.tsx +102 -0
  14. package/registry/calendar.tsx +238 -0
  15. package/registry/card.tsx +114 -0
  16. package/registry/carousel.tsx +169 -0
  17. package/registry/checkbox.tsx +79 -0
  18. package/registry/collapsible.tsx +110 -0
  19. package/registry/container.tsx +60 -0
  20. package/registry/dialog.tsx +264 -0
  21. package/registry/dropdown-menu.tsx +387 -0
  22. package/registry/image.tsx +144 -0
  23. package/registry/input.tsx +44 -0
  24. package/registry/label.tsx +34 -0
  25. package/registry/loading-spinner.tsx +108 -0
  26. package/registry/otp-input.tsx +152 -0
  27. package/registry/pagination.tsx +146 -0
  28. package/registry/popover.tsx +135 -0
  29. package/registry/progress.tsx +49 -0
  30. package/registry/radio.tsx +99 -0
  31. package/registry/search.tsx +146 -0
  32. package/registry/select.tsx +190 -0
  33. package/registry/separator.tsx +44 -0
  34. package/registry/sheet.tsx +180 -0
  35. package/registry/skeleton.tsx +26 -0
  36. package/registry/slider.tsx +115 -0
  37. package/registry/star-rating.tsx +131 -0
  38. package/registry/switch.tsx +70 -0
  39. package/registry/table.tsx +136 -0
  40. package/registry/tabs.tsx +122 -0
  41. package/registry/text.tsx +70 -0
  42. package/registry/textarea.tsx +39 -0
  43. package/registry/toast.tsx +95 -0
  44. package/registry/tooltip.tsx +122 -0
@@ -0,0 +1,158 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ interface AccordionContextValue {
5
+ value: string[]
6
+ onValueChange: (value: string[]) => void
7
+ type: "single" | "multiple"
8
+ }
9
+
10
+ const AccordionContext = React.createContext<AccordionContextValue | null>(null)
11
+
12
+ interface AccordionItemContextValue {
13
+ value: string
14
+ isOpen: boolean
15
+ toggle: () => void
16
+ }
17
+
18
+ const AccordionItemContext = React.createContext<AccordionItemContextValue | null>(null)
19
+
20
+ interface AccordionProps extends React.HTMLAttributes<HTMLDivElement> {
21
+ type?: "single" | "multiple"
22
+ value?: string[]
23
+ onValueChange?: (value: string[]) => void
24
+ defaultValue?: string[]
25
+ collapsible?: boolean
26
+ }
27
+
28
+ /**
29
+ * Accordion component with single/multiple expand modes
30
+ *
31
+ * @example
32
+ * <Accordion type="single" collapsible>
33
+ * <AccordionItem value="item-1">
34
+ * <AccordionTrigger>Section 1</AccordionTrigger>
35
+ * <AccordionContent>Content 1</AccordionContent>
36
+ * </AccordionItem>
37
+ * </Accordion>
38
+ */
39
+ const Accordion = React.forwardRef<HTMLDivElement, AccordionProps>(
40
+ ({ className, type = "single", value: controlledValue, onValueChange, defaultValue = [], children, ...props }, ref) => {
41
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue)
42
+
43
+ const value = controlledValue !== undefined ? controlledValue : uncontrolledValue
44
+ const setValue = onValueChange || setUncontrolledValue
45
+
46
+ return (
47
+ <AccordionContext.Provider value={{ value, onValueChange: setValue, type }}>
48
+ <div ref={ref} className={cn("", className)} {...props}>
49
+ {children}
50
+ </div>
51
+ </AccordionContext.Provider>
52
+ )
53
+ }
54
+ )
55
+ Accordion.displayName = "Accordion"
56
+
57
+ interface AccordionItemProps extends React.HTMLAttributes<HTMLDivElement> {
58
+ value: string
59
+ }
60
+
61
+ const AccordionItem = React.forwardRef<HTMLDivElement, AccordionItemProps>(
62
+ ({ className, value, children, ...props }, ref) => {
63
+ const context = React.useContext(AccordionContext)
64
+ if (!context) throw new Error("AccordionItem must be used within Accordion")
65
+
66
+ const isOpen = context.value.includes(value)
67
+
68
+ const toggle = () => {
69
+ if (context.type === "single") {
70
+ context.onValueChange(isOpen ? [] : [value])
71
+ } else {
72
+ context.onValueChange(
73
+ isOpen
74
+ ? context.value.filter((v) => v !== value)
75
+ : [...context.value, value]
76
+ )
77
+ }
78
+ }
79
+
80
+ return (
81
+ <AccordionItemContext.Provider value={{ value, isOpen, toggle }}>
82
+ <div
83
+ ref={ref}
84
+ className={cn("border-b", className)}
85
+ data-state={isOpen ? "open" : "closed"}
86
+ {...props}
87
+ >
88
+ {children}
89
+ </div>
90
+ </AccordionItemContext.Provider>
91
+ )
92
+ }
93
+ )
94
+ AccordionItem.displayName = "AccordionItem"
95
+
96
+ const AccordionTrigger = React.forwardRef<
97
+ HTMLButtonElement,
98
+ React.ButtonHTMLAttributes<HTMLButtonElement>
99
+ >(({ className, children, ...props }, ref) => {
100
+ const context = React.useContext(AccordionItemContext)
101
+ if (!context) throw new Error("AccordionTrigger must be used within AccordionItem")
102
+
103
+ return (
104
+ <h3 className="flex">
105
+ <button
106
+ ref={ref}
107
+ type="button"
108
+ aria-expanded={context.isOpen}
109
+ className={cn(
110
+ "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
111
+ className
112
+ )}
113
+ data-state={context.isOpen ? "open" : "closed"}
114
+ onClick={context.toggle}
115
+ {...props}
116
+ >
117
+ {children}
118
+ <svg
119
+ className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200"
120
+ fill="none"
121
+ viewBox="0 0 24 24"
122
+ stroke="currentColor"
123
+ strokeWidth={2}
124
+ >
125
+ <path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
126
+ </svg>
127
+ </button>
128
+ </h3>
129
+ )
130
+ })
131
+ AccordionTrigger.displayName = "AccordionTrigger"
132
+
133
+ const AccordionContent = React.forwardRef<
134
+ HTMLDivElement,
135
+ React.HTMLAttributes<HTMLDivElement>
136
+ >(({ className, children, ...props }, ref) => {
137
+ const context = React.useContext(AccordionItemContext)
138
+ if (!context) throw new Error("AccordionContent must be used within AccordionItem")
139
+
140
+ return (
141
+ <div
142
+ ref={ref}
143
+ role="region"
144
+ className={cn(
145
+ "overflow-hidden text-sm",
146
+ context.isOpen ? "animate-accordion-down" : "animate-accordion-up hidden",
147
+ className
148
+ )}
149
+ data-state={context.isOpen ? "open" : "closed"}
150
+ {...props}
151
+ >
152
+ <div className="pb-4 pt-0">{children}</div>
153
+ </div>
154
+ )
155
+ })
156
+ AccordionContent.displayName = "AccordionContent"
157
+
158
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
@@ -0,0 +1,206 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ interface AlertDialogContextValue {
5
+ open: boolean
6
+ onOpenChange: (open: boolean) => void
7
+ }
8
+
9
+ const AlertDialogContext = React.createContext<AlertDialogContextValue | null>(null)
10
+
11
+ interface AlertDialogProps {
12
+ children: React.ReactNode
13
+ open?: boolean
14
+ onOpenChange?: (open: boolean) => void
15
+ defaultOpen?: boolean
16
+ }
17
+
18
+ /**
19
+ * AlertDialog for confirmation actions
20
+ * Unlike Dialog, it requires explicit action to close (no click-outside dismiss)
21
+ *
22
+ * @example
23
+ * <AlertDialog>
24
+ * <AlertDialogTrigger asChild>
25
+ * <Button variant="destructive">Delete</Button>
26
+ * </AlertDialogTrigger>
27
+ * <AlertDialogContent>
28
+ * <AlertDialogHeader>
29
+ * <AlertDialogTitle>Are you sure?</AlertDialogTitle>
30
+ * <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
31
+ * </AlertDialogHeader>
32
+ * <AlertDialogFooter>
33
+ * <AlertDialogCancel>Cancel</AlertDialogCancel>
34
+ * <AlertDialogAction>Continue</AlertDialogAction>
35
+ * </AlertDialogFooter>
36
+ * </AlertDialogContent>
37
+ * </AlertDialog>
38
+ */
39
+ function AlertDialog({ children, open: controlledOpen, onOpenChange, defaultOpen = false }: AlertDialogProps) {
40
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
41
+
42
+ const open = controlledOpen !== undefined ? controlledOpen : uncontrolledOpen
43
+ const setOpen = onOpenChange || setUncontrolledOpen
44
+
45
+ return (
46
+ <AlertDialogContext.Provider value={{ open, onOpenChange: setOpen }}>
47
+ {children}
48
+ </AlertDialogContext.Provider>
49
+ )
50
+ }
51
+
52
+ interface AlertDialogTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
53
+ asChild?: boolean
54
+ }
55
+
56
+ const AlertDialogTrigger = React.forwardRef<HTMLButtonElement, AlertDialogTriggerProps>(
57
+ ({ onClick, asChild, children, ...props }, ref) => {
58
+ const context = React.useContext(AlertDialogContext)
59
+ if (!context) throw new Error("AlertDialogTrigger must be used within AlertDialog")
60
+
61
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
62
+ onClick?.(e)
63
+ context.onOpenChange(true)
64
+ }
65
+
66
+ if (asChild && React.isValidElement(children)) {
67
+ return React.cloneElement(children as React.ReactElement<any>, {
68
+ onClick: handleClick,
69
+ ref,
70
+ })
71
+ }
72
+
73
+ return (
74
+ <button ref={ref} onClick={handleClick} {...props}>
75
+ {children}
76
+ </button>
77
+ )
78
+ }
79
+ )
80
+ AlertDialogTrigger.displayName = "AlertDialogTrigger"
81
+
82
+ const AlertDialogContent = React.forwardRef<
83
+ HTMLDivElement,
84
+ React.HTMLAttributes<HTMLDivElement>
85
+ >(({ className, children, ...props }, ref) => {
86
+ const context = React.useContext(AlertDialogContext)
87
+ if (!context) throw new Error("AlertDialogContent must be used within AlertDialog")
88
+
89
+ React.useEffect(() => {
90
+ if (context.open) {
91
+ document.body.style.overflow = "hidden"
92
+ }
93
+ return () => {
94
+ document.body.style.overflow = ""
95
+ }
96
+ }, [context.open])
97
+
98
+ if (!context.open) return null
99
+
100
+ return (
101
+ <>
102
+ <div className="fixed inset-0 z-50 bg-black/80" />
103
+ <div
104
+ ref={ref}
105
+ role="alertdialog"
106
+ aria-modal="true"
107
+ className={cn(
108
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 sm:rounded-lg",
109
+ className
110
+ )}
111
+ {...props}
112
+ >
113
+ {children}
114
+ </div>
115
+ </>
116
+ )
117
+ })
118
+ AlertDialogContent.displayName = "AlertDialogContent"
119
+
120
+ const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
121
+ <div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
122
+ )
123
+ AlertDialogHeader.displayName = "AlertDialogHeader"
124
+
125
+ const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
126
+ <div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
127
+ )
128
+ AlertDialogFooter.displayName = "AlertDialogFooter"
129
+
130
+ const AlertDialogTitle = React.forwardRef<
131
+ HTMLHeadingElement,
132
+ React.HTMLAttributes<HTMLHeadingElement>
133
+ >(({ className, ...props }, ref) => (
134
+ <h2 ref={ref} className={cn("text-lg font-semibold", className)} {...props} />
135
+ ))
136
+ AlertDialogTitle.displayName = "AlertDialogTitle"
137
+
138
+ const AlertDialogDescription = React.forwardRef<
139
+ HTMLParagraphElement,
140
+ React.HTMLAttributes<HTMLParagraphElement>
141
+ >(({ className, ...props }, ref) => (
142
+ <p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
143
+ ))
144
+ AlertDialogDescription.displayName = "AlertDialogDescription"
145
+
146
+ const AlertDialogAction = React.forwardRef<
147
+ HTMLButtonElement,
148
+ React.ButtonHTMLAttributes<HTMLButtonElement>
149
+ >(({ className, onClick, ...props }, ref) => {
150
+ const context = React.useContext(AlertDialogContext)
151
+
152
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
153
+ onClick?.(e)
154
+ context?.onOpenChange(false)
155
+ }
156
+
157
+ return (
158
+ <button
159
+ ref={ref}
160
+ className={cn(
161
+ "inline-flex h-10 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground transition-colors hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
162
+ className
163
+ )}
164
+ onClick={handleClick}
165
+ {...props}
166
+ />
167
+ )
168
+ })
169
+ AlertDialogAction.displayName = "AlertDialogAction"
170
+
171
+ const AlertDialogCancel = React.forwardRef<
172
+ HTMLButtonElement,
173
+ React.ButtonHTMLAttributes<HTMLButtonElement>
174
+ >(({ className, onClick, ...props }, ref) => {
175
+ const context = React.useContext(AlertDialogContext)
176
+
177
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
178
+ onClick?.(e)
179
+ context?.onOpenChange(false)
180
+ }
181
+
182
+ return (
183
+ <button
184
+ ref={ref}
185
+ className={cn(
186
+ "mt-2 inline-flex h-10 items-center justify-center rounded-md border border-input bg-transparent px-4 py-2 text-sm font-semibold transition-colors hover:bg-accent hover:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 sm:mt-0",
187
+ className
188
+ )}
189
+ onClick={handleClick}
190
+ {...props}
191
+ />
192
+ )
193
+ })
194
+ AlertDialogCancel.displayName = "AlertDialogCancel"
195
+
196
+ export {
197
+ AlertDialog,
198
+ AlertDialogTrigger,
199
+ AlertDialogContent,
200
+ AlertDialogHeader,
201
+ AlertDialogFooter,
202
+ AlertDialogTitle,
203
+ AlertDialogDescription,
204
+ AlertDialogAction,
205
+ AlertDialogCancel,
206
+ }
@@ -0,0 +1,73 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const alertVariants = cva(
6
+ "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-background text-foreground",
11
+ destructive:
12
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ variant: "default",
17
+ },
18
+ }
19
+ )
20
+
21
+ /**
22
+ * Alert component for inline feedback
23
+ *
24
+ * @example
25
+ * <Alert>
26
+ * <AlertTitle>Note</AlertTitle>
27
+ * <AlertDescription>This is an informational message.</AlertDescription>
28
+ * </Alert>
29
+ *
30
+ * @example
31
+ * <Alert variant="destructive">
32
+ * <AlertTitle>Error</AlertTitle>
33
+ * <AlertDescription>Something went wrong.</AlertDescription>
34
+ * </Alert>
35
+ */
36
+ const Alert = React.forwardRef<
37
+ HTMLDivElement,
38
+ React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
39
+ >(({ className, variant, ...props }, ref) => (
40
+ <div
41
+ ref={ref}
42
+ role="alert"
43
+ className={cn(alertVariants({ variant }), className)}
44
+ {...props}
45
+ />
46
+ ))
47
+ Alert.displayName = "Alert"
48
+
49
+ const AlertTitle = React.forwardRef<
50
+ HTMLParagraphElement,
51
+ React.HTMLAttributes<HTMLHeadingElement>
52
+ >(({ className, ...props }, ref) => (
53
+ <h5
54
+ ref={ref}
55
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
56
+ {...props}
57
+ />
58
+ ))
59
+ AlertTitle.displayName = "AlertTitle"
60
+
61
+ const AlertDescription = React.forwardRef<
62
+ HTMLParagraphElement,
63
+ React.HTMLAttributes<HTMLParagraphElement>
64
+ >(({ className, ...props }, ref) => (
65
+ <div
66
+ ref={ref}
67
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
68
+ {...props}
69
+ />
70
+ ))
71
+ AlertDescription.displayName = "AlertDescription"
72
+
73
+ export { Alert, AlertTitle, AlertDescription }
@@ -0,0 +1,44 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ interface AspectRatioProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ /**
6
+ * The aspect ratio (width / height)
7
+ * @default 1
8
+ */
9
+ ratio?: number
10
+ }
11
+
12
+ /**
13
+ * AspectRatio component to maintain consistent dimensions
14
+ *
15
+ * @example
16
+ * // 16:9 video aspect ratio
17
+ * <AspectRatio ratio={16 / 9}>
18
+ * <img src="..." className="object-cover w-full h-full" />
19
+ * </AspectRatio>
20
+ *
21
+ * @example
22
+ * // Square
23
+ * <AspectRatio ratio={1}>
24
+ * <div className="bg-muted" />
25
+ * </AspectRatio>
26
+ */
27
+ const AspectRatio = React.forwardRef<HTMLDivElement, AspectRatioProps>(
28
+ ({ className, ratio = 1, style, children, ...props }, ref) => (
29
+ <div
30
+ ref={ref}
31
+ className={cn("relative w-full", className)}
32
+ style={{
33
+ paddingBottom: `${100 / ratio}%`,
34
+ ...style,
35
+ }}
36
+ {...props}
37
+ >
38
+ <div className="absolute inset-0">{children}</div>
39
+ </div>
40
+ )
41
+ )
42
+ AspectRatio.displayName = "AspectRatio"
43
+
44
+ export { AspectRatio }
@@ -0,0 +1,94 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const avatarVariants = cva(
6
+ "relative flex shrink-0 overflow-hidden rounded-full",
7
+ {
8
+ variants: {
9
+ size: {
10
+ sm: "h-8 w-8 text-xs",
11
+ default: "h-10 w-10 text-sm",
12
+ lg: "h-12 w-12 text-base",
13
+ xl: "h-16 w-16 text-lg",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ size: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ type AvatarVariants = VariantProps<typeof avatarVariants>
23
+
24
+ interface AvatarProps extends AvatarVariants {
25
+ className?: string
26
+ children?: React.ReactNode
27
+ }
28
+
29
+ /**
30
+ * Avatar component with image and fallback
31
+ *
32
+ * @example
33
+ * <Avatar>
34
+ * <AvatarImage src="/avatar.jpg" alt="User" />
35
+ * <AvatarFallback>JD</AvatarFallback>
36
+ * </Avatar>
37
+ */
38
+ const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(
39
+ ({ className, size, ...props }, ref) => (
40
+ <span
41
+ ref={ref}
42
+ className={cn(avatarVariants({ size, className }))}
43
+ {...props}
44
+ />
45
+ )
46
+ )
47
+ Avatar.displayName = "Avatar"
48
+
49
+ interface AvatarImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
50
+ onLoadingStatusChange?: (status: "loading" | "loaded" | "error") => void
51
+ }
52
+
53
+ const AvatarImage = React.forwardRef<HTMLImageElement, AvatarImageProps>(
54
+ ({ className, onLoadingStatusChange, ...props }, ref) => {
55
+ const [status, setStatus] = React.useState<"loading" | "loaded" | "error">("loading")
56
+
57
+ React.useEffect(() => {
58
+ onLoadingStatusChange?.(status)
59
+ }, [status, onLoadingStatusChange])
60
+
61
+ return (
62
+ <img
63
+ ref={ref}
64
+ className={cn(
65
+ "aspect-square h-full w-full object-cover",
66
+ status === "loading" && "opacity-0",
67
+ status === "error" && "hidden",
68
+ className
69
+ )}
70
+ onLoad={() => setStatus("loaded")}
71
+ onError={() => setStatus("error")}
72
+ {...props}
73
+ />
74
+ )
75
+ }
76
+ )
77
+ AvatarImage.displayName = "AvatarImage"
78
+
79
+ const AvatarFallback = React.forwardRef<
80
+ HTMLSpanElement,
81
+ React.HTMLAttributes<HTMLSpanElement>
82
+ >(({ className, ...props }, ref) => (
83
+ <span
84
+ ref={ref}
85
+ className={cn(
86
+ "flex h-full w-full items-center justify-center rounded-full bg-muted font-medium",
87
+ className
88
+ )}
89
+ {...props}
90
+ />
91
+ ))
92
+ AvatarFallback.displayName = "AvatarFallback"
93
+
94
+ export { Avatar, AvatarImage, AvatarFallback, avatarVariants }
@@ -0,0 +1,68 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const badgeVariants = cva(
6
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default:
11
+ "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
12
+ secondary:
13
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
14
+ destructive:
15
+ "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
16
+ outline: "text-foreground",
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ variant: "default",
21
+ },
22
+ }
23
+ )
24
+
25
+ type BadgeVariants = VariantProps<typeof badgeVariants>
26
+
27
+ interface BadgeBaseProps extends BadgeVariants {
28
+ className?: string
29
+ children?: React.ReactNode
30
+ }
31
+
32
+ /**
33
+ * Polymorphic Badge component for status indicators
34
+ *
35
+ * @example
36
+ * // Default badge
37
+ * <Badge>New</Badge>
38
+ *
39
+ * // Destructive variant
40
+ * <Badge variant="destructive">Error</Badge>
41
+ *
42
+ * // As a link
43
+ * <Badge as="a" href="/status">View Status</Badge>
44
+ */
45
+ const Badge = React.forwardRef(
46
+ <T extends React.ElementType = "span">(
47
+ {
48
+ as,
49
+ className,
50
+ variant,
51
+ ...props
52
+ }: BadgeBaseProps & { as?: T } & Omit<React.ComponentPropsWithoutRef<T>, keyof BadgeBaseProps | "as">,
53
+ ref: React.ForwardedRef<React.ElementRef<T>>
54
+ ) => {
55
+ const Comp = as || "span"
56
+
57
+ return (
58
+ <Comp
59
+ ref={ref as any}
60
+ className={cn(badgeVariants({ variant, className }))}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+ )
66
+ Badge.displayName = "Badge"
67
+
68
+ export { Badge, badgeVariants }