@djangocfg/ui-core 2.1.90 → 2.1.91
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 +8 -9
- package/package.json +5 -3
- package/src/components/button-download.tsx +276 -0
- package/src/components/dropdown-menu.tsx +219 -0
- package/src/components/index.ts +16 -2
- package/src/components/menubar.tsx +274 -0
- package/src/components/multi-select-pro/async.tsx +600 -0
- package/src/components/multi-select-pro/helpers.tsx +84 -0
- package/src/components/multi-select-pro/index.tsx +613 -0
- package/src/components/navigation-menu.tsx +153 -0
- package/src/components/otp/index.tsx +198 -0
- package/src/components/otp/types.ts +133 -0
- package/src/components/otp/use-otp-input.ts +225 -0
- package/src/components/phone-input.tsx +277 -0
- package/src/components/sonner.tsx +32 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useCopy.ts +2 -10
- package/src/hooks/useLocalStorage.ts +300 -0
- package/src/hooks/useResolvedTheme.ts +68 -0
- package/src/hooks/useSessionStorage.ts +290 -0
- package/src/hooks/useToast.ts +20 -244
- package/src/lib/index.ts +1 -0
- package/src/lib/logger/index.ts +10 -0
- package/src/lib/logger/logStore.ts +122 -0
- package/src/lib/logger/logger.ts +175 -0
- package/src/lib/logger/types.ts +82 -0
- package/src/utils/LazyComponent.tsx +116 -0
- package/src/utils/index.ts +9 -0
- package/src/components/toast.tsx +0 -144
- package/src/components/toaster.tsx +0 -41
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @djangocfg/ui-core
|
|
2
2
|
|
|
3
|
-
Pure React UI library with 60
|
|
3
|
+
Pure React UI library with 60 components built on Radix UI + Tailwind CSS v4.
|
|
4
4
|
|
|
5
5
|
**No Next.js dependencies** — works with Electron, Vite, CRA, and any React environment.
|
|
6
6
|
|
|
@@ -21,7 +21,7 @@ pnpm add @djangocfg/ui-core
|
|
|
21
21
|
| `@djangocfg/ui-core` | Electron, Vite, CRA, any React app |
|
|
22
22
|
| `@djangocfg/ui-nextjs` | Next.js apps (extends ui-core) |
|
|
23
23
|
|
|
24
|
-
## Components (
|
|
24
|
+
## Components (60)
|
|
25
25
|
|
|
26
26
|
### Forms (16)
|
|
27
27
|
`Label` `Button` `ButtonLink` `Input` `Checkbox` `RadioGroup` `Select` `Textarea` `Switch` `Slider` `Combobox` `MultiSelect` `InputOTP` `PhoneInput` `Form` `Field`
|
|
@@ -32,8 +32,8 @@ pnpm add @djangocfg/ui-core
|
|
|
32
32
|
### Overlay (7)
|
|
33
33
|
`Dialog` `AlertDialog` `Sheet` `Drawer` `Popover` `HoverCard` `Tooltip`
|
|
34
34
|
|
|
35
|
-
### Feedback (
|
|
36
|
-
`
|
|
35
|
+
### Feedback (5)
|
|
36
|
+
`Toaster` `Alert` `Progress` `Badge` `Avatar`
|
|
37
37
|
|
|
38
38
|
### Data (8)
|
|
39
39
|
`Table` `Tabs` `Accordion` `Collapsible` `Toggle` `ToggleGroup` `Calendar` `Carousel`
|
|
@@ -55,22 +55,21 @@ pnpm add @djangocfg/ui-core
|
|
|
55
55
|
| `useDebounce` | Debounce values |
|
|
56
56
|
| `useDebouncedCallback` | Debounced callbacks |
|
|
57
57
|
| `useImageLoader` | Image loading state |
|
|
58
|
-
| `useToast` | Toast notifications |
|
|
58
|
+
| `useToast` / `toast` | Toast notifications (Sonner) |
|
|
59
59
|
| `useEventListener` | Event bus |
|
|
60
60
|
| `useDebugTools` | Debug utilities |
|
|
61
61
|
|
|
62
62
|
## Usage
|
|
63
63
|
|
|
64
64
|
```tsx
|
|
65
|
-
import { Button, Card, Input
|
|
65
|
+
import { Button, Card, Input } from '@djangocfg/ui-core';
|
|
66
|
+
import { toast } from '@djangocfg/ui-core/hooks';
|
|
66
67
|
|
|
67
68
|
function Example() {
|
|
68
|
-
const { toast } = useToast();
|
|
69
|
-
|
|
70
69
|
return (
|
|
71
70
|
<Card>
|
|
72
71
|
<Input placeholder="Email" />
|
|
73
|
-
<Button onClick={() => toast(
|
|
72
|
+
<Button onClick={() => toast.success('Saved!')}>
|
|
74
73
|
Submit
|
|
75
74
|
</Button>
|
|
76
75
|
</Card>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.91",
|
|
4
4
|
"description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -53,7 +53,9 @@
|
|
|
53
53
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
54
54
|
"react-hook-form": "^7.0.0",
|
|
55
55
|
"tailwindcss": "^4.0.0",
|
|
56
|
-
"zod": "^4.0.0"
|
|
56
|
+
"zod": "^4.0.0",
|
|
57
|
+
"zustand": "^5.0.0",
|
|
58
|
+
"consola": "^3.4.2"
|
|
57
59
|
},
|
|
58
60
|
"dependencies": {
|
|
59
61
|
"@hookform/resolvers": "^5.2.2",
|
|
@@ -102,7 +104,7 @@
|
|
|
102
104
|
"vaul": "1.1.2"
|
|
103
105
|
},
|
|
104
106
|
"devDependencies": {
|
|
105
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
107
|
+
"@djangocfg/typescript-config": "^2.1.91",
|
|
106
108
|
"@types/node": "^24.7.2",
|
|
107
109
|
"@types/react": "^19.1.0",
|
|
108
110
|
"@types/react-dom": "^19.1.0",
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { AlertCircle, CheckCircle2, Download, Loader2 } from 'lucide-react';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
|
|
6
|
+
import { Button, type ButtonProps } from './button';
|
|
7
|
+
import { cn } from '../lib';
|
|
8
|
+
import { useLocalStorage } from '../hooks/useLocalStorage';
|
|
9
|
+
|
|
10
|
+
// Token key used by the API client
|
|
11
|
+
const TOKEN_KEY = "auth_token"
|
|
12
|
+
|
|
13
|
+
export interface DownloadButtonProps extends Omit<ButtonProps, 'onClick'> {
|
|
14
|
+
/**
|
|
15
|
+
* URL to download from
|
|
16
|
+
*/
|
|
17
|
+
url: string
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Optional filename override. If not provided, will try to extract from Content-Disposition header
|
|
21
|
+
*/
|
|
22
|
+
filename?: string
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Optional callback when download starts
|
|
26
|
+
*/
|
|
27
|
+
onDownloadStart?: () => void
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Optional callback when download completes
|
|
31
|
+
*/
|
|
32
|
+
onDownloadComplete?: (filename: string) => void
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Optional callback when download fails
|
|
36
|
+
*/
|
|
37
|
+
onDownloadError?: (error: Error) => void
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Use fetch API for download (allows progress tracking and auth headers)
|
|
41
|
+
* Default: true
|
|
42
|
+
*/
|
|
43
|
+
useFetch?: boolean
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Show download status icons (loading, success, error)
|
|
47
|
+
* Default: true
|
|
48
|
+
*/
|
|
49
|
+
showStatus?: boolean
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Auto-reset status after success/error (in ms)
|
|
53
|
+
* Default: 2000
|
|
54
|
+
*/
|
|
55
|
+
statusResetDelay?: number
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Method for download (GET or POST)
|
|
59
|
+
* Default: GET
|
|
60
|
+
*/
|
|
61
|
+
method?: 'GET' | 'POST'
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Optional request body for POST requests
|
|
65
|
+
*/
|
|
66
|
+
body?: any
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type DownloadStatus = 'idle' | 'downloading' | 'success' | 'error'
|
|
70
|
+
|
|
71
|
+
const DownloadButton = React.forwardRef<HTMLButtonElement, DownloadButtonProps>(
|
|
72
|
+
(
|
|
73
|
+
{
|
|
74
|
+
url,
|
|
75
|
+
filename,
|
|
76
|
+
onDownloadStart,
|
|
77
|
+
onDownloadComplete,
|
|
78
|
+
onDownloadError,
|
|
79
|
+
useFetch = true,
|
|
80
|
+
showStatus = true,
|
|
81
|
+
statusResetDelay = 2000,
|
|
82
|
+
method = 'GET',
|
|
83
|
+
body,
|
|
84
|
+
children,
|
|
85
|
+
disabled,
|
|
86
|
+
className,
|
|
87
|
+
...props
|
|
88
|
+
},
|
|
89
|
+
ref
|
|
90
|
+
) => {
|
|
91
|
+
const [status, setStatus] = React.useState<DownloadStatus>('idle')
|
|
92
|
+
const resetTimeoutRef = React.useRef<NodeJS.Timeout | undefined>(undefined)
|
|
93
|
+
|
|
94
|
+
// Get auth token from localStorage
|
|
95
|
+
const [token] = useLocalStorage<string>(TOKEN_KEY, '')
|
|
96
|
+
|
|
97
|
+
// Clean up timeout on unmount
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
return () => {
|
|
100
|
+
if (resetTimeoutRef.current) {
|
|
101
|
+
clearTimeout(resetTimeoutRef.current)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}, [])
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Extract filename from Content-Disposition header
|
|
108
|
+
*/
|
|
109
|
+
const extractFilename = (headers: Headers): string | null => {
|
|
110
|
+
const disposition = headers.get('Content-Disposition')
|
|
111
|
+
if (!disposition) return null
|
|
112
|
+
|
|
113
|
+
// Try to match: filename*=UTF-8''example.txt or filename="example.txt"
|
|
114
|
+
const filenameMatch = disposition.match(/filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i)
|
|
115
|
+
if (filenameMatch && filenameMatch[1]) {
|
|
116
|
+
return decodeURIComponent(filenameMatch[1])
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Trigger browser download for blob
|
|
124
|
+
*/
|
|
125
|
+
const triggerDownload = (blob: Blob, downloadFilename: string) => {
|
|
126
|
+
const url = window.URL.createObjectURL(blob)
|
|
127
|
+
const a = document.createElement('a')
|
|
128
|
+
a.href = url
|
|
129
|
+
a.download = downloadFilename
|
|
130
|
+
document.body.appendChild(a)
|
|
131
|
+
a.click()
|
|
132
|
+
document.body.removeChild(a)
|
|
133
|
+
window.URL.revokeObjectURL(url)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Reset status after delay
|
|
138
|
+
*/
|
|
139
|
+
const scheduleStatusReset = () => {
|
|
140
|
+
if (resetTimeoutRef.current) {
|
|
141
|
+
clearTimeout(resetTimeoutRef.current)
|
|
142
|
+
}
|
|
143
|
+
resetTimeoutRef.current = setTimeout(() => {
|
|
144
|
+
setStatus('idle')
|
|
145
|
+
}, statusResetDelay)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Download using fetch API (supports auth, progress, error handling)
|
|
150
|
+
*/
|
|
151
|
+
const downloadWithFetch = async () => {
|
|
152
|
+
try {
|
|
153
|
+
setStatus('downloading')
|
|
154
|
+
onDownloadStart?.()
|
|
155
|
+
|
|
156
|
+
const headers: HeadersInit = {}
|
|
157
|
+
|
|
158
|
+
// Add authorization header if token is available
|
|
159
|
+
if (token) {
|
|
160
|
+
headers['Authorization'] = `Bearer ${token}`
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const options: RequestInit = {
|
|
164
|
+
method,
|
|
165
|
+
headers,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (method === 'POST' && body) {
|
|
169
|
+
options.body = JSON.stringify(body)
|
|
170
|
+
options.headers = {
|
|
171
|
+
...options.headers,
|
|
172
|
+
'Content-Type': 'application/json',
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const response = await fetch(url, options)
|
|
177
|
+
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
throw new Error(`Download failed: ${response.status} ${response.statusText}`)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const blob = await response.blob()
|
|
183
|
+
|
|
184
|
+
// Determine filename
|
|
185
|
+
const downloadFilename =
|
|
186
|
+
filename ||
|
|
187
|
+
extractFilename(response.headers) ||
|
|
188
|
+
`download-${Date.now()}.bin`
|
|
189
|
+
|
|
190
|
+
// Trigger download
|
|
191
|
+
triggerDownload(blob, downloadFilename)
|
|
192
|
+
|
|
193
|
+
setStatus('success')
|
|
194
|
+
onDownloadComplete?.(downloadFilename)
|
|
195
|
+
scheduleStatusReset()
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error('Download error:', error)
|
|
198
|
+
setStatus('error')
|
|
199
|
+
onDownloadError?.(error as Error)
|
|
200
|
+
scheduleStatusReset()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Simple download using window.open (fallback)
|
|
206
|
+
*/
|
|
207
|
+
const downloadWithWindowOpen = () => {
|
|
208
|
+
try {
|
|
209
|
+
setStatus('downloading')
|
|
210
|
+
onDownloadStart?.()
|
|
211
|
+
window.open(url, '_blank')
|
|
212
|
+
|
|
213
|
+
// We can't really track success/failure with window.open
|
|
214
|
+
// So just assume success after a short delay
|
|
215
|
+
setTimeout(() => {
|
|
216
|
+
setStatus('success')
|
|
217
|
+
onDownloadComplete?.(filename || 'file')
|
|
218
|
+
scheduleStatusReset()
|
|
219
|
+
}, 500)
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.error('Download error:', error)
|
|
222
|
+
setStatus('error')
|
|
223
|
+
onDownloadError?.(error as Error)
|
|
224
|
+
scheduleStatusReset()
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Handle download click
|
|
230
|
+
*/
|
|
231
|
+
const handleDownload = () => {
|
|
232
|
+
if (status === 'downloading') return
|
|
233
|
+
|
|
234
|
+
if (useFetch) {
|
|
235
|
+
downloadWithFetch()
|
|
236
|
+
} else {
|
|
237
|
+
downloadWithWindowOpen()
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Render status icon
|
|
243
|
+
*/
|
|
244
|
+
const renderStatusIcon = () => {
|
|
245
|
+
if (!showStatus) return null
|
|
246
|
+
|
|
247
|
+
switch (status) {
|
|
248
|
+
case 'downloading':
|
|
249
|
+
return <Loader2 className="animate-spin" />
|
|
250
|
+
case 'success':
|
|
251
|
+
return <CheckCircle2 className="text-green-600" />
|
|
252
|
+
case 'error':
|
|
253
|
+
return <AlertCircle className="text-red-600" />
|
|
254
|
+
default:
|
|
255
|
+
return <Download />
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<Button
|
|
261
|
+
ref={ref}
|
|
262
|
+
onClick={handleDownload}
|
|
263
|
+
disabled={disabled || status === 'downloading'}
|
|
264
|
+
className={cn(className)}
|
|
265
|
+
{...props}
|
|
266
|
+
>
|
|
267
|
+
{renderStatusIcon()}
|
|
268
|
+
{children}
|
|
269
|
+
</Button>
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
DownloadButton.displayName = "DownloadButton"
|
|
275
|
+
|
|
276
|
+
export { DownloadButton }
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Check, ChevronRight, Circle } from 'lucide-react';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib';
|
|
7
|
+
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
8
|
+
|
|
9
|
+
const DropdownMenu = DropdownMenuPrimitive.Root
|
|
10
|
+
|
|
11
|
+
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
|
12
|
+
|
|
13
|
+
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
|
14
|
+
|
|
15
|
+
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
|
16
|
+
|
|
17
|
+
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
|
18
|
+
|
|
19
|
+
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
|
20
|
+
|
|
21
|
+
const DropdownMenuSubTrigger = React.forwardRef<
|
|
22
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
|
23
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
24
|
+
inset?: boolean
|
|
25
|
+
}
|
|
26
|
+
>(({ className, inset, children, ...props }, ref) => (
|
|
27
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
28
|
+
ref={ref}
|
|
29
|
+
className={cn(
|
|
30
|
+
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
31
|
+
inset && "pl-8",
|
|
32
|
+
className
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
{children}
|
|
37
|
+
<ChevronRight className="ml-auto" />
|
|
38
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
39
|
+
))
|
|
40
|
+
DropdownMenuSubTrigger.displayName =
|
|
41
|
+
DropdownMenuPrimitive.SubTrigger.displayName
|
|
42
|
+
|
|
43
|
+
const DropdownMenuSubContent = React.forwardRef<
|
|
44
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
|
45
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
|
46
|
+
>(({ className, ...props }, ref) => (
|
|
47
|
+
<DropdownMenuPrimitive.SubContent
|
|
48
|
+
ref={ref}
|
|
49
|
+
className={cn(
|
|
50
|
+
"z-600 min-w-32 overflow-hidden rounded-sm border bg-popover backdrop-blur-xl p-1 text-popover-foreground shadow-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
51
|
+
className
|
|
52
|
+
)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
))
|
|
56
|
+
DropdownMenuSubContent.displayName =
|
|
57
|
+
DropdownMenuPrimitive.SubContent.displayName
|
|
58
|
+
|
|
59
|
+
const DropdownMenuContent = React.forwardRef<
|
|
60
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
61
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
62
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
63
|
+
<DropdownMenuPrimitive.Portal>
|
|
64
|
+
<DropdownMenuPrimitive.Content
|
|
65
|
+
ref={ref}
|
|
66
|
+
sideOffset={sideOffset}
|
|
67
|
+
className={cn(
|
|
68
|
+
"z-600 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-32 overflow-y-auto overflow-x-hidden rounded-sm border bg-popover backdrop-blur-xl p-1 text-popover-foreground shadow-sm",
|
|
69
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
70
|
+
className
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
/>
|
|
74
|
+
</DropdownMenuPrimitive.Portal>
|
|
75
|
+
))
|
|
76
|
+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
|
77
|
+
|
|
78
|
+
const DropdownMenuItem = React.forwardRef<
|
|
79
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
80
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
|
81
|
+
inset?: boolean
|
|
82
|
+
href?: string
|
|
83
|
+
key?: React.Key
|
|
84
|
+
}
|
|
85
|
+
>(({ className, inset, href, children, ...props }, ref) => {
|
|
86
|
+
const classes = cn(
|
|
87
|
+
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
88
|
+
inset && "pl-8",
|
|
89
|
+
className
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if (href) {
|
|
93
|
+
return (
|
|
94
|
+
<DropdownMenuPrimitive.Item asChild ref={ref} {...props}>
|
|
95
|
+
<a href={href} className={classes}>
|
|
96
|
+
{children}
|
|
97
|
+
</a>
|
|
98
|
+
</DropdownMenuPrimitive.Item>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<DropdownMenuPrimitive.Item
|
|
104
|
+
ref={ref}
|
|
105
|
+
className={classes}
|
|
106
|
+
{...props}
|
|
107
|
+
>
|
|
108
|
+
{children}
|
|
109
|
+
</DropdownMenuPrimitive.Item>
|
|
110
|
+
)
|
|
111
|
+
})
|
|
112
|
+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
|
113
|
+
|
|
114
|
+
const DropdownMenuCheckboxItem = React.forwardRef<
|
|
115
|
+
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
|
116
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> & { key?: React.Key }
|
|
117
|
+
>(({ className, children, checked, ...props }, ref) => (
|
|
118
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
119
|
+
ref={ref}
|
|
120
|
+
className={cn(
|
|
121
|
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
122
|
+
className
|
|
123
|
+
)}
|
|
124
|
+
checked={checked}
|
|
125
|
+
{...props}
|
|
126
|
+
>
|
|
127
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
128
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
129
|
+
<Check className="h-4 w-4" />
|
|
130
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
131
|
+
</span>
|
|
132
|
+
{children}
|
|
133
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
134
|
+
))
|
|
135
|
+
DropdownMenuCheckboxItem.displayName =
|
|
136
|
+
DropdownMenuPrimitive.CheckboxItem.displayName
|
|
137
|
+
|
|
138
|
+
const DropdownMenuRadioItem = React.forwardRef<
|
|
139
|
+
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
|
140
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> & { key?: React.Key }
|
|
141
|
+
>(({ className, children, ...props }, ref) => (
|
|
142
|
+
<DropdownMenuPrimitive.RadioItem
|
|
143
|
+
ref={ref}
|
|
144
|
+
className={cn(
|
|
145
|
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
146
|
+
className
|
|
147
|
+
)}
|
|
148
|
+
{...props}
|
|
149
|
+
>
|
|
150
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
151
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
152
|
+
<Circle className="h-2 w-2 fill-current" />
|
|
153
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
154
|
+
</span>
|
|
155
|
+
{children}
|
|
156
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
157
|
+
))
|
|
158
|
+
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
|
159
|
+
|
|
160
|
+
const DropdownMenuLabel = React.forwardRef<
|
|
161
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
|
162
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
|
163
|
+
inset?: boolean
|
|
164
|
+
}
|
|
165
|
+
>(({ className, inset, ...props }, ref) => (
|
|
166
|
+
<DropdownMenuPrimitive.Label
|
|
167
|
+
ref={ref}
|
|
168
|
+
className={cn(
|
|
169
|
+
"px-2 py-1.5 text-sm font-semibold",
|
|
170
|
+
inset && "pl-8",
|
|
171
|
+
className
|
|
172
|
+
)}
|
|
173
|
+
{...props}
|
|
174
|
+
/>
|
|
175
|
+
))
|
|
176
|
+
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
|
177
|
+
|
|
178
|
+
const DropdownMenuSeparator = React.forwardRef<
|
|
179
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
180
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
181
|
+
>(({ className, ...props }, ref) => (
|
|
182
|
+
<DropdownMenuPrimitive.Separator
|
|
183
|
+
ref={ref}
|
|
184
|
+
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
185
|
+
{...props}
|
|
186
|
+
/>
|
|
187
|
+
))
|
|
188
|
+
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
|
189
|
+
|
|
190
|
+
const DropdownMenuShortcut = ({
|
|
191
|
+
className,
|
|
192
|
+
...props
|
|
193
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
194
|
+
return (
|
|
195
|
+
<span
|
|
196
|
+
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
|
197
|
+
{...props}
|
|
198
|
+
/>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
|
202
|
+
|
|
203
|
+
export {
|
|
204
|
+
DropdownMenu,
|
|
205
|
+
DropdownMenuTrigger,
|
|
206
|
+
DropdownMenuContent,
|
|
207
|
+
DropdownMenuItem,
|
|
208
|
+
DropdownMenuCheckboxItem,
|
|
209
|
+
DropdownMenuRadioItem,
|
|
210
|
+
DropdownMenuLabel,
|
|
211
|
+
DropdownMenuSeparator,
|
|
212
|
+
DropdownMenuShortcut,
|
|
213
|
+
DropdownMenuGroup,
|
|
214
|
+
DropdownMenuPortal,
|
|
215
|
+
DropdownMenuSub,
|
|
216
|
+
DropdownMenuSubContent,
|
|
217
|
+
DropdownMenuSubTrigger,
|
|
218
|
+
DropdownMenuRadioGroup,
|
|
219
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -17,9 +17,15 @@ export { Combobox } from './combobox';
|
|
|
17
17
|
export type { ComboboxOption, ComboboxProps } from './combobox';
|
|
18
18
|
export { MultiSelect } from './multi-select';
|
|
19
19
|
export type { MultiSelectOption, MultiSelectProps } from './multi-select';
|
|
20
|
+
export { MultiSelectPro } from './multi-select-pro';
|
|
21
|
+
export type { MultiSelectProOption, MultiSelectProGroup, MultiSelectProProps, MultiSelectProRef, AnimationConfig, ResponsiveConfig } from './multi-select-pro';
|
|
22
|
+
export { MultiSelectProAsync } from './multi-select-pro/async';
|
|
23
|
+
export type { MultiSelectProAsyncProps } from './multi-select-pro/async';
|
|
20
24
|
export { Switch } from './switch';
|
|
21
25
|
export { Slider } from './slider';
|
|
22
26
|
export { InputOTP, InputOTPGroup, InputOTPSlot } from './input-otp';
|
|
27
|
+
export { OTPInput, InputOTPSeparator, useSmartOTP } from './otp';
|
|
28
|
+
export type { SmartOTPProps as OTPInputProps, OTPValidationMode, OTPPasteBehavior, OTPValidator } from './otp/types';
|
|
23
29
|
|
|
24
30
|
// Layout Components
|
|
25
31
|
export { Separator } from './separator';
|
|
@@ -64,11 +70,10 @@ export { Toggle } from './toggle';
|
|
|
64
70
|
export { ToggleGroup, ToggleGroupItem } from './toggle-group';
|
|
65
71
|
export { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut } from './command';
|
|
66
72
|
export { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger } from './context-menu';
|
|
73
|
+
export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup } from './dropdown-menu';
|
|
67
74
|
|
|
68
75
|
// Feedback Components
|
|
69
76
|
export { Alert, AlertDescription, AlertTitle } from './alert';
|
|
70
|
-
export { Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from './toast';
|
|
71
|
-
export { Toaster } from './toaster';
|
|
72
77
|
|
|
73
78
|
// Form Components
|
|
74
79
|
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField } from './form';
|
|
@@ -85,5 +90,14 @@ export { Field, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldLegen
|
|
|
85
90
|
|
|
86
91
|
export { CopyButton, CopyField } from './copy';
|
|
87
92
|
export type { CopyButtonProps, CopyFieldProps } from './copy';
|
|
93
|
+
export { DownloadButton } from './button-download';
|
|
94
|
+
export type { DownloadButtonProps } from './button-download';
|
|
88
95
|
export { OgImage } from './og-image';
|
|
89
96
|
export type { OgImageProps } from './og-image';
|
|
97
|
+
|
|
98
|
+
// Additional Components
|
|
99
|
+
export { Toaster } from './sonner';
|
|
100
|
+
export { PhoneInput } from './phone-input';
|
|
101
|
+
export type { PhoneInputProps } from './phone-input';
|
|
102
|
+
export { navigationMenuTriggerStyle, NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuContent, NavigationMenuTrigger, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport } from './navigation-menu';
|
|
103
|
+
export { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarSeparator, MenubarLabel, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarPortal, MenubarSubContent, MenubarSubTrigger, MenubarGroup, MenubarSub, MenubarShortcut } from './menubar';
|