@djangocfg/ui-nextjs 2.1.90 → 2.1.92

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 (161) hide show
  1. package/README.md +6 -15
  2. package/package.json +6 -25
  3. package/src/blocks/SplitHero/SplitHeroMedia.tsx +1 -1
  4. package/src/components/index.ts +0 -40
  5. package/src/hooks/index.ts +0 -6
  6. package/src/index.ts +2 -11
  7. package/src/components/button-download.tsx +0 -277
  8. package/src/components/markdown/MarkdownMessage.tsx +0 -340
  9. package/src/components/markdown/index.ts +0 -5
  10. package/src/components/menubar.tsx +0 -275
  11. package/src/components/multi-select-pro/async.tsx +0 -598
  12. package/src/components/multi-select-pro/helpers.tsx +0 -84
  13. package/src/components/multi-select-pro/index.tsx +0 -612
  14. package/src/components/navigation-menu.tsx +0 -154
  15. package/src/components/otp/index.tsx +0 -197
  16. package/src/components/otp/types.ts +0 -133
  17. package/src/components/otp/use-otp-input.ts +0 -225
  18. package/src/components/phone-input.tsx +0 -277
  19. package/src/components/sonner.tsx +0 -32
  20. package/src/hooks/useLocalStorage.ts +0 -300
  21. package/src/hooks/useSessionStorage.ts +0 -290
  22. package/src/lib/index.ts +0 -5
  23. package/src/lib/logger/index.ts +0 -10
  24. package/src/lib/logger/logStore.ts +0 -122
  25. package/src/lib/logger/logger.ts +0 -175
  26. package/src/lib/logger/types.ts +0 -82
  27. package/src/stores/index.ts +0 -8
  28. package/src/stores/mediaCache.ts +0 -534
  29. package/src/tools/AudioPlayer/README.md +0 -206
  30. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +0 -216
  31. package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +0 -280
  32. package/src/tools/AudioPlayer/components/HybridWaveform.tsx +0 -279
  33. package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +0 -149
  34. package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +0 -110
  35. package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +0 -58
  36. package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +0 -45
  37. package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +0 -82
  38. package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +0 -8
  39. package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +0 -6
  40. package/src/tools/AudioPlayer/components/index.ts +0 -22
  41. package/src/tools/AudioPlayer/context/HybridAudioProvider.tsx +0 -158
  42. package/src/tools/AudioPlayer/context/index.ts +0 -16
  43. package/src/tools/AudioPlayer/effects/index.ts +0 -412
  44. package/src/tools/AudioPlayer/hooks/index.ts +0 -35
  45. package/src/tools/AudioPlayer/hooks/useHybridAudio.ts +0 -387
  46. package/src/tools/AudioPlayer/hooks/useHybridAudioAnalysis.ts +0 -95
  47. package/src/tools/AudioPlayer/hooks/useVisualization.tsx +0 -207
  48. package/src/tools/AudioPlayer/index.ts +0 -133
  49. package/src/tools/AudioPlayer/types/effects.ts +0 -73
  50. package/src/tools/AudioPlayer/types/index.ts +0 -27
  51. package/src/tools/AudioPlayer/utils/debug.ts +0 -14
  52. package/src/tools/AudioPlayer/utils/formatTime.ts +0 -10
  53. package/src/tools/AudioPlayer/utils/index.ts +0 -6
  54. package/src/tools/ImageViewer/@refactoring/00-PLAN.md +0 -71
  55. package/src/tools/ImageViewer/@refactoring/01-TYPES.md +0 -121
  56. package/src/tools/ImageViewer/@refactoring/02-UTILS.md +0 -143
  57. package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +0 -261
  58. package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +0 -427
  59. package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +0 -126
  60. package/src/tools/ImageViewer/README.md +0 -200
  61. package/src/tools/ImageViewer/components/ImageInfo.tsx +0 -44
  62. package/src/tools/ImageViewer/components/ImageToolbar.tsx +0 -150
  63. package/src/tools/ImageViewer/components/ImageViewer.tsx +0 -241
  64. package/src/tools/ImageViewer/components/index.ts +0 -7
  65. package/src/tools/ImageViewer/hooks/index.ts +0 -9
  66. package/src/tools/ImageViewer/hooks/useImageLoading.ts +0 -204
  67. package/src/tools/ImageViewer/hooks/useImageTransform.ts +0 -101
  68. package/src/tools/ImageViewer/index.ts +0 -60
  69. package/src/tools/ImageViewer/types.ts +0 -81
  70. package/src/tools/ImageViewer/utils/constants.ts +0 -59
  71. package/src/tools/ImageViewer/utils/debug.ts +0 -14
  72. package/src/tools/ImageViewer/utils/index.ts +0 -17
  73. package/src/tools/ImageViewer/utils/lqip.ts +0 -47
  74. package/src/tools/JsonForm/JsonSchemaForm.tsx +0 -197
  75. package/src/tools/JsonForm/examples/BotConfigExample.tsx +0 -249
  76. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +0 -161
  77. package/src/tools/JsonForm/index.ts +0 -46
  78. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +0 -47
  79. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +0 -74
  80. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +0 -107
  81. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +0 -35
  82. package/src/tools/JsonForm/templates/FieldTemplate.tsx +0 -62
  83. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +0 -116
  84. package/src/tools/JsonForm/templates/index.ts +0 -12
  85. package/src/tools/JsonForm/types.ts +0 -83
  86. package/src/tools/JsonForm/utils.ts +0 -213
  87. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +0 -37
  88. package/src/tools/JsonForm/widgets/ColorWidget.tsx +0 -219
  89. package/src/tools/JsonForm/widgets/NumberWidget.tsx +0 -89
  90. package/src/tools/JsonForm/widgets/SelectWidget.tsx +0 -97
  91. package/src/tools/JsonForm/widgets/SliderWidget.tsx +0 -148
  92. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +0 -35
  93. package/src/tools/JsonForm/widgets/TextWidget.tsx +0 -96
  94. package/src/tools/JsonForm/widgets/index.ts +0 -14
  95. package/src/tools/JsonTree/index.tsx +0 -243
  96. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +0 -213
  97. package/src/tools/LottiePlayer/index.tsx +0 -55
  98. package/src/tools/LottiePlayer/types.ts +0 -108
  99. package/src/tools/LottiePlayer/useLottie.ts +0 -164
  100. package/src/tools/Mermaid/Mermaid.client.tsx +0 -82
  101. package/src/tools/Mermaid/components/MermaidCodeViewer.tsx +0 -95
  102. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +0 -103
  103. package/src/tools/Mermaid/hooks/index.ts +0 -4
  104. package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +0 -73
  105. package/src/tools/Mermaid/hooks/useMermaidFullscreen.ts +0 -46
  106. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +0 -226
  107. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +0 -29
  108. package/src/tools/Mermaid/index.tsx +0 -41
  109. package/src/tools/Mermaid/utils/mermaid-helpers.ts +0 -33
  110. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +0 -149
  111. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +0 -263
  112. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +0 -125
  113. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +0 -100
  114. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +0 -157
  115. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +0 -253
  116. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +0 -173
  117. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +0 -68
  118. package/src/tools/OpenapiViewer/components/index.ts +0 -14
  119. package/src/tools/OpenapiViewer/constants.ts +0 -39
  120. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +0 -337
  121. package/src/tools/OpenapiViewer/hooks/index.ts +0 -8
  122. package/src/tools/OpenapiViewer/hooks/useMobile.ts +0 -10
  123. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +0 -199
  124. package/src/tools/OpenapiViewer/index.tsx +0 -38
  125. package/src/tools/OpenapiViewer/types.ts +0 -151
  126. package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +0 -149
  127. package/src/tools/OpenapiViewer/utils/formatters.ts +0 -71
  128. package/src/tools/OpenapiViewer/utils/index.ts +0 -9
  129. package/src/tools/OpenapiViewer/utils/versionManager.ts +0 -161
  130. package/src/tools/PrettyCode/PrettyCode.client.tsx +0 -208
  131. package/src/tools/PrettyCode/index.tsx +0 -45
  132. package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +0 -91
  133. package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +0 -284
  134. package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +0 -141
  135. package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +0 -178
  136. package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +0 -95
  137. package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +0 -139
  138. package/src/tools/VideoPlayer/README.md +0 -264
  139. package/src/tools/VideoPlayer/components/VideoControls.tsx +0 -138
  140. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +0 -174
  141. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +0 -201
  142. package/src/tools/VideoPlayer/components/index.ts +0 -14
  143. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +0 -52
  144. package/src/tools/VideoPlayer/context/index.ts +0 -8
  145. package/src/tools/VideoPlayer/hooks/index.ts +0 -12
  146. package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +0 -70
  147. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +0 -116
  148. package/src/tools/VideoPlayer/index.ts +0 -77
  149. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +0 -284
  150. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +0 -505
  151. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +0 -400
  152. package/src/tools/VideoPlayer/providers/index.ts +0 -8
  153. package/src/tools/VideoPlayer/types/index.ts +0 -38
  154. package/src/tools/VideoPlayer/types/player.ts +0 -116
  155. package/src/tools/VideoPlayer/types/provider.ts +0 -93
  156. package/src/tools/VideoPlayer/types/sources.ts +0 -97
  157. package/src/tools/VideoPlayer/utils/debug.ts +0 -14
  158. package/src/tools/VideoPlayer/utils/fileSource.ts +0 -78
  159. package/src/tools/VideoPlayer/utils/index.ts +0 -12
  160. package/src/tools/VideoPlayer/utils/resolvers.ts +0 -75
  161. package/src/tools/index.ts +0 -170
@@ -1,598 +0,0 @@
1
- /**
2
- * MultiSelectProAsync - Extended MultiSelectPro with async search support
3
- *
4
- * Based on MultiSelectPro with added features:
5
- * - Async search through external API
6
- * - Debounced search queries
7
- * - Loading states
8
- * - Controlled search value from parent
9
- *
10
- * All original MultiSelectPro features preserved:
11
- * - Animations, variants, responsive, groups, etc.
12
- */
13
-
14
- "use client"
15
-
16
- import { cva } from 'class-variance-authority';
17
- import { Check, ChevronsUpDown, Loader2, X, XCircle } from 'lucide-react';
18
- import * as React from 'react';
19
-
20
- import {
21
- Badge, Button, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList,
22
- CommandSeparator, Popover, PopoverContent, PopoverTrigger, Separator
23
- } from '@djangocfg/ui-core/components';
24
- import { cn } from '@djangocfg/ui-core/lib';
25
-
26
- // ==================== TYPES ====================
27
-
28
- export interface MultiSelectProAsyncOption {
29
- label: string
30
- value: string
31
- description?: string // Optional subtitle/description text shown below label
32
- icon?: React.ComponentType<{ className?: string }>
33
- disabled?: boolean
34
- style?: {
35
- badgeColor?: string
36
- iconColor?: string
37
- gradient?: string
38
- }
39
- }
40
-
41
- export interface MultiSelectProAsyncGroup {
42
- heading: string
43
- options: MultiSelectProAsyncOption[]
44
- }
45
-
46
- export interface AnimationConfig {
47
- badgeAnimation?: "bounce" | "pulse" | "wiggle" | "fade" | "slide" | "none"
48
- popoverAnimation?: "scale" | "slide" | "fade" | "flip" | "none"
49
- optionHoverAnimation?: "highlight" | "scale" | "glow" | "none"
50
- duration?: number
51
- delay?: number
52
- }
53
-
54
- export interface MultiSelectProAsyncRef {
55
- reset: () => void
56
- getSelectedValues: () => string[]
57
- setSelectedValues: (values: string[]) => void
58
- clear: () => void
59
- focus: () => void
60
- }
61
-
62
- export interface MultiSelectProAsyncProps {
63
- // Options from API
64
- options: MultiSelectProAsyncOption[] | MultiSelectProAsyncGroup[]
65
-
66
- // Selection
67
- onValueChange?: (value: string[]) => void
68
- defaultValue?: string[]
69
-
70
- // Async Search
71
- searchValue: string
72
- onSearchChange: (value: string) => void
73
- isLoading?: boolean
74
-
75
- // UI
76
- placeholder?: string
77
- searchPlaceholder?: string
78
- emptyText?: string
79
- loadingText?: string
80
- variant?: "default" | "secondary" | "destructive" | "inverted"
81
- animation?: number
82
- animationConfig?: AnimationConfig
83
- maxCount?: number
84
- modalPopover?: boolean
85
- className?: string
86
- popoverClassName?: string
87
- disabled?: boolean
88
-
89
- // Behavior
90
- hideSelectAll?: boolean
91
- searchable?: boolean
92
- closeOnSelect?: boolean
93
- resetOnDefaultValueChange?: boolean
94
-
95
- // Advanced
96
- autoSize?: boolean
97
- singleLine?: boolean
98
- minWidth?: string
99
- maxWidth?: string
100
- deduplicateOptions?: boolean
101
- }
102
-
103
- // ==================== VARIANTS ====================
104
-
105
- const multiSelectVariants = cva(
106
- "w-full justify-between min-h-10 h-auto py-2",
107
- {
108
- variants: {
109
- variant: {
110
- default: "border-input bg-background hover:bg-accent/50",
111
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
112
- destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
113
- inverted: "bg-primary text-primary-foreground hover:bg-primary/90",
114
- },
115
- },
116
- defaultVariants: {
117
- variant: "default",
118
- },
119
- }
120
- )
121
-
122
- const badgeAnimations = {
123
- bounce: "animate-bounce",
124
- pulse: "animate-pulse",
125
- wiggle: "animate-wiggle",
126
- fade: "animate-fadeIn",
127
- slide: "animate-slideIn",
128
- none: "",
129
- }
130
-
131
- const popoverAnimations = {
132
- scale: "data-[state=open]:animate-scaleIn data-[state=closed]:animate-scaleOut",
133
- slide: "data-[state=open]:animate-slideDown data-[state=closed]:animate-slideUp",
134
- fade: "data-[state=open]:animate-fadeIn data-[state=closed]:animate-fadeOut",
135
- flip: "data-[state=open]:animate-flipIn data-[state=closed]:animate-flipOut",
136
- none: "",
137
- }
138
-
139
- // ==================== HELPERS ====================
140
-
141
- function isGroupedOptions(options: MultiSelectProAsyncOption[] | MultiSelectProAsyncGroup[]): options is MultiSelectProAsyncGroup[] {
142
- return options.length > 0 && 'heading' in options[0]
143
- }
144
-
145
- function flattenOptions(options: MultiSelectProAsyncOption[] | MultiSelectProAsyncGroup[]): MultiSelectProAsyncOption[] {
146
- if (isGroupedOptions(options)) {
147
- return options.flatMap((group) => group.options)
148
- }
149
- return options
150
- }
151
-
152
- function deduplicateOptions(options: MultiSelectProAsyncOption[]): MultiSelectProAsyncOption[] {
153
- const seen = new Set<string>()
154
- return options.filter((option) => {
155
- if (seen.has(option.value)) {
156
- return false
157
- }
158
- seen.add(option.value)
159
- return true
160
- })
161
- }
162
-
163
- // ==================== COMPONENT ====================
164
-
165
- export const MultiSelectProAsync = React.forwardRef<MultiSelectProAsyncRef, MultiSelectProAsyncProps>(
166
- (
167
- {
168
- options,
169
- onValueChange,
170
- defaultValue = [],
171
- searchValue,
172
- onSearchChange,
173
- isLoading = false,
174
- placeholder = "Select options",
175
- searchPlaceholder = "Search...",
176
- emptyText = "No results found.",
177
- loadingText = "Loading...",
178
- variant = "default",
179
- animation = 0,
180
- animationConfig,
181
- maxCount = 3,
182
- modalPopover = false,
183
- className,
184
- popoverClassName,
185
- disabled = false,
186
- hideSelectAll = false,
187
- searchable = true,
188
- closeOnSelect = false,
189
- resetOnDefaultValueChange = true,
190
- autoSize = false,
191
- singleLine = false,
192
- minWidth,
193
- maxWidth,
194
- deduplicateOptions: shouldDeduplicate = false,
195
- },
196
- ref
197
- ) => {
198
- const [open, setOpen] = React.useState(false)
199
- const [selectedValues, setSelectedValues] = React.useState<string[]>(defaultValue)
200
- const buttonRef = React.useRef<HTMLButtonElement>(null)
201
- const [announcements, setAnnouncements] = React.useState<string>("")
202
- // Cache selected options to persist them when they disappear from current options
203
- const [selectedOptionsCache, setSelectedOptionsCache] = React.useState<Map<string, MultiSelectProAsyncOption>>(new Map())
204
-
205
- // Process options
206
- const flatOptions = React.useMemo(() => {
207
- const flat = flattenOptions(options)
208
- return shouldDeduplicate ? deduplicateOptions(flat) : flat
209
- }, [options, shouldDeduplicate])
210
-
211
- // Update cache whenever new options appear
212
- React.useEffect(() => {
213
- setSelectedOptionsCache(prev => {
214
- const updated = new Map(prev)
215
- flatOptions.forEach(option => {
216
- if (selectedValues.includes(option.value)) {
217
- updated.set(option.value, option)
218
- }
219
- })
220
- return updated
221
- })
222
- }, [flatOptions, selectedValues])
223
-
224
- // Animation configuration
225
- const animConfig = React.useMemo(
226
- (): AnimationConfig => ({
227
- badgeAnimation: animationConfig?.badgeAnimation || (animation > 0 ? "bounce" : "none"),
228
- popoverAnimation: animationConfig?.popoverAnimation || "scale",
229
- optionHoverAnimation: animationConfig?.optionHoverAnimation || "highlight",
230
- duration: animationConfig?.duration || animation || 0.3,
231
- delay: animationConfig?.delay || 0,
232
- }),
233
- [animation, animationConfig]
234
- )
235
-
236
- // Reset on defaultValue change
237
- React.useEffect(() => {
238
- if (resetOnDefaultValueChange) {
239
- setSelectedValues(defaultValue)
240
- }
241
- }, [JSON.stringify(defaultValue), resetOnDefaultValueChange])
242
-
243
- // Announce changes for screen readers
244
- const announce = React.useCallback((message: string) => {
245
- setAnnouncements(message)
246
- setTimeout(() => setAnnouncements(""), 1000)
247
- }, [])
248
-
249
- // Toggle selection
250
- const toggleOption = React.useCallback(
251
- (value: string) => {
252
- const isRemoving = selectedValues.includes(value)
253
- const newValues = isRemoving
254
- ? selectedValues.filter((v) => v !== value)
255
- : [...selectedValues, value]
256
-
257
- setSelectedValues(newValues)
258
- onValueChange?.(newValues)
259
-
260
- const option = flatOptions.find((o) => o.value === value)
261
- if (option) {
262
- // Add to cache when selecting
263
- if (!isRemoving) {
264
- setSelectedOptionsCache(prev => new Map(prev).set(value, option))
265
- }
266
-
267
- announce(
268
- isRemoving
269
- ? `Removed ${option.label}`
270
- : `Added ${option.label}`
271
- )
272
- }
273
-
274
- if (closeOnSelect) {
275
- setOpen(false)
276
- }
277
- },
278
- [selectedValues, onValueChange, flatOptions, announce, closeOnSelect]
279
- )
280
-
281
- // Select all
282
- const handleSelectAll = React.useCallback(() => {
283
- const allValues = flatOptions.filter((o) => !o.disabled).map((o) => o.value)
284
- setSelectedValues(allValues)
285
- onValueChange?.(allValues)
286
-
287
- // Cache all selected options
288
- setSelectedOptionsCache(prev => {
289
- const updated = new Map(prev)
290
- flatOptions.forEach(option => {
291
- if (!option.disabled) {
292
- updated.set(option.value, option)
293
- }
294
- })
295
- return updated
296
- })
297
-
298
- announce(`Selected all ${allValues.length} options`)
299
- }, [flatOptions, onValueChange, announce])
300
-
301
- // Clear all
302
- const handleClearAll = React.useCallback(() => {
303
- setSelectedValues([])
304
- onValueChange?.([])
305
- announce("Cleared all selections")
306
- }, [onValueChange, announce])
307
-
308
- // Imperative methods
309
- React.useImperativeHandle(ref, () => ({
310
- reset: () => {
311
- setSelectedValues(defaultValue)
312
- announce("Reset to default values")
313
- },
314
- getSelectedValues: () => selectedValues,
315
- setSelectedValues: (values: string[]) => {
316
- setSelectedValues(values)
317
- onValueChange?.(values)
318
- },
319
- clear: handleClearAll,
320
- focus: () => buttonRef.current?.focus(),
321
- }))
322
-
323
- // Selected options for display - use cache as fallback
324
- const selectedOptions = React.useMemo(
325
- () => selectedValues.map(value => {
326
- // First try to find in current options
327
- const option = flatOptions.find(o => o.value === value)
328
- // If not found, fallback to cache
329
- return option || selectedOptionsCache.get(value)
330
- }).filter((option): option is MultiSelectProAsyncOption => option !== undefined),
331
- [flatOptions, selectedValues, selectedOptionsCache]
332
- )
333
-
334
- // Render badge with custom styles
335
- const renderBadge = (option: MultiSelectProAsyncOption, index: number) => {
336
- const { style, icon: Icon } = option
337
- const badgeStyle: React.CSSProperties = {}
338
-
339
- if (style?.gradient) {
340
- badgeStyle.background = style.gradient
341
- badgeStyle.color = style.iconColor || "white"
342
- } else if (style?.badgeColor) {
343
- badgeStyle.backgroundColor = style.badgeColor
344
- badgeStyle.color = style.iconColor || "white"
345
- }
346
-
347
- const animationClass = animConfig.badgeAnimation
348
- ? badgeAnimations[animConfig.badgeAnimation]
349
- : ""
350
-
351
- return (
352
- <Badge
353
- key={option.value}
354
- variant={variant === "default" ? "secondary" : "outline"}
355
- className={cn(
356
- "mr-1 mb-1 text-xs gap-1 flex items-center",
357
- animationClass
358
- )}
359
- style={{
360
- ...badgeStyle,
361
- animationDelay: `${(animConfig.delay || 0) * index}s`,
362
- animationDuration: `${animConfig.duration}s`,
363
- }}
364
- >
365
- {Icon && <Icon className="h-3 w-3" />}
366
- <span>{option.label}</span>
367
- {!disabled && (
368
- <button
369
- className="ml-1 rounded-full hover:bg-muted-foreground/20 focus:outline-none focus:ring-2 focus:ring-ring"
370
- onClick={(e) => {
371
- e.stopPropagation()
372
- toggleOption(option.value)
373
- }}
374
- aria-label={`Remove ${option.label}`}
375
- >
376
- <X className="h-3 w-3" />
377
- </button>
378
- )}
379
- </Badge>
380
- )
381
- }
382
-
383
- // Display value
384
- const displayValue = React.useMemo(() => {
385
- if (selectedOptions.length === 0) {
386
- return <span className="text-muted-foreground">{placeholder}</span>
387
- }
388
-
389
- const displayed = selectedOptions.slice(0, maxCount)
390
- const remaining = selectedOptions.length - maxCount
391
-
392
- return (
393
- <div className={cn("flex gap-1", singleLine ? "flex-nowrap overflow-x-auto" : "flex-wrap")}>
394
- {displayed.map((option, index) => renderBadge(option, index))}
395
- {remaining > 0 && (
396
- <Badge variant="outline" className="text-xs">
397
- +{remaining} more
398
- </Badge>
399
- )}
400
- </div>
401
- )
402
- }, [selectedOptions, maxCount, placeholder, singleLine, variant, disabled, animConfig])
403
-
404
- // Render options
405
- const renderOptions = () => {
406
- if (isGroupedOptions(options)) {
407
- return options.map((group, groupIndex) => (
408
- <React.Fragment key={group.heading}>
409
- {groupIndex > 0 && <CommandSeparator />}
410
- <CommandGroup heading={group.heading}>
411
- {group.options.map((option) => {
412
- const isSelected = selectedValues.includes(option.value)
413
- const Icon = option.icon
414
-
415
- return (
416
- <CommandItem
417
- key={option.value}
418
- value={option.value}
419
- onSelect={() => !option.disabled && toggleOption(option.value)}
420
- disabled={option.disabled}
421
- className={cn(
422
- "cursor-pointer",
423
- option.disabled && "opacity-50 cursor-not-allowed"
424
- )}
425
- >
426
- <Check
427
- className={cn(
428
- "mr-2 h-4 w-4 shrink-0",
429
- isSelected ? "opacity-100" : "opacity-0"
430
- )}
431
- />
432
- {Icon && <Icon className="mr-2 h-4 w-4" />}
433
- <div className="flex-1 flex flex-col gap-0.5">
434
- <span>{option.label}</span>
435
- {option.description && (
436
- <span className="text-xs text-muted-foreground">{option.description}</span>
437
- )}
438
- </div>
439
- </CommandItem>
440
- )
441
- })}
442
- </CommandGroup>
443
- </React.Fragment>
444
- ))
445
- }
446
-
447
- return (
448
- <CommandGroup>
449
- {(options as MultiSelectProAsyncOption[]).map((option) => {
450
- const isSelected = selectedValues.includes(option.value)
451
- const Icon = option.icon
452
-
453
- return (
454
- <CommandItem
455
- key={option.value}
456
- value={option.value}
457
- onSelect={() => !option.disabled && toggleOption(option.value)}
458
- disabled={option.disabled}
459
- className={cn(
460
- "cursor-pointer",
461
- option.disabled && "opacity-50 cursor-not-allowed"
462
- )}
463
- >
464
- <Check
465
- className={cn(
466
- "mr-2 h-4 w-4 shrink-0",
467
- isSelected ? "opacity-100" : "opacity-0"
468
- )}
469
- />
470
- {Icon && <Icon className="mr-2 h-4 w-4" />}
471
- <div className="flex-1 flex flex-col gap-0.5">
472
- <span>{option.label}</span>
473
- {option.description && (
474
- <span className="text-xs text-muted-foreground">{option.description}</span>
475
- )}
476
- </div>
477
- </CommandItem>
478
- )
479
- })}
480
- </CommandGroup>
481
- )
482
- }
483
-
484
- const containerStyle: React.CSSProperties = {}
485
- if (minWidth) containerStyle.minWidth = minWidth
486
- if (maxWidth) containerStyle.maxWidth = maxWidth
487
-
488
- return (
489
- <div style={containerStyle} className="relative">
490
- {/* ARIA Live Region for announcements */}
491
- <div
492
- role="status"
493
- aria-live="polite"
494
- aria-atomic="true"
495
- className="sr-only"
496
- >
497
- {announcements}
498
- </div>
499
-
500
- <Popover
501
- open={open}
502
- onOpenChange={(isOpen) => {
503
- if (!disabled) {
504
- setOpen(isOpen)
505
- if (isOpen) {
506
- announce(`Dropdown opened. ${flatOptions.length} options available`)
507
- } else {
508
- onSearchChange("") // Clear search when closing
509
- announce("Dropdown closed")
510
- }
511
- }
512
- }}
513
- modal={modalPopover}
514
- >
515
- <PopoverTrigger asChild>
516
- <Button
517
- ref={buttonRef}
518
- variant="outline"
519
- role="combobox"
520
- aria-expanded={open}
521
- aria-label={placeholder}
522
- className={cn(multiSelectVariants({ variant }), className)}
523
- disabled={disabled}
524
- >
525
- <div className={cn("flex-1 text-left", autoSize ? "" : "overflow-hidden")}>
526
- {displayValue}
527
- </div>
528
- <div className="flex items-center gap-1 ml-2">
529
- {selectedValues.length > 0 && !disabled && (
530
- <button
531
- onClick={(e) => {
532
- e.stopPropagation()
533
- handleClearAll()
534
- }}
535
- className="rounded-full hover:bg-muted-foreground/20 focus:outline-none focus:ring-2 focus:ring-ring"
536
- aria-label="Clear all selections"
537
- >
538
- <XCircle className="h-4 w-4 shrink-0 opacity-50" />
539
- </button>
540
- )}
541
- <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
542
- </div>
543
- </Button>
544
- </PopoverTrigger>
545
- <PopoverContent
546
- className={cn(
547
- "w-[var(--radix-popover-trigger-width)] p-0",
548
- animConfig.popoverAnimation ? popoverAnimations[animConfig.popoverAnimation] : "",
549
- popoverClassName
550
- )}
551
- align="start"
552
- style={{
553
- animationDuration: `${animConfig.duration}s`,
554
- }}
555
- >
556
- <Command shouldFilter={false}>
557
- {searchable && (
558
- <CommandInput
559
- placeholder={searchPlaceholder}
560
- value={searchValue}
561
- onValueChange={onSearchChange}
562
- />
563
- )}
564
- <CommandList>
565
- {!hideSelectAll && !isGroupedOptions(options) && (
566
- <>
567
- <CommandGroup>
568
- <CommandItem
569
- onSelect={handleSelectAll}
570
- className="cursor-pointer justify-center font-medium"
571
- >
572
- Select All
573
- </CommandItem>
574
- </CommandGroup>
575
- <Separator />
576
- </>
577
- )}
578
-
579
- {isLoading ? (
580
- <div className="py-6 text-center text-sm">
581
- <Loader2 className="h-4 w-4 animate-spin mx-auto mb-2" />
582
- {loadingText}
583
- </div>
584
- ) : options.length === 0 ? (
585
- <CommandEmpty>{emptyText}</CommandEmpty>
586
- ) : (
587
- renderOptions()
588
- )}
589
- </CommandList>
590
- </Command>
591
- </PopoverContent>
592
- </Popover>
593
- </div>
594
- )
595
- }
596
- )
597
-
598
- MultiSelectProAsync.displayName = "MultiSelectProAsync"
@@ -1,84 +0,0 @@
1
- /**
2
- * Helper utilities for MultiSelectPro components
3
- */
4
-
5
- import type { MultiSelectProOption } from './index'
6
- import type { MultiSelectProAsyncOption } from './async'
7
-
8
- // ==================== TYPES ====================
9
-
10
- /**
11
- * Generic option builder config
12
- */
13
- export interface OptionBuilderConfig<T = any> {
14
- /** Extract unique ID/value from item */
15
- getValue: (item: T) => string
16
- /** Extract main label text */
17
- getLabel: (item: T) => string
18
- /** Optional: Extract description/subtitle text */
19
- getDescription?: (item: T) => string | undefined
20
- /** Optional: Check if item is disabled */
21
- isDisabled?: (item: T) => boolean
22
- /** Optional: Custom icon component */
23
- getIcon?: (item: T) => React.ComponentType<{ className?: string }> | undefined
24
- /** Optional: Custom styling */
25
- getStyle?: (item: T) => {
26
- badgeColor?: string
27
- iconColor?: string
28
- gradient?: string
29
- } | undefined
30
- }
31
-
32
- // ==================== HELPERS ====================
33
-
34
- /**
35
- * Generic option builder for MultiSelectPro components
36
- *
37
- * @example
38
- * ```tsx
39
- * // Simple usage
40
- * const options = items.map(createOption({
41
- * getValue: (item) => item.id,
42
- * getLabel: (item) => item.name,
43
- * }))
44
- *
45
- * // With description
46
- * const options = channels.map(createOption({
47
- * getValue: (ch) => ch.id,
48
- * getLabel: (ch) => ch.title,
49
- * getDescription: (ch) => `📱 ${ch.phone}`,
50
- * }))
51
- * ```
52
- */
53
- export function createOption<T>(
54
- config: OptionBuilderConfig<T>
55
- ): (item: T) => MultiSelectProOption | MultiSelectProAsyncOption {
56
- return (item: T) => ({
57
- value: config.getValue(item),
58
- label: config.getLabel(item),
59
- description: config.getDescription?.(item),
60
- disabled: config.isDisabled?.(item) ?? false,
61
- icon: config.getIcon?.(item),
62
- style: config.getStyle?.(item),
63
- })
64
- }
65
-
66
- /**
67
- * Batch create options from array
68
- *
69
- * @example
70
- * ```tsx
71
- * const options = createOptions(channels, {
72
- * getValue: (ch) => ch.id,
73
- * getLabel: (ch) => ch.title,
74
- * getDescription: (ch) => ch.phone,
75
- * })
76
- * ```
77
- */
78
- export function createOptions<T>(
79
- items: T[],
80
- config: OptionBuilderConfig<T>
81
- ): (MultiSelectProOption | MultiSelectProAsyncOption)[] {
82
- const builder = createOption(config)
83
- return items.map(builder)
84
- }