@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,169 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ interface CarouselContextValue {
5
+ currentIndex: number
6
+ setCurrentIndex: (index: number) => void
7
+ itemsCount: number
8
+ setItemsCount: (count: number) => void
9
+ }
10
+
11
+ const CarouselContext = React.createContext<CarouselContextValue | null>(null)
12
+
13
+ interface CarouselProps extends React.HTMLAttributes<HTMLDivElement> {
14
+ /** Auto-play interval in ms (0 to disable) */
15
+ autoPlay?: number
16
+ /** Loop back to start */
17
+ loop?: boolean
18
+ }
19
+
20
+ /**
21
+ * Carousel/Slider component
22
+ *
23
+ * @example
24
+ * <Carousel>
25
+ * <CarouselContent>
26
+ * <CarouselItem>Slide 1</CarouselItem>
27
+ * <CarouselItem>Slide 2</CarouselItem>
28
+ * </CarouselContent>
29
+ * <CarouselPrevious />
30
+ * <CarouselNext />
31
+ * </Carousel>
32
+ */
33
+ const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
34
+ ({ className, children, autoPlay = 0, loop = true, ...props }, ref) => {
35
+ const [currentIndex, setCurrentIndex] = React.useState(0)
36
+ const [itemsCount, setItemsCount] = React.useState(0)
37
+
38
+ React.useEffect(() => {
39
+ if (autoPlay > 0 && itemsCount > 0) {
40
+ const interval = setInterval(() => {
41
+ setCurrentIndex((prev) => {
42
+ if (prev >= itemsCount - 1) {
43
+ return loop ? 0 : prev
44
+ }
45
+ return prev + 1
46
+ })
47
+ }, autoPlay)
48
+ return () => clearInterval(interval)
49
+ }
50
+ }, [autoPlay, itemsCount, loop])
51
+
52
+ return (
53
+ <CarouselContext.Provider value={{ currentIndex, setCurrentIndex, itemsCount, setItemsCount }}>
54
+ <div
55
+ ref={ref}
56
+ className={cn("relative", className)}
57
+ role="region"
58
+ aria-roledescription="carousel"
59
+ {...props}
60
+ >
61
+ {children}
62
+ </div>
63
+ </CarouselContext.Provider>
64
+ )
65
+ }
66
+ )
67
+ Carousel.displayName = "Carousel"
68
+
69
+ const CarouselContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
70
+ ({ className, children, ...props }, ref) => {
71
+ const context = React.useContext(CarouselContext)
72
+ if (!context) throw new Error("CarouselContent must be used within Carousel")
73
+
74
+ const childrenArray = React.Children.toArray(children)
75
+
76
+ React.useEffect(() => {
77
+ context.setItemsCount(childrenArray.length)
78
+ }, [childrenArray.length, context])
79
+
80
+ return (
81
+ <div ref={ref} className={cn("overflow-hidden", className)} {...props}>
82
+ <div
83
+ className="flex transition-transform duration-300 ease-in-out"
84
+ style={{ transform: `translateX(-${context.currentIndex * 100}%)` }}
85
+ >
86
+ {children}
87
+ </div>
88
+ </div>
89
+ )
90
+ }
91
+ )
92
+ CarouselContent.displayName = "CarouselContent"
93
+
94
+ const CarouselItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
95
+ ({ className, ...props }, ref) => (
96
+ <div
97
+ ref={ref}
98
+ role="group"
99
+ aria-roledescription="slide"
100
+ className={cn("min-w-0 shrink-0 grow-0 basis-full", className)}
101
+ {...props}
102
+ />
103
+ )
104
+ )
105
+ CarouselItem.displayName = "CarouselItem"
106
+
107
+ const CarouselPrevious = React.forwardRef<
108
+ HTMLButtonElement,
109
+ React.ButtonHTMLAttributes<HTMLButtonElement>
110
+ >(({ className, ...props }, ref) => {
111
+ const context = React.useContext(CarouselContext)
112
+ if (!context) throw new Error("CarouselPrevious must be used within Carousel")
113
+
114
+ const canGoPrev = context.currentIndex > 0
115
+
116
+ return (
117
+ <button
118
+ ref={ref}
119
+ type="button"
120
+ className={cn(
121
+ "absolute left-4 top-1/2 -translate-y-1/2 h-8 w-8 rounded-full border bg-background shadow-md flex items-center justify-center",
122
+ "hover:bg-accent disabled:opacity-50",
123
+ className
124
+ )}
125
+ disabled={!canGoPrev}
126
+ onClick={() => context.setCurrentIndex(context.currentIndex - 1)}
127
+ aria-label="Previous slide"
128
+ {...props}
129
+ >
130
+ <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
131
+ <path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
132
+ </svg>
133
+ </button>
134
+ )
135
+ })
136
+ CarouselPrevious.displayName = "CarouselPrevious"
137
+
138
+ const CarouselNext = React.forwardRef<
139
+ HTMLButtonElement,
140
+ React.ButtonHTMLAttributes<HTMLButtonElement>
141
+ >(({ className, ...props }, ref) => {
142
+ const context = React.useContext(CarouselContext)
143
+ if (!context) throw new Error("CarouselNext must be used within Carousel")
144
+
145
+ const canGoNext = context.currentIndex < context.itemsCount - 1
146
+
147
+ return (
148
+ <button
149
+ ref={ref}
150
+ type="button"
151
+ className={cn(
152
+ "absolute right-4 top-1/2 -translate-y-1/2 h-8 w-8 rounded-full border bg-background shadow-md flex items-center justify-center",
153
+ "hover:bg-accent disabled:opacity-50",
154
+ className
155
+ )}
156
+ disabled={!canGoNext}
157
+ onClick={() => context.setCurrentIndex(context.currentIndex + 1)}
158
+ aria-label="Next slide"
159
+ {...props}
160
+ >
161
+ <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
162
+ <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
163
+ </svg>
164
+ </button>
165
+ )
166
+ })
167
+ CarouselNext.displayName = "CarouselNext"
168
+
169
+ export { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext }
@@ -0,0 +1,79 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ interface CheckboxProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onChange"> {
5
+ /**
6
+ * Whether the checkbox is checked
7
+ */
8
+ checked?: boolean
9
+ /**
10
+ * Callback when the checked state changes
11
+ */
12
+ onCheckedChange?: (checked: boolean) => void
13
+ /**
14
+ * Whether the checkbox is disabled
15
+ */
16
+ disabled?: boolean
17
+ }
18
+
19
+ /**
20
+ * Checkbox component with keyboard accessibility
21
+ *
22
+ * @example
23
+ * const [checked, setChecked] = useState(false)
24
+ * <Checkbox checked={checked} onCheckedChange={setChecked} />
25
+ */
26
+ const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
27
+ ({ className, checked = false, onCheckedChange, disabled, ...props }, ref) => {
28
+ const handleClick = () => {
29
+ if (!disabled) {
30
+ onCheckedChange?.(!checked)
31
+ }
32
+ }
33
+
34
+ const handleKeyDown = (e: React.KeyboardEvent) => {
35
+ if (e.key === " " || e.key === "Enter") {
36
+ e.preventDefault()
37
+ handleClick()
38
+ }
39
+ }
40
+
41
+ return (
42
+ <button
43
+ type="button"
44
+ role="checkbox"
45
+ aria-checked={checked}
46
+ aria-disabled={disabled}
47
+ disabled={disabled}
48
+ ref={ref}
49
+ className={cn(
50
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
51
+ checked && "bg-primary text-primary-foreground",
52
+ className
53
+ )}
54
+ onClick={handleClick}
55
+ onKeyDown={handleKeyDown}
56
+ {...props}
57
+ >
58
+ {checked && (
59
+ <svg
60
+ className="h-full w-full"
61
+ fill="none"
62
+ viewBox="0 0 24 24"
63
+ stroke="currentColor"
64
+ strokeWidth={3}
65
+ >
66
+ <path
67
+ strokeLinecap="round"
68
+ strokeLinejoin="round"
69
+ d="M5 13l4 4L19 7"
70
+ />
71
+ </svg>
72
+ )}
73
+ </button>
74
+ )
75
+ }
76
+ )
77
+ Checkbox.displayName = "Checkbox"
78
+
79
+ export { Checkbox }
@@ -0,0 +1,110 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ interface CollapsibleContextValue {
5
+ open: boolean
6
+ onOpenChange: (open: boolean) => void
7
+ }
8
+
9
+ const CollapsibleContext = React.createContext<CollapsibleContextValue | null>(null)
10
+
11
+ interface CollapsibleProps {
12
+ children: React.ReactNode
13
+ open?: boolean
14
+ onOpenChange?: (open: boolean) => void
15
+ defaultOpen?: boolean
16
+ className?: string
17
+ }
18
+
19
+ /**
20
+ * Collapsible component for expandable content
21
+ *
22
+ * @example
23
+ * <Collapsible>
24
+ * <CollapsibleTrigger>Toggle</CollapsibleTrigger>
25
+ * <CollapsibleContent>
26
+ * Hidden content here
27
+ * </CollapsibleContent>
28
+ * </Collapsible>
29
+ */
30
+ const Collapsible = React.forwardRef<HTMLDivElement, CollapsibleProps>(
31
+ ({ children, open: controlledOpen, onOpenChange, defaultOpen = false, className }, ref) => {
32
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
33
+
34
+ const open = controlledOpen !== undefined ? controlledOpen : uncontrolledOpen
35
+ const setOpen = onOpenChange || setUncontrolledOpen
36
+
37
+ return (
38
+ <CollapsibleContext.Provider value={{ open, onOpenChange: setOpen }}>
39
+ <div ref={ref} className={className} data-state={open ? "open" : "closed"}>
40
+ {children}
41
+ </div>
42
+ </CollapsibleContext.Provider>
43
+ )
44
+ }
45
+ )
46
+ Collapsible.displayName = "Collapsible"
47
+
48
+ interface CollapsibleTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
49
+ asChild?: boolean
50
+ }
51
+
52
+ const CollapsibleTrigger = React.forwardRef<HTMLButtonElement, CollapsibleTriggerProps>(
53
+ ({ className, children, asChild, onClick, ...props }, ref) => {
54
+ const context = React.useContext(CollapsibleContext)
55
+ if (!context) throw new Error("CollapsibleTrigger must be used within Collapsible")
56
+
57
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
58
+ onClick?.(e)
59
+ context.onOpenChange(!context.open)
60
+ }
61
+
62
+ if (asChild && React.isValidElement(children)) {
63
+ return React.cloneElement(children as React.ReactElement<any>, {
64
+ onClick: handleClick,
65
+ "aria-expanded": context.open,
66
+ ref,
67
+ })
68
+ }
69
+
70
+ return (
71
+ <button
72
+ ref={ref}
73
+ type="button"
74
+ aria-expanded={context.open}
75
+ className={className}
76
+ onClick={handleClick}
77
+ {...props}
78
+ >
79
+ {children}
80
+ </button>
81
+ )
82
+ }
83
+ )
84
+ CollapsibleTrigger.displayName = "CollapsibleTrigger"
85
+
86
+ const CollapsibleContent = React.forwardRef<
87
+ HTMLDivElement,
88
+ React.HTMLAttributes<HTMLDivElement>
89
+ >(({ className, children, ...props }, ref) => {
90
+ const context = React.useContext(CollapsibleContext)
91
+ if (!context) throw new Error("CollapsibleContent must be used within Collapsible")
92
+
93
+ return (
94
+ <div
95
+ ref={ref}
96
+ className={cn(
97
+ "overflow-hidden transition-all",
98
+ context.open ? "animate-collapsible-down" : "animate-collapsible-up hidden",
99
+ className
100
+ )}
101
+ data-state={context.open ? "open" : "closed"}
102
+ {...props}
103
+ >
104
+ {children}
105
+ </div>
106
+ )
107
+ })
108
+ CollapsibleContent.displayName = "CollapsibleContent"
109
+
110
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }
@@ -0,0 +1,60 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const containerVariants = cva("mx-auto w-full px-4", {
6
+ variants: {
7
+ size: {
8
+ sm: "max-w-screen-sm",
9
+ md: "max-w-screen-md",
10
+ lg: "max-w-screen-lg",
11
+ xl: "max-w-screen-xl",
12
+ "2xl": "max-w-screen-2xl",
13
+ full: "max-w-full",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ size: "xl",
18
+ },
19
+ })
20
+
21
+ type ContainerVariants = VariantProps<typeof containerVariants>
22
+
23
+ interface ContainerBaseProps extends ContainerVariants {
24
+ className?: string
25
+ children?: React.ReactNode
26
+ }
27
+
28
+ /**
29
+ * Polymorphic Container for max-width layouts
30
+ *
31
+ * @example
32
+ * <Container size="lg">Content</Container>
33
+ *
34
+ * @example
35
+ * <Container as="section" size="md">Section content</Container>
36
+ */
37
+ const Container = React.forwardRef(
38
+ <T extends React.ElementType = "div">(
39
+ {
40
+ as,
41
+ className,
42
+ size,
43
+ ...props
44
+ }: ContainerBaseProps & { as?: T } & Omit<React.ComponentPropsWithoutRef<T>, keyof ContainerBaseProps | "as">,
45
+ ref: React.ForwardedRef<React.ElementRef<T>>
46
+ ) => {
47
+ const Comp = as || "div"
48
+
49
+ return (
50
+ <Comp
51
+ ref={ref as any}
52
+ className={cn(containerVariants({ size, className }))}
53
+ {...props}
54
+ />
55
+ )
56
+ }
57
+ )
58
+ Container.displayName = "Container"
59
+
60
+ export { Container, containerVariants }
@@ -0,0 +1,264 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ interface DialogContextValue {
5
+ open: boolean
6
+ onOpenChange: (open: boolean) => void
7
+ }
8
+
9
+ const DialogContext = React.createContext<DialogContextValue | null>(null)
10
+
11
+ function useDialogContext() {
12
+ const context = React.useContext(DialogContext)
13
+ if (!context) {
14
+ throw new Error("Dialog components must be used within a Dialog")
15
+ }
16
+ return context
17
+ }
18
+
19
+ interface DialogProps {
20
+ children: React.ReactNode
21
+ open?: boolean
22
+ onOpenChange?: (open: boolean) => void
23
+ defaultOpen?: boolean
24
+ }
25
+
26
+ /**
27
+ * Dialog component with focus trap and keyboard handling
28
+ *
29
+ * @example
30
+ * const [open, setOpen] = useState(false)
31
+ *
32
+ * <Dialog open={open} onOpenChange={setOpen}>
33
+ * <DialogTrigger asChild>
34
+ * <Button>Open Dialog</Button>
35
+ * </DialogTrigger>
36
+ * <DialogContent>
37
+ * <DialogHeader>
38
+ * <DialogTitle>Title</DialogTitle>
39
+ * <DialogDescription>Description</DialogDescription>
40
+ * </DialogHeader>
41
+ * <div>Content</div>
42
+ * <DialogFooter>
43
+ * <Button>Save</Button>
44
+ * </DialogFooter>
45
+ * </DialogContent>
46
+ * </Dialog>
47
+ */
48
+ function Dialog({ children, open: controlledOpen, onOpenChange, defaultOpen = false }: DialogProps) {
49
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
50
+
51
+ const open = controlledOpen !== undefined ? controlledOpen : uncontrolledOpen
52
+ const setOpen = onOpenChange || setUncontrolledOpen
53
+
54
+ return (
55
+ <DialogContext.Provider value={{ open, onOpenChange: setOpen }}>
56
+ {children}
57
+ </DialogContext.Provider>
58
+ )
59
+ }
60
+
61
+ interface DialogTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
62
+ asChild?: boolean
63
+ }
64
+
65
+ const DialogTrigger = React.forwardRef<HTMLButtonElement, DialogTriggerProps>(
66
+ ({ onClick, asChild, children, ...props }, ref) => {
67
+ const { onOpenChange } = useDialogContext()
68
+
69
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
70
+ onClick?.(e)
71
+ onOpenChange(true)
72
+ }
73
+
74
+ if (asChild && React.isValidElement(children)) {
75
+ return React.cloneElement(children as React.ReactElement<any>, {
76
+ onClick: handleClick,
77
+ ref,
78
+ })
79
+ }
80
+
81
+ return (
82
+ <button ref={ref} onClick={handleClick} {...props}>
83
+ {children}
84
+ </button>
85
+ )
86
+ }
87
+ )
88
+ DialogTrigger.displayName = "DialogTrigger"
89
+
90
+ const DialogPortal = ({ children }: { children: React.ReactNode }) => {
91
+ const { open } = useDialogContext()
92
+
93
+ if (!open) return null
94
+
95
+ return <>{children}</>
96
+ }
97
+
98
+ const DialogOverlay = React.forwardRef<
99
+ HTMLDivElement,
100
+ React.HTMLAttributes<HTMLDivElement>
101
+ >(({ className, ...props }, ref) => (
102
+ <div
103
+ ref={ref}
104
+ className={cn(
105
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
106
+ className
107
+ )}
108
+ {...props}
109
+ />
110
+ ))
111
+ DialogOverlay.displayName = "DialogOverlay"
112
+
113
+ const DialogContent = React.forwardRef<
114
+ HTMLDivElement,
115
+ React.HTMLAttributes<HTMLDivElement>
116
+ >(({ className, children, ...props }, ref) => {
117
+ const { open, onOpenChange } = useDialogContext()
118
+ const contentRef = React.useRef<HTMLDivElement>(null)
119
+
120
+ // Handle escape key
121
+ React.useEffect(() => {
122
+ const handleEscape = (e: KeyboardEvent) => {
123
+ if (e.key === "Escape") {
124
+ onOpenChange(false)
125
+ }
126
+ }
127
+
128
+ if (open) {
129
+ document.addEventListener("keydown", handleEscape)
130
+ document.body.style.overflow = "hidden"
131
+ }
132
+
133
+ return () => {
134
+ document.removeEventListener("keydown", handleEscape)
135
+ document.body.style.overflow = ""
136
+ }
137
+ }, [open, onOpenChange])
138
+
139
+ // Focus trap
140
+ React.useEffect(() => {
141
+ if (open && contentRef.current) {
142
+ const focusableElements = contentRef.current.querySelectorAll(
143
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
144
+ )
145
+ const firstElement = focusableElements[0] as HTMLElement
146
+ const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement
147
+
148
+ const handleTab = (e: KeyboardEvent) => {
149
+ if (e.key === "Tab") {
150
+ if (e.shiftKey && document.activeElement === firstElement) {
151
+ e.preventDefault()
152
+ lastElement?.focus()
153
+ } else if (!e.shiftKey && document.activeElement === lastElement) {
154
+ e.preventDefault()
155
+ firstElement?.focus()
156
+ }
157
+ }
158
+ }
159
+
160
+ document.addEventListener("keydown", handleTab)
161
+ firstElement?.focus()
162
+
163
+ return () => document.removeEventListener("keydown", handleTab)
164
+ }
165
+ }, [open])
166
+
167
+ if (!open) return null
168
+
169
+ return (
170
+ <DialogPortal>
171
+ <DialogOverlay onClick={() => onOpenChange(false)} />
172
+ <div
173
+ ref={(node) => {
174
+ (contentRef as React.MutableRefObject<HTMLDivElement | null>).current = node
175
+ if (typeof ref === "function") ref(node)
176
+ else if (ref) ref.current = node
177
+ }}
178
+ role="dialog"
179
+ aria-modal="true"
180
+ className={cn(
181
+ "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",
182
+ className
183
+ )}
184
+ {...props}
185
+ >
186
+ {children}
187
+ <button
188
+ className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none"
189
+ onClick={() => onOpenChange(false)}
190
+ >
191
+ <svg
192
+ className="h-4 w-4"
193
+ fill="none"
194
+ viewBox="0 0 24 24"
195
+ stroke="currentColor"
196
+ strokeWidth={2}
197
+ >
198
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
199
+ </svg>
200
+ <span className="sr-only">Close</span>
201
+ </button>
202
+ </div>
203
+ </DialogPortal>
204
+ )
205
+ })
206
+ DialogContent.displayName = "DialogContent"
207
+
208
+ const DialogHeader = ({
209
+ className,
210
+ ...props
211
+ }: React.HTMLAttributes<HTMLDivElement>) => (
212
+ <div
213
+ className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)}
214
+ {...props}
215
+ />
216
+ )
217
+ DialogHeader.displayName = "DialogHeader"
218
+
219
+ const DialogFooter = ({
220
+ className,
221
+ ...props
222
+ }: React.HTMLAttributes<HTMLDivElement>) => (
223
+ <div
224
+ className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
225
+ {...props}
226
+ />
227
+ )
228
+ DialogFooter.displayName = "DialogFooter"
229
+
230
+ const DialogTitle = React.forwardRef<
231
+ HTMLHeadingElement,
232
+ React.HTMLAttributes<HTMLHeadingElement>
233
+ >(({ className, ...props }, ref) => (
234
+ <h2
235
+ ref={ref}
236
+ className={cn("text-lg font-semibold leading-none tracking-tight", className)}
237
+ {...props}
238
+ />
239
+ ))
240
+ DialogTitle.displayName = "DialogTitle"
241
+
242
+ const DialogDescription = React.forwardRef<
243
+ HTMLParagraphElement,
244
+ React.HTMLAttributes<HTMLParagraphElement>
245
+ >(({ className, ...props }, ref) => (
246
+ <p
247
+ ref={ref}
248
+ className={cn("text-sm text-muted-foreground", className)}
249
+ {...props}
250
+ />
251
+ ))
252
+ DialogDescription.displayName = "DialogDescription"
253
+
254
+ export {
255
+ Dialog,
256
+ DialogPortal,
257
+ DialogOverlay,
258
+ DialogTrigger,
259
+ DialogContent,
260
+ DialogHeader,
261
+ DialogFooter,
262
+ DialogTitle,
263
+ DialogDescription,
264
+ }