@moontra/moonui-pro 2.4.6 → 2.5.0

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.
@@ -0,0 +1,343 @@
1
+ "use client"
2
+
3
+ import React from 'react'
4
+ import { motion } from 'framer-motion'
5
+ import { Card } from '../../ui/card'
6
+ import { cn } from '../../../lib/utils'
7
+ import {
8
+ TrendingUp,
9
+ TrendingDown,
10
+ Minus,
11
+ ArrowUpRight,
12
+ ArrowDownRight,
13
+ MoreVertical,
14
+ Maximize2,
15
+ Download,
16
+ Share2
17
+ } from 'lucide-react'
18
+ import { MetricData } from '../types'
19
+ import { Button } from '../../ui/button'
20
+ import {
21
+ DropdownMenu,
22
+ DropdownMenuContent,
23
+ DropdownMenuItem,
24
+ DropdownMenuTrigger,
25
+ } from '../../ui/dropdown-menu'
26
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip'
27
+
28
+ interface MetricCardProps {
29
+ data: MetricData
30
+ onClick?: () => void
31
+ onAction?: (action: string) => void
32
+ className?: string
33
+ showSparkline?: boolean
34
+ showForecast?: boolean
35
+ interactive?: boolean
36
+ glassmorphism?: boolean
37
+ }
38
+
39
+ export function MetricCard({
40
+ data,
41
+ onClick,
42
+ onAction,
43
+ className,
44
+ showSparkline = true,
45
+ showForecast = false,
46
+ interactive = true,
47
+ glassmorphism = false
48
+ }: MetricCardProps) {
49
+ const [isHovered, setIsHovered] = React.useState(false)
50
+
51
+ // Renk sınıfları
52
+ const colorClasses = {
53
+ primary: 'text-blue-600 dark:text-blue-400',
54
+ success: 'text-green-600 dark:text-green-400',
55
+ warning: 'text-yellow-600 dark:text-yellow-400',
56
+ danger: 'text-red-600 dark:text-red-400',
57
+ info: 'text-purple-600 dark:text-purple-400'
58
+ }
59
+
60
+ // Değişim renkleri
61
+ const getChangeColor = (type: 'increase' | 'decrease' | 'neutral') => {
62
+ switch (type) {
63
+ case 'increase':
64
+ return 'text-green-600 dark:text-green-400'
65
+ case 'decrease':
66
+ return 'text-red-600 dark:text-red-400'
67
+ default:
68
+ return 'text-gray-600 dark:text-gray-400'
69
+ }
70
+ }
71
+
72
+ // Değişim ikonu
73
+ const getChangeIcon = (type: 'increase' | 'decrease' | 'neutral') => {
74
+ switch (type) {
75
+ case 'increase':
76
+ return <ArrowUpRight className="h-4 w-4" />
77
+ case 'decrease':
78
+ return <ArrowDownRight className="h-4 w-4" />
79
+ default:
80
+ return <Minus className="h-4 w-4" />
81
+ }
82
+ }
83
+
84
+ // Sparkline çizimi
85
+ const renderSparkline = () => {
86
+ if (!data.sparkline || data.sparkline.length < 2) return null
87
+
88
+ const max = Math.max(...data.sparkline)
89
+ const min = Math.min(...data.sparkline)
90
+ const range = max - min || 1
91
+
92
+ const points = data.sparkline
93
+ .map((value, index) => {
94
+ const x = (index / (data.sparkline!.length - 1)) * 100
95
+ const y = 100 - ((value - min) / range) * 100
96
+ return `${x},${y}`
97
+ })
98
+ .join(' ')
99
+
100
+ const gradientId = `gradient-${data.id}`
101
+
102
+ return (
103
+ <motion.div
104
+ className="absolute bottom-0 left-0 right-0 h-16 overflow-hidden rounded-b-lg opacity-20"
105
+ initial={{ opacity: 0 }}
106
+ animate={{ opacity: isHovered ? 0.3 : 0.2 }}
107
+ transition={{ duration: 0.2 }}
108
+ >
109
+ <svg
110
+ className="w-full h-full"
111
+ viewBox="0 0 100 100"
112
+ preserveAspectRatio="none"
113
+ >
114
+ <defs>
115
+ <linearGradient id={gradientId} x1="0%" y1="0%" x2="0%" y2="100%">
116
+ <stop offset="0%" stopColor="currentColor" stopOpacity="0.3" />
117
+ <stop offset="100%" stopColor="currentColor" stopOpacity="0" />
118
+ </linearGradient>
119
+ </defs>
120
+ <polyline
121
+ fill="none"
122
+ stroke="currentColor"
123
+ strokeWidth="2"
124
+ points={points}
125
+ className={colorClasses[data.color || 'primary']}
126
+ />
127
+ <polygon
128
+ fill={`url(#${gradientId})`}
129
+ points={`0,100 ${points} 100,100`}
130
+ className={colorClasses[data.color || 'primary']}
131
+ />
132
+ </svg>
133
+ </motion.div>
134
+ )
135
+ }
136
+
137
+ // İlerleme barı (hedef varsa)
138
+ const renderProgress = () => {
139
+ if (!data.target) return null
140
+
141
+ const currentValue = typeof data.value === 'number' ? data.value : parseFloat(data.value as string)
142
+ const progress = (currentValue / data.target) * 100
143
+
144
+ return (
145
+ <motion.div
146
+ className="mt-3"
147
+ initial={{ opacity: 0, y: 10 }}
148
+ animate={{ opacity: 1, y: 0 }}
149
+ transition={{ delay: 0.1 }}
150
+ >
151
+ <div className="flex justify-between text-xs text-muted-foreground mb-1">
152
+ <span>Progress to target</span>
153
+ <span>{progress.toFixed(1)}%</span>
154
+ </div>
155
+ <div className="h-1.5 bg-muted rounded-full overflow-hidden">
156
+ <motion.div
157
+ className={cn(
158
+ "h-full rounded-full",
159
+ progress >= 100 ? 'bg-green-500' : 'bg-primary'
160
+ )}
161
+ initial={{ width: 0 }}
162
+ animate={{ width: `${Math.min(progress, 100)}%` }}
163
+ transition={{ duration: 0.5, ease: "easeOut" }}
164
+ />
165
+ </div>
166
+ <div className="flex justify-between text-xs text-muted-foreground mt-1">
167
+ <span>{currentValue} {data.unit}</span>
168
+ <span>Target: {data.target} {data.unit}</span>
169
+ </div>
170
+ </motion.div>
171
+ )
172
+ }
173
+
174
+ // Değer formatı
175
+ const formatValue = (value: string | number): string => {
176
+ if (typeof value === 'number') {
177
+ if (value >= 1000000) {
178
+ return (value / 1000000).toFixed(1) + 'M'
179
+ } else if (value >= 1000) {
180
+ return (value / 1000).toFixed(1) + 'K'
181
+ }
182
+ return value.toLocaleString()
183
+ }
184
+ return value.toString()
185
+ }
186
+
187
+ // Card variants
188
+ const cardVariants = {
189
+ initial: { opacity: 0, y: 20 },
190
+ animate: {
191
+ opacity: 1,
192
+ y: 0,
193
+ transition: { duration: 0.3, ease: "easeOut" }
194
+ },
195
+ hover: interactive ? {
196
+ y: -2,
197
+ transition: { duration: 0.2 }
198
+ } : {}
199
+ }
200
+
201
+ return (
202
+ <motion.div
203
+ variants={cardVariants}
204
+ initial="initial"
205
+ animate="animate"
206
+ whileHover="hover"
207
+ onHoverStart={() => setIsHovered(true)}
208
+ onHoverEnd={() => setIsHovered(false)}
209
+ >
210
+ <Card
211
+ className={cn(
212
+ "relative overflow-hidden cursor-pointer transition-all duration-300",
213
+ interactive && "hover:shadow-lg hover:shadow-primary/5",
214
+ glassmorphism && "bg-background/60 backdrop-blur-md border-white/10",
215
+ className
216
+ )}
217
+ onClick={onClick}
218
+ >
219
+ {/* Açık arka plan efekti */}
220
+ {glassmorphism && (
221
+ <div className="absolute inset-0 bg-gradient-to-br from-primary/5 to-transparent" />
222
+ )}
223
+
224
+ {/* Üst bar */}
225
+ <div className="flex items-center justify-between p-4 pb-2">
226
+ <div className="flex items-center gap-3">
227
+ <motion.div
228
+ className={cn(
229
+ "p-2 rounded-lg",
230
+ glassmorphism ? "bg-white/10 backdrop-blur" : "bg-muted"
231
+ )}
232
+ whileHover={{ scale: 1.05 }}
233
+ whileTap={{ scale: 0.95 }}
234
+ >
235
+ <div className={cn("h-5 w-5", colorClasses[data.color || 'primary'])}>
236
+ {data.icon}
237
+ </div>
238
+ </motion.div>
239
+ <div>
240
+ <h3 className="text-sm font-medium text-muted-foreground">{data.title}</h3>
241
+ </div>
242
+ </div>
243
+
244
+ {/* Aksiyonlar */}
245
+ {interactive && (
246
+ <DropdownMenu>
247
+ <DropdownMenuTrigger asChild>
248
+ <Button
249
+ variant="ghost"
250
+ size="sm"
251
+ className="h-8 w-8 p-0"
252
+ onClick={(e) => e.stopPropagation()}
253
+ >
254
+ <MoreVertical className="h-4 w-4" />
255
+ </Button>
256
+ </DropdownMenuTrigger>
257
+ <DropdownMenuContent align="end">
258
+ <DropdownMenuItem onClick={() => onAction?.('fullscreen')}>
259
+ <Maximize2 className="mr-2 h-4 w-4" />
260
+ Fullscreen
261
+ </DropdownMenuItem>
262
+ <DropdownMenuItem onClick={() => onAction?.('export')}>
263
+ <Download className="mr-2 h-4 w-4" />
264
+ Export Data
265
+ </DropdownMenuItem>
266
+ <DropdownMenuItem onClick={() => onAction?.('share')}>
267
+ <Share2 className="mr-2 h-4 w-4" />
268
+ Share
269
+ </DropdownMenuItem>
270
+ </DropdownMenuContent>
271
+ </DropdownMenu>
272
+ )}
273
+ </div>
274
+
275
+ {/* Ana değer */}
276
+ <div className="px-4 pb-4">
277
+ <TooltipProvider>
278
+ <Tooltip>
279
+ <TooltipTrigger asChild>
280
+ <motion.div
281
+ className="text-3xl font-bold tracking-tight"
282
+ animate={{
283
+ scale: isHovered ? 1.02 : 1,
284
+ }}
285
+ transition={{ duration: 0.2 }}
286
+ >
287
+ {formatValue(data.value)}
288
+ {data.unit && <span className="text-lg ml-1 text-muted-foreground">{data.unit}</span>}
289
+ </motion.div>
290
+ </TooltipTrigger>
291
+ <TooltipContent>
292
+ <p>Exact value: {data.value} {data.unit}</p>
293
+ </TooltipContent>
294
+ </Tooltip>
295
+ </TooltipProvider>
296
+
297
+ {/* Değişim göstergesi */}
298
+ {data.change && (
299
+ <motion.div
300
+ className={cn(
301
+ "flex items-center gap-1 text-sm mt-2",
302
+ getChangeColor(data.change.type)
303
+ )}
304
+ initial={{ opacity: 0, x: -10 }}
305
+ animate={{ opacity: 1, x: 0 }}
306
+ transition={{ delay: 0.1 }}
307
+ >
308
+ {getChangeIcon(data.change.type)}
309
+ <span className="font-medium">
310
+ {data.change.type === 'neutral' ? '' : data.change.type === 'increase' ? '+' : '-'}
311
+ {Math.abs(data.change.value)}%
312
+ </span>
313
+ <span className="text-muted-foreground">
314
+ from {data.change.period}
315
+ </span>
316
+ </motion.div>
317
+ )}
318
+
319
+ {/* Tahmin */}
320
+ {showForecast && data.forecast && (
321
+ <motion.div
322
+ className="flex items-center gap-1 text-xs text-muted-foreground mt-1"
323
+ initial={{ opacity: 0 }}
324
+ animate={{ opacity: 1 }}
325
+ transition={{ delay: 0.2 }}
326
+ >
327
+ <TrendingUp className="h-3 w-3" />
328
+ <span>Forecast: {formatValue(data.forecast)} {data.unit}</span>
329
+ </motion.div>
330
+ )}
331
+
332
+ {/* İlerleme barı */}
333
+ {renderProgress()}
334
+ </div>
335
+
336
+ {/* Sparkline */}
337
+ {showSparkline && renderSparkline()}
338
+ </Card>
339
+ </motion.div>
340
+ )
341
+ }
342
+
343
+ export default MetricCard
@@ -24,8 +24,8 @@ export * from "./pinch-zoom"
24
24
  // Spotlight Card
25
25
  export * from "./spotlight-card"
26
26
 
27
- // Calendar
28
- export * from "./calendar"
27
+ // Calendar component (Advanced version with events)
28
+ export { Calendar as AdvancedCalendar } from "./calendar"
29
29
 
30
30
  // Kanban
31
31
  export * from "./kanban"
@@ -0,0 +1,65 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ChevronLeft, ChevronRight } from "lucide-react"
5
+ import { DayPicker } from "react-day-picker"
6
+ import { cn } from "../../lib/utils"
7
+ import { buttonVariants } from "./button"
8
+
9
+ export type CalendarProps = React.ComponentProps<typeof DayPicker>
10
+
11
+ function Calendar({
12
+ className,
13
+ classNames,
14
+ showOutsideDays = true,
15
+ ...props
16
+ }: CalendarProps) {
17
+ return (
18
+ <DayPicker
19
+ showOutsideDays={showOutsideDays}
20
+ className={cn("p-3", className)}
21
+ classNames={{
22
+ months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
23
+ month: "space-y-4",
24
+ caption: "flex justify-center pt-1 relative items-center",
25
+ caption_label: "text-sm font-medium",
26
+ nav: "space-x-1 flex items-center",
27
+ nav_button: cn(
28
+ buttonVariants({ variant: "outline" }),
29
+ "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
30
+ ),
31
+ nav_button_previous: "absolute left-1",
32
+ nav_button_next: "absolute right-1",
33
+ table: "w-full border-collapse space-y-1",
34
+ head_row: "flex",
35
+ head_cell:
36
+ "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
37
+ row: "flex w-full mt-2",
38
+ cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
39
+ day: cn(
40
+ buttonVariants({ variant: "ghost" }),
41
+ "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
42
+ ),
43
+ day_range_end: "day-range-end",
44
+ day_selected:
45
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
46
+ day_today: "bg-accent text-accent-foreground",
47
+ day_outside:
48
+ "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
49
+ day_disabled: "text-muted-foreground opacity-50",
50
+ day_range_middle:
51
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
52
+ day_hidden: "invisible",
53
+ ...classNames,
54
+ }}
55
+ components={{
56
+ // IconLeft: () => <ChevronLeft className="h-4 w-4" />,
57
+ // IconRight: () => <ChevronRight className="h-4 w-4" />,
58
+ }}
59
+ {...props}
60
+ />
61
+ )
62
+ }
63
+ Calendar.displayName = "Calendar"
64
+
65
+ export { Calendar }
@@ -34,6 +34,10 @@ export {
34
34
  Button, buttonVariants
35
35
  } from './button';
36
36
 
37
+ export {
38
+ Calendar
39
+ } from './calendar';
40
+
37
41
  export {
38
42
  MoonUICardPro, MoonUICardHeaderPro, MoonUICardFooterPro, MoonUICardTitlePro, MoonUICardDescriptionPro, MoonUICardContentPro,
39
43
  Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent
@@ -99,6 +103,10 @@ export {
99
103
  radioGroupItemVariants, RadioGroupContext, RadioGroup, RadioGroupItem, RadioLabel, RadioItemWithLabel
100
104
  } from './radio-group';
101
105
 
106
+ export {
107
+ ScrollArea, ScrollBar
108
+ } from './scroll-area';
109
+
102
110
  export {
103
111
  MoonUISelectPro, MoonUISelectTriggerPro, MoonUISelectContentPro, MoonUISelectItemPro, MoonUISelectValuePro, MoonUISelectGroupPro, MoonUISelectLabelPro, MoonUISelectSeparatorPro,
104
112
  Select, SelectTrigger, SelectContent, SelectItem, SelectValue, SelectGroup, SelectLabel, SelectSeparator
@@ -109,6 +117,10 @@ export {
109
117
  Separator, separatorVariants
110
118
  } from './separator';
111
119
 
120
+ export {
121
+ Sheet, SheetTrigger, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription, SheetClose, SheetPortal, SheetOverlay
122
+ } from './sheet';
123
+
112
124
  export {
113
125
  MoonUISkeletonPro,
114
126
  Skeleton
@@ -0,0 +1,47 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5
+ import { cn } from "../../lib/utils"
6
+
7
+ const ScrollArea = React.forwardRef<
8
+ React.ElementRef<typeof ScrollAreaPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
10
+ >(({ className, children, ...props }, ref) => (
11
+ <ScrollAreaPrimitive.Root
12
+ ref={ref}
13
+ className={cn("relative overflow-hidden", className)}
14
+ {...props}
15
+ >
16
+ <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
17
+ {children}
18
+ </ScrollAreaPrimitive.Viewport>
19
+ <ScrollBar />
20
+ <ScrollAreaPrimitive.Corner />
21
+ </ScrollAreaPrimitive.Root>
22
+ ))
23
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
24
+
25
+ const ScrollBar = React.forwardRef<
26
+ React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
27
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
28
+ >(({ className, orientation = "vertical", ...props }, ref) => (
29
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
30
+ ref={ref}
31
+ orientation={orientation}
32
+ className={cn(
33
+ "flex touch-none select-none transition-colors",
34
+ orientation === "vertical" &&
35
+ "h-full w-2.5 border-l border-l-transparent p-[1px]",
36
+ orientation === "horizontal" &&
37
+ "h-2.5 flex-col border-t border-t-transparent p-[1px]",
38
+ className
39
+ )}
40
+ {...props}
41
+ >
42
+ <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
43
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
44
+ ))
45
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
46
+
47
+ export { ScrollArea, ScrollBar }
@@ -0,0 +1,139 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SheetPrimitive from "@radix-ui/react-dialog"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+ import { X } from "lucide-react"
7
+ import { cn } from "../../lib/utils"
8
+
9
+ const Sheet = SheetPrimitive.Root
10
+
11
+ const SheetTrigger = SheetPrimitive.Trigger
12
+
13
+ const SheetClose = SheetPrimitive.Close
14
+
15
+ const SheetPortal = SheetPrimitive.Portal
16
+
17
+ const SheetOverlay = React.forwardRef<
18
+ React.ElementRef<typeof SheetPrimitive.Overlay>,
19
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
20
+ >(({ className, ...props }, ref) => (
21
+ <SheetPrimitive.Overlay
22
+ className={cn(
23
+ "fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
24
+ className
25
+ )}
26
+ {...props}
27
+ ref={ref}
28
+ />
29
+ ))
30
+ SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
31
+
32
+ const sheetVariants = cva(
33
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
34
+ {
35
+ variants: {
36
+ side: {
37
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
38
+ bottom:
39
+ "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
40
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
41
+ right:
42
+ "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
43
+ },
44
+ },
45
+ defaultVariants: {
46
+ side: "right",
47
+ },
48
+ }
49
+ )
50
+
51
+ interface SheetContentProps
52
+ extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
53
+ VariantProps<typeof sheetVariants> {}
54
+
55
+ const SheetContent = React.forwardRef<
56
+ React.ElementRef<typeof SheetPrimitive.Content>,
57
+ SheetContentProps
58
+ >(({ side = "right", className, children, ...props }, ref) => (
59
+ <SheetPortal>
60
+ <SheetOverlay />
61
+ <SheetPrimitive.Content
62
+ ref={ref}
63
+ className={cn(sheetVariants({ side }), className)}
64
+ {...props}
65
+ >
66
+ {children}
67
+ <SheetPrimitive.Close 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 data-[state=open]:bg-secondary">
68
+ <X className="h-4 w-4" />
69
+ <span className="sr-only">Close</span>
70
+ </SheetPrimitive.Close>
71
+ </SheetPrimitive.Content>
72
+ </SheetPortal>
73
+ ))
74
+ SheetContent.displayName = SheetPrimitive.Content.displayName
75
+
76
+ const SheetHeader = ({
77
+ className,
78
+ ...props
79
+ }: React.HTMLAttributes<HTMLDivElement>) => (
80
+ <div
81
+ className={cn(
82
+ "flex flex-col space-y-2 text-center sm:text-left",
83
+ className
84
+ )}
85
+ {...props}
86
+ />
87
+ )
88
+ SheetHeader.displayName = "SheetHeader"
89
+
90
+ const SheetFooter = ({
91
+ className,
92
+ ...props
93
+ }: React.HTMLAttributes<HTMLDivElement>) => (
94
+ <div
95
+ className={cn(
96
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
97
+ className
98
+ )}
99
+ {...props}
100
+ />
101
+ )
102
+ SheetFooter.displayName = "SheetFooter"
103
+
104
+ const SheetTitle = React.forwardRef<
105
+ React.ElementRef<typeof SheetPrimitive.Title>,
106
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
107
+ >(({ className, ...props }, ref) => (
108
+ <SheetPrimitive.Title
109
+ ref={ref}
110
+ className={cn("text-lg font-semibold text-foreground", className)}
111
+ {...props}
112
+ />
113
+ ))
114
+ SheetTitle.displayName = SheetPrimitive.Title.displayName
115
+
116
+ const SheetDescription = React.forwardRef<
117
+ React.ElementRef<typeof SheetPrimitive.Description>,
118
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
119
+ >(({ className, ...props }, ref) => (
120
+ <SheetPrimitive.Description
121
+ ref={ref}
122
+ className={cn("text-sm text-muted-foreground", className)}
123
+ {...props}
124
+ />
125
+ ))
126
+ SheetDescription.displayName = SheetPrimitive.Description.displayName
127
+
128
+ export {
129
+ Sheet,
130
+ SheetPortal,
131
+ SheetOverlay,
132
+ SheetTrigger,
133
+ SheetClose,
134
+ SheetContent,
135
+ SheetHeader,
136
+ SheetFooter,
137
+ SheetTitle,
138
+ SheetDescription,
139
+ }