@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.
- package/README.md +151 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +640 -0
- package/package.json +43 -0
- package/registry/accordion.tsx +158 -0
- package/registry/alert-dialog.tsx +206 -0
- package/registry/alert.tsx +73 -0
- package/registry/aspect-ratio.tsx +44 -0
- package/registry/avatar.tsx +94 -0
- package/registry/badge.tsx +68 -0
- package/registry/breadcrumb.tsx +151 -0
- package/registry/button-group.tsx +84 -0
- package/registry/button.tsx +102 -0
- package/registry/calendar.tsx +238 -0
- package/registry/card.tsx +114 -0
- package/registry/carousel.tsx +169 -0
- package/registry/checkbox.tsx +79 -0
- package/registry/collapsible.tsx +110 -0
- package/registry/container.tsx +60 -0
- package/registry/dialog.tsx +264 -0
- package/registry/dropdown-menu.tsx +387 -0
- package/registry/image.tsx +144 -0
- package/registry/input.tsx +44 -0
- package/registry/label.tsx +34 -0
- package/registry/loading-spinner.tsx +108 -0
- package/registry/otp-input.tsx +152 -0
- package/registry/pagination.tsx +146 -0
- package/registry/popover.tsx +135 -0
- package/registry/progress.tsx +49 -0
- package/registry/radio.tsx +99 -0
- package/registry/search.tsx +146 -0
- package/registry/select.tsx +190 -0
- package/registry/separator.tsx +44 -0
- package/registry/sheet.tsx +180 -0
- package/registry/skeleton.tsx +26 -0
- package/registry/slider.tsx +115 -0
- package/registry/star-rating.tsx +131 -0
- package/registry/switch.tsx +70 -0
- package/registry/table.tsx +136 -0
- package/registry/tabs.tsx +122 -0
- package/registry/text.tsx +70 -0
- package/registry/textarea.tsx +39 -0
- package/registry/toast.tsx +95 -0
- 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 }
|