@djangocfg/ui-nextjs 1.4.45

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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/package.json +110 -0
  4. package/src/animations/AnimatedBackground.tsx +645 -0
  5. package/src/animations/index.ts +2 -0
  6. package/src/blocks/ArticleCard.tsx +94 -0
  7. package/src/blocks/ArticleList.tsx +95 -0
  8. package/src/blocks/CTASection.tsx +136 -0
  9. package/src/blocks/FeatureSection.tsx +104 -0
  10. package/src/blocks/Hero.tsx +102 -0
  11. package/src/blocks/NewsletterSection.tsx +119 -0
  12. package/src/blocks/StatsSection.tsx +103 -0
  13. package/src/blocks/SuperHero.tsx +328 -0
  14. package/src/blocks/TestimonialSection.tsx +122 -0
  15. package/src/blocks/index.ts +9 -0
  16. package/src/components/README.md +2018 -0
  17. package/src/components/breadcrumb-navigation.tsx +127 -0
  18. package/src/components/breadcrumb.tsx +132 -0
  19. package/src/components/button-download.tsx +275 -0
  20. package/src/components/dropdown-menu.tsx +219 -0
  21. package/src/components/index.ts +86 -0
  22. package/src/components/markdown/MarkdownMessage.tsx +338 -0
  23. package/src/components/markdown/index.ts +5 -0
  24. package/src/components/menubar.tsx +274 -0
  25. package/src/components/multi-select-pro/async.tsx +608 -0
  26. package/src/components/multi-select-pro/helpers.tsx +84 -0
  27. package/src/components/multi-select-pro/index.tsx +622 -0
  28. package/src/components/navigation-menu.tsx +153 -0
  29. package/src/components/pagination-static.tsx +348 -0
  30. package/src/components/pagination.tsx +138 -0
  31. package/src/components/phone-input.tsx +276 -0
  32. package/src/components/sidebar.tsx +866 -0
  33. package/src/components/sonner.tsx +31 -0
  34. package/src/components/ssr-pagination.tsx +237 -0
  35. package/src/hooks/index.ts +19 -0
  36. package/src/hooks/useCfgRouter.ts +153 -0
  37. package/src/hooks/useLocalStorage.ts +221 -0
  38. package/src/hooks/useQueryParams.ts +73 -0
  39. package/src/hooks/useSessionStorage.ts +188 -0
  40. package/src/hooks/useTheme.ts +57 -0
  41. package/src/index.ts +24 -0
  42. package/src/lib/index.ts +2 -0
  43. package/src/styles/index.css +2 -0
  44. package/src/theme/ForceTheme.tsx +115 -0
  45. package/src/theme/ThemeProvider.tsx +82 -0
  46. package/src/theme/ThemeToggle.tsx +52 -0
  47. package/src/theme/index.ts +3 -0
  48. package/src/tools/JsonForm/JsonSchemaForm.tsx +199 -0
  49. package/src/tools/JsonForm/examples/BotConfigExample.tsx +245 -0
  50. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +157 -0
  51. package/src/tools/JsonForm/index.ts +46 -0
  52. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +46 -0
  53. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +73 -0
  54. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +106 -0
  55. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +34 -0
  56. package/src/tools/JsonForm/templates/FieldTemplate.tsx +61 -0
  57. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +43 -0
  58. package/src/tools/JsonForm/templates/index.ts +12 -0
  59. package/src/tools/JsonForm/types.ts +83 -0
  60. package/src/tools/JsonForm/utils.ts +212 -0
  61. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +36 -0
  62. package/src/tools/JsonForm/widgets/NumberWidget.tsx +88 -0
  63. package/src/tools/JsonForm/widgets/SelectWidget.tsx +100 -0
  64. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +34 -0
  65. package/src/tools/JsonForm/widgets/TextWidget.tsx +95 -0
  66. package/src/tools/JsonForm/widgets/index.ts +12 -0
  67. package/src/tools/JsonTree/index.tsx +252 -0
  68. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +212 -0
  69. package/src/tools/LottiePlayer/index.tsx +54 -0
  70. package/src/tools/LottiePlayer/types.ts +108 -0
  71. package/src/tools/LottiePlayer/useLottie.ts +163 -0
  72. package/src/tools/Mermaid/Mermaid.client.tsx +341 -0
  73. package/src/tools/Mermaid/index.tsx +40 -0
  74. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +144 -0
  75. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +255 -0
  76. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +123 -0
  77. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +98 -0
  78. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +164 -0
  79. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
  80. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +169 -0
  81. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +64 -0
  82. package/src/tools/OpenapiViewer/components/index.ts +14 -0
  83. package/src/tools/OpenapiViewer/constants.ts +39 -0
  84. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +338 -0
  85. package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
  86. package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
  87. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +203 -0
  88. package/src/tools/OpenapiViewer/index.tsx +36 -0
  89. package/src/tools/OpenapiViewer/types.ts +152 -0
  90. package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
  91. package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
  92. package/src/tools/OpenapiViewer/utils/index.ts +9 -0
  93. package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
  94. package/src/tools/PrettyCode/PrettyCode.client.tsx +217 -0
  95. package/src/tools/PrettyCode/index.tsx +43 -0
  96. package/src/tools/VideoPlayer/README.md +239 -0
  97. package/src/tools/VideoPlayer/VideoControls.tsx +138 -0
  98. package/src/tools/VideoPlayer/VideoPlayer.tsx +230 -0
  99. package/src/tools/VideoPlayer/index.ts +9 -0
  100. package/src/tools/VideoPlayer/types.ts +62 -0
  101. package/src/tools/index.ts +43 -0
@@ -0,0 +1,127 @@
1
+ import React from 'react';
2
+ import Link from 'next/link';
3
+ import {
4
+ Breadcrumb,
5
+ BreadcrumbList,
6
+ BreadcrumbItem,
7
+ BreadcrumbLink,
8
+ BreadcrumbPage,
9
+ BreadcrumbSeparator,
10
+ BreadcrumbEllipsis,
11
+ } from './breadcrumb';
12
+ import { cn } from '@djangocfg/ui-core/lib';
13
+
14
+ export interface BreadcrumbItem {
15
+ /** Display text for the breadcrumb */
16
+ label: string;
17
+ /** URL to navigate to. If not provided, item will be rendered as current page */
18
+ href?: string;
19
+ /** Whether this item is the current page (will be rendered as BreadcrumbPage) */
20
+ isCurrentPage?: boolean;
21
+ /** Optional icon to display before the label */
22
+ icon?: React.ReactNode;
23
+ }
24
+
25
+ export interface BreadcrumbNavigationProps {
26
+ /** Array of breadcrumb items */
27
+ items: BreadcrumbItem[];
28
+ /** Custom separator between items */
29
+ separator?: React.ReactNode;
30
+ /** Maximum number of items to show before collapsing with ellipsis */
31
+ maxItems?: number;
32
+ /** Custom className for the breadcrumb container */
33
+ className?: string;
34
+ /** Whether to use Next.js Link component for navigation */
35
+ useNextLink?: boolean;
36
+ }
37
+
38
+ export const BreadcrumbNavigation: React.FC<BreadcrumbNavigationProps> = ({
39
+ items,
40
+ separator,
41
+ maxItems = 5,
42
+ className,
43
+ useNextLink = true,
44
+ }) => {
45
+ // Handle empty or single item cases
46
+ if (!items || items.length === 0) {
47
+ return null;
48
+ }
49
+
50
+ // Determine which items to show based on maxItems
51
+ const shouldCollapse = items.length > maxItems;
52
+ let displayItems: BreadcrumbItem[] = items;
53
+
54
+ if (shouldCollapse && items.length > 0) {
55
+ // Show first item, ellipsis, and last few items
56
+ if(items.length > 0) {
57
+ const firstItem = items[0]!;
58
+ const lastItems = items.slice(-(maxItems - 2)).filter(Boolean);
59
+ displayItems = [firstItem, ...lastItems];
60
+ }
61
+ }
62
+
63
+ const renderBreadcrumbItem = (item: BreadcrumbItem, index: number, isLast: boolean) => {
64
+ const isCurrentPage = item.isCurrentPage || isLast || !item.href;
65
+
66
+ const content = (
67
+ <>
68
+ {item.icon && <span className="mr-1">{item.icon}</span>}
69
+ {item.label}
70
+ </>
71
+ );
72
+
73
+ if (isCurrentPage) {
74
+ return (
75
+ <BreadcrumbItem key={`${item.label}-${index}`}>
76
+ <BreadcrumbPage>{content}</BreadcrumbPage>
77
+ </BreadcrumbItem>
78
+ );
79
+ }
80
+
81
+ return (
82
+ <BreadcrumbItem key={`${item.label}-${index}`}>
83
+ <BreadcrumbLink asChild={useNextLink}>
84
+ {useNextLink ? (
85
+ <Link href={item.href!}>{content}</Link>
86
+ ) : (
87
+ <a href={item.href!}>{content}</a>
88
+ )}
89
+ </BreadcrumbLink>
90
+ </BreadcrumbItem>
91
+ );
92
+ };
93
+
94
+ return (
95
+ <Breadcrumb className={className}>
96
+ <BreadcrumbList>
97
+ {displayItems.map((item, index) => {
98
+ const isLast = index === displayItems.length - 1;
99
+ const isFirst = index === 0;
100
+
101
+ // Show ellipsis after first item if we collapsed items
102
+ if (shouldCollapse && isFirst && displayItems.length > 2) {
103
+ return (
104
+ <React.Fragment key={`fragment-${index}`}>
105
+ {renderBreadcrumbItem(item, index, isLast)}
106
+ <BreadcrumbSeparator>{separator}</BreadcrumbSeparator>
107
+ <BreadcrumbItem>
108
+ <BreadcrumbEllipsis />
109
+ </BreadcrumbItem>
110
+ {!isLast && <BreadcrumbSeparator>{separator}</BreadcrumbSeparator>}
111
+ </React.Fragment>
112
+ );
113
+ }
114
+
115
+ return (
116
+ <React.Fragment key={`fragment-${index}`}>
117
+ {renderBreadcrumbItem(item, index, isLast)}
118
+ {!isLast && <BreadcrumbSeparator>{separator}</BreadcrumbSeparator>}
119
+ </React.Fragment>
120
+ );
121
+ })}
122
+ </BreadcrumbList>
123
+ </Breadcrumb>
124
+ );
125
+ };
126
+
127
+ BreadcrumbNavigation.displayName = 'BreadcrumbNavigation';
@@ -0,0 +1,132 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Slot } from "@radix-ui/react-slot"
5
+ import { cn } from "@djangocfg/ui-core/lib"
6
+ import { ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons"
7
+ import Link from "next/link"
8
+
9
+ const Breadcrumb = React.forwardRef<
10
+ HTMLElement,
11
+ React.ComponentPropsWithoutRef<"nav"> & {
12
+ separator?: React.ReactNode
13
+ }
14
+ >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
15
+ Breadcrumb.displayName = "Breadcrumb"
16
+
17
+ const BreadcrumbList = React.forwardRef<
18
+ HTMLOListElement,
19
+ React.ComponentPropsWithoutRef<"ol">
20
+ >(({ className, ...props }, ref) => (
21
+ <ol
22
+ ref={ref}
23
+ className={cn(
24
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
25
+ className
26
+ )}
27
+ {...props}
28
+ />
29
+ ))
30
+ BreadcrumbList.displayName = "BreadcrumbList"
31
+
32
+ const BreadcrumbItem = React.forwardRef<
33
+ HTMLLIElement,
34
+ React.ComponentPropsWithoutRef<"li"> & { key?: React.Key }
35
+ >(({ className, ...props }, ref) => (
36
+ <li
37
+ ref={ref}
38
+ className={cn("inline-flex items-center gap-1.5", className)}
39
+ {...props}
40
+ />
41
+ ))
42
+ BreadcrumbItem.displayName = "BreadcrumbItem"
43
+
44
+ const BreadcrumbLink = React.forwardRef<
45
+ HTMLAnchorElement,
46
+ React.ComponentPropsWithoutRef<"a"> & {
47
+ asChild?: boolean
48
+ href?: string
49
+ }
50
+ >(({ asChild, className, href, children, ...props }, ref) => {
51
+ const Comp = asChild ? Slot : "a"
52
+
53
+ if (href) {
54
+ return (
55
+ <Link
56
+ href={href}
57
+ className={cn("transition-colors hover:text-foreground", className)}
58
+ ref={ref}
59
+ >
60
+ {children}
61
+ </Link>
62
+ )
63
+ }
64
+
65
+ return (
66
+ <Comp
67
+ ref={ref}
68
+ className={cn("transition-colors hover:text-foreground", className)}
69
+ {...props}
70
+ >
71
+ {children}
72
+ </Comp>
73
+ )
74
+ })
75
+ BreadcrumbLink.displayName = "BreadcrumbLink"
76
+
77
+ const BreadcrumbPage = React.forwardRef<
78
+ HTMLSpanElement,
79
+ React.ComponentPropsWithoutRef<"span">
80
+ >(({ className, ...props }, ref) => (
81
+ <span
82
+ ref={ref}
83
+ role="link"
84
+ aria-disabled="true"
85
+ aria-current="page"
86
+ className={cn("font-normal text-foreground", className)}
87
+ {...props}
88
+ />
89
+ ))
90
+ BreadcrumbPage.displayName = "BreadcrumbPage"
91
+
92
+ const BreadcrumbSeparator = ({
93
+ children,
94
+ className,
95
+ ...props
96
+ }: React.ComponentProps<"li"> & { key?: React.Key }) => (
97
+ <li
98
+ role="presentation"
99
+ aria-hidden="true"
100
+ className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
101
+ {...props}
102
+ >
103
+ {children ?? <ChevronRightIcon />}
104
+ </li>
105
+ )
106
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
107
+
108
+ const BreadcrumbEllipsis = ({
109
+ className,
110
+ ...props
111
+ }: React.ComponentProps<"span">) => (
112
+ <span
113
+ role="presentation"
114
+ aria-hidden="true"
115
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
116
+ {...props}
117
+ >
118
+ <DotsHorizontalIcon className="h-4 w-4" />
119
+ <span className="sr-only">More</span>
120
+ </span>
121
+ )
122
+ BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
123
+
124
+ export {
125
+ Breadcrumb,
126
+ BreadcrumbList,
127
+ BreadcrumbItem,
128
+ BreadcrumbLink,
129
+ BreadcrumbPage,
130
+ BreadcrumbSeparator,
131
+ BreadcrumbEllipsis,
132
+ }
@@ -0,0 +1,275 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Loader2, Download, CheckCircle2, AlertCircle } from "lucide-react"
5
+ import { Button, type ButtonProps } from "@djangocfg/ui-core/components"
6
+ import { cn } from "@djangocfg/ui-core/lib"
7
+ import { useLocalStorage } from "../hooks/useLocalStorage"
8
+
9
+ // Token key used by the API client
10
+ const TOKEN_KEY = "auth_token"
11
+
12
+ export interface DownloadButtonProps extends Omit<ButtonProps, 'onClick'> {
13
+ /**
14
+ * URL to download from
15
+ */
16
+ url: string
17
+
18
+ /**
19
+ * Optional filename override. If not provided, will try to extract from Content-Disposition header
20
+ */
21
+ filename?: string
22
+
23
+ /**
24
+ * Optional callback when download starts
25
+ */
26
+ onDownloadStart?: () => void
27
+
28
+ /**
29
+ * Optional callback when download completes
30
+ */
31
+ onDownloadComplete?: (filename: string) => void
32
+
33
+ /**
34
+ * Optional callback when download fails
35
+ */
36
+ onDownloadError?: (error: Error) => void
37
+
38
+ /**
39
+ * Use fetch API for download (allows progress tracking and auth headers)
40
+ * Default: true
41
+ */
42
+ useFetch?: boolean
43
+
44
+ /**
45
+ * Show download status icons (loading, success, error)
46
+ * Default: true
47
+ */
48
+ showStatus?: boolean
49
+
50
+ /**
51
+ * Auto-reset status after success/error (in ms)
52
+ * Default: 2000
53
+ */
54
+ statusResetDelay?: number
55
+
56
+ /**
57
+ * Method for download (GET or POST)
58
+ * Default: GET
59
+ */
60
+ method?: 'GET' | 'POST'
61
+
62
+ /**
63
+ * Optional request body for POST requests
64
+ */
65
+ body?: any
66
+ }
67
+
68
+ type DownloadStatus = 'idle' | 'downloading' | 'success' | 'error'
69
+
70
+ const DownloadButton = React.forwardRef<HTMLButtonElement, DownloadButtonProps>(
71
+ (
72
+ {
73
+ url,
74
+ filename,
75
+ onDownloadStart,
76
+ onDownloadComplete,
77
+ onDownloadError,
78
+ useFetch = true,
79
+ showStatus = true,
80
+ statusResetDelay = 2000,
81
+ method = 'GET',
82
+ body,
83
+ children,
84
+ disabled,
85
+ className,
86
+ ...props
87
+ },
88
+ ref
89
+ ) => {
90
+ const [status, setStatus] = React.useState<DownloadStatus>('idle')
91
+ const resetTimeoutRef = React.useRef<NodeJS.Timeout | undefined>(undefined)
92
+
93
+ // Get auth token from localStorage
94
+ const [token] = useLocalStorage<string>(TOKEN_KEY, '')
95
+
96
+ // Clean up timeout on unmount
97
+ React.useEffect(() => {
98
+ return () => {
99
+ if (resetTimeoutRef.current) {
100
+ clearTimeout(resetTimeoutRef.current)
101
+ }
102
+ }
103
+ }, [])
104
+
105
+ /**
106
+ * Extract filename from Content-Disposition header
107
+ */
108
+ const extractFilename = (headers: Headers): string | null => {
109
+ const disposition = headers.get('Content-Disposition')
110
+ if (!disposition) return null
111
+
112
+ // Try to match: filename*=UTF-8''example.txt or filename="example.txt"
113
+ const filenameMatch = disposition.match(/filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i)
114
+ if (filenameMatch && filenameMatch[1]) {
115
+ return decodeURIComponent(filenameMatch[1])
116
+ }
117
+
118
+ return null
119
+ }
120
+
121
+ /**
122
+ * Trigger browser download for blob
123
+ */
124
+ const triggerDownload = (blob: Blob, downloadFilename: string) => {
125
+ const url = window.URL.createObjectURL(blob)
126
+ const a = document.createElement('a')
127
+ a.href = url
128
+ a.download = downloadFilename
129
+ document.body.appendChild(a)
130
+ a.click()
131
+ document.body.removeChild(a)
132
+ window.URL.revokeObjectURL(url)
133
+ }
134
+
135
+ /**
136
+ * Reset status after delay
137
+ */
138
+ const scheduleStatusReset = () => {
139
+ if (resetTimeoutRef.current) {
140
+ clearTimeout(resetTimeoutRef.current)
141
+ }
142
+ resetTimeoutRef.current = setTimeout(() => {
143
+ setStatus('idle')
144
+ }, statusResetDelay)
145
+ }
146
+
147
+ /**
148
+ * Download using fetch API (supports auth, progress, error handling)
149
+ */
150
+ const downloadWithFetch = async () => {
151
+ try {
152
+ setStatus('downloading')
153
+ onDownloadStart?.()
154
+
155
+ const headers: HeadersInit = {}
156
+
157
+ // Add authorization header if token is available
158
+ if (token) {
159
+ headers['Authorization'] = `Bearer ${token}`
160
+ }
161
+
162
+ const options: RequestInit = {
163
+ method,
164
+ headers,
165
+ }
166
+
167
+ if (method === 'POST' && body) {
168
+ options.body = JSON.stringify(body)
169
+ options.headers = {
170
+ ...options.headers,
171
+ 'Content-Type': 'application/json',
172
+ }
173
+ }
174
+
175
+ const response = await fetch(url, options)
176
+
177
+ if (!response.ok) {
178
+ throw new Error(`Download failed: ${response.status} ${response.statusText}`)
179
+ }
180
+
181
+ const blob = await response.blob()
182
+
183
+ // Determine filename
184
+ const downloadFilename =
185
+ filename ||
186
+ extractFilename(response.headers) ||
187
+ `download-${Date.now()}.bin`
188
+
189
+ // Trigger download
190
+ triggerDownload(blob, downloadFilename)
191
+
192
+ setStatus('success')
193
+ onDownloadComplete?.(downloadFilename)
194
+ scheduleStatusReset()
195
+ } catch (error) {
196
+ console.error('Download error:', error)
197
+ setStatus('error')
198
+ onDownloadError?.(error as Error)
199
+ scheduleStatusReset()
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Simple download using window.open (fallback)
205
+ */
206
+ const downloadWithWindowOpen = () => {
207
+ try {
208
+ setStatus('downloading')
209
+ onDownloadStart?.()
210
+ window.open(url, '_blank')
211
+
212
+ // We can't really track success/failure with window.open
213
+ // So just assume success after a short delay
214
+ setTimeout(() => {
215
+ setStatus('success')
216
+ onDownloadComplete?.(filename || 'file')
217
+ scheduleStatusReset()
218
+ }, 500)
219
+ } catch (error) {
220
+ console.error('Download error:', error)
221
+ setStatus('error')
222
+ onDownloadError?.(error as Error)
223
+ scheduleStatusReset()
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Handle download click
229
+ */
230
+ const handleDownload = () => {
231
+ if (status === 'downloading') return
232
+
233
+ if (useFetch) {
234
+ downloadWithFetch()
235
+ } else {
236
+ downloadWithWindowOpen()
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Render status icon
242
+ */
243
+ const renderStatusIcon = () => {
244
+ if (!showStatus) return null
245
+
246
+ switch (status) {
247
+ case 'downloading':
248
+ return <Loader2 className="animate-spin" />
249
+ case 'success':
250
+ return <CheckCircle2 className="text-green-600" />
251
+ case 'error':
252
+ return <AlertCircle className="text-red-600" />
253
+ default:
254
+ return <Download />
255
+ }
256
+ }
257
+
258
+ return (
259
+ <Button
260
+ ref={ref}
261
+ onClick={handleDownload}
262
+ disabled={disabled || status === 'downloading'}
263
+ className={cn(className)}
264
+ {...props}
265
+ >
266
+ {renderStatusIcon()}
267
+ {children}
268
+ </Button>
269
+ )
270
+ }
271
+ )
272
+
273
+ DownloadButton.displayName = "DownloadButton"
274
+
275
+ export { DownloadButton }