@ardly/bunext 1.0.6 → 1.0.8

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 (118) hide show
  1. package/README.md +61 -16
  2. package/cli.mjs +126 -0
  3. package/package.json +14 -58
  4. package/.eslintrc.json +0 -8
  5. package/.prettierignore +0 -4
  6. package/STRUCTURE.md +0 -77
  7. package/bin/cli.mjs +0 -126
  8. package/components.json +0 -21
  9. package/next.config.ts +0 -22
  10. package/postcss.config.mjs +0 -8
  11. package/prettier.config.js +0 -7
  12. package/public/android-chrome-192x192.png +0 -0
  13. package/public/android-chrome-512x512.png +0 -0
  14. package/public/favicon.svg +0 -1
  15. package/public/loading-dots.gif +0 -0
  16. package/public/logo.svg +0 -1
  17. package/public/ogImage.webp +0 -0
  18. package/public/site.webmanifest +0 -19
  19. package/src/actions/placeholder.ts +0 -30
  20. package/src/actions/sampleAction.ts +0 -39
  21. package/src/app/(index)/intr/TestCard.tsx +0 -31
  22. package/src/app/(index)/intr/page.tsx +0 -17
  23. package/src/app/(index)/page.tsx +0 -156
  24. package/src/app/(index)/pagetr/page.tsx +0 -37
  25. package/src/app/error-wrapper.tsx +0 -32
  26. package/src/app/global-error.tsx +0 -53
  27. package/src/app/layout.tsx +0 -56
  28. package/src/app/loading.tsx +0 -11
  29. package/src/app/not-found.tsx +0 -45
  30. package/src/app/sitemap.ts +0 -14
  31. package/src/components/Providers/root-provider.tsx +0 -22
  32. package/src/components/Providers/theme-provider.tsx +0 -27
  33. package/src/components/TestComp.tsx +0 -11
  34. package/src/components/brand.tsx +0 -35
  35. package/src/components/navigation/footer.tsx +0 -32
  36. package/src/components/navigation/main-nav.tsx +0 -55
  37. package/src/components/navigation/mobile-nav.tsx +0 -154
  38. package/src/components/navigation/site-header.tsx +0 -67
  39. package/src/components/ui/avatar.tsx +0 -50
  40. package/src/components/ui/badge.tsx +0 -36
  41. package/src/components/ui/button.tsx +0 -56
  42. package/src/components/ui/card.tsx +0 -79
  43. package/src/components/ui/command.tsx +0 -153
  44. package/src/components/ui/dialog.tsx +0 -122
  45. package/src/components/ui/drawer.tsx +0 -118
  46. package/src/components/ui/dropdown-menu.tsx +0 -200
  47. package/src/components/ui/input.tsx +0 -22
  48. package/src/components/ui/label.tsx +0 -26
  49. package/src/components/ui/multi-select.tsx +0 -380
  50. package/src/components/ui/origin/multiselect.tsx +0 -645
  51. package/src/components/ui/popover.tsx +0 -31
  52. package/src/components/ui/radio-group.tsx +0 -44
  53. package/src/components/ui/separator.tsx +0 -31
  54. package/src/components/ui/skeleton.tsx +0 -15
  55. package/src/components/ui/themeSelector.tsx +0 -157
  56. package/src/components/ui/toast.tsx +0 -129
  57. package/src/components/ui/toaster.tsx +0 -31
  58. package/src/components/ui/tooltip.tsx +0 -39
  59. package/src/components/utils/ConditionalLink.tsx +0 -46
  60. package/src/components/utils/Image.tsx +0 -57
  61. package/src/components/utils/Img.tsx +0 -104
  62. package/src/components/utils/Spinner.tsx +0 -29
  63. package/src/components/utils/TopButton.tsx +0 -67
  64. package/src/components/utils/TransitionLink.tsx +0 -67
  65. package/src/components/utils/copy.tsx +0 -98
  66. package/src/components/utils/featureFlag.tsx +0 -22
  67. package/src/components/utils/icons.tsx +0 -155
  68. package/src/components/utils/share-modal.tsx +0 -159
  69. package/src/hooks/use-intersection.ts +0 -52
  70. package/src/hooks/use-lazy-load.ts +0 -33
  71. package/src/hooks/use-meta-color.ts +0 -25
  72. package/src/hooks/use-toast.ts +0 -191
  73. package/src/lib/config/featureflags.ts +0 -63
  74. package/src/lib/config/siteConfig.ts +0 -172
  75. package/src/lib/config/user.ts +0 -9
  76. package/src/lib/data/footer-data.ts +0 -85
  77. package/src/lib/data/nav-data.ts +0 -30
  78. package/src/lib/data/siteData.ts +0 -52
  79. package/src/lib/utils/index.ts +0 -190
  80. package/src/styles/customGlobal.css +0 -141
  81. package/src/styles/globals.css +0 -72
  82. package/src/styles/tailwind/base.ts +0 -46
  83. package/src/styles/tailwind/fonts/ClashDisplay-Bold.eot +0 -0
  84. package/src/styles/tailwind/fonts/ClashDisplay-Bold.ttf +0 -0
  85. package/src/styles/tailwind/fonts/ClashDisplay-Bold.woff +0 -0
  86. package/src/styles/tailwind/fonts/ClashDisplay-Bold.woff2 +0 -0
  87. package/src/styles/tailwind/fonts/ClashDisplay-Extralight.eot +0 -0
  88. package/src/styles/tailwind/fonts/ClashDisplay-Extralight.ttf +0 -0
  89. package/src/styles/tailwind/fonts/ClashDisplay-Extralight.woff +0 -0
  90. package/src/styles/tailwind/fonts/ClashDisplay-Extralight.woff2 +0 -0
  91. package/src/styles/tailwind/fonts/ClashDisplay-Light.eot +0 -0
  92. package/src/styles/tailwind/fonts/ClashDisplay-Light.ttf +0 -0
  93. package/src/styles/tailwind/fonts/ClashDisplay-Light.woff +0 -0
  94. package/src/styles/tailwind/fonts/ClashDisplay-Light.woff2 +0 -0
  95. package/src/styles/tailwind/fonts/ClashDisplay-Medium.eot +0 -0
  96. package/src/styles/tailwind/fonts/ClashDisplay-Medium.ttf +0 -0
  97. package/src/styles/tailwind/fonts/ClashDisplay-Medium.woff +0 -0
  98. package/src/styles/tailwind/fonts/ClashDisplay-Medium.woff2 +0 -0
  99. package/src/styles/tailwind/fonts/ClashDisplay-Regular.eot +0 -0
  100. package/src/styles/tailwind/fonts/ClashDisplay-Regular.ttf +0 -0
  101. package/src/styles/tailwind/fonts/ClashDisplay-Regular.woff +0 -0
  102. package/src/styles/tailwind/fonts/ClashDisplay-Regular.woff2 +0 -0
  103. package/src/styles/tailwind/fonts/ClashDisplay-Semibold.eot +0 -0
  104. package/src/styles/tailwind/fonts/ClashDisplay-Semibold.ttf +0 -0
  105. package/src/styles/tailwind/fonts/ClashDisplay-Semibold.woff +0 -0
  106. package/src/styles/tailwind/fonts/ClashDisplay-Semibold.woff2 +0 -0
  107. package/src/styles/tailwind/fonts/ClashDisplay-Variable.eot +0 -0
  108. package/src/styles/tailwind/fonts/ClashDisplay-Variable.ttf +0 -0
  109. package/src/styles/tailwind/fonts/ClashDisplay-Variable.woff +0 -0
  110. package/src/styles/tailwind/fonts/ClashDisplay-Variable.woff2 +0 -0
  111. package/src/styles/tailwind/fonts/GeistMonoVF.woff +0 -0
  112. package/src/styles/tailwind/fonts/GeistVF.woff +0 -0
  113. package/src/styles/tailwind/fonts.ts +0 -51
  114. package/src/styles/tailwind/tailwindUtils.ts +0 -29
  115. package/src/types/index.ts +0 -80
  116. package/tailwind.config.ts +0 -104
  117. package/tsconfig.json +0 -28
  118. package/vercel.json +0 -6
@@ -1,645 +0,0 @@
1
- 'use client'
2
- // https://originui.com/selects
3
- import { Command as CommandPrimitive, useCommandState } from 'cmdk'
4
- import { X } from 'lucide-react'
5
- import * as React from 'react'
6
- import { forwardRef, useEffect } from 'react'
7
-
8
- import { cn } from '@/lib/utils'
9
- import {
10
- Command,
11
- CommandGroup,
12
- CommandItem,
13
- CommandList,
14
- } from '@/components/ui/command'
15
-
16
- export interface Option {
17
- value: string
18
- label: string
19
- disable?: boolean
20
- /** fixed option that can‘t be removed. */
21
- fixed?: boolean
22
- /** Group the options by providing key. */
23
- [key: string]: string | boolean | undefined
24
- }
25
- interface GroupOption {
26
- [key: string]: Option[]
27
- }
28
-
29
- interface MultipleSelectorProps {
30
- value?: Option[]
31
- defaultOptions?: Option[]
32
- /** manually controlled options */
33
- options?: Option[]
34
- placeholder?: string
35
- /** Loading component. */
36
- loadingIndicator?: React.ReactNode
37
- /** Empty component. */
38
- emptyIndicator?: React.ReactNode
39
- /** Debounce time for async search. Only work with `onSearch`. */
40
- delay?: number
41
- /**
42
- * Only work with `onSearch` prop. Trigger search when `onFocus`.
43
- * For example, when user click on the input, it will trigger the search to get initial options.
44
- **/
45
- triggerSearchOnFocus?: boolean
46
- /** async search */
47
- onSearch?: (value: string) => Promise<Option[]>
48
- /**
49
- * sync search. This search will not showing loadingIndicator.
50
- * The rest props are the same as async search.
51
- * i.e.: creatable, groupBy, delay.
52
- **/
53
- onSearchSync?: (value: string) => Option[]
54
- onChange?: (options: Option[]) => void
55
- /** Limit the maximum number of selected options. */
56
- maxSelected?: number
57
- /** When the number of selected options exceeds the limit, the onMaxSelected will be called. */
58
- onMaxSelected?: (maxLimit: number) => void
59
- /** Hide the placeholder when there are options selected. */
60
- hidePlaceholderWhenSelected?: boolean
61
- disabled?: boolean
62
- /** Group the options base on provided key. */
63
- groupBy?: string
64
- className?: string
65
- badgeClassName?: string
66
- /**
67
- * First item selected is a default behavior by cmdk. That is why the default is true.
68
- * This is a workaround solution by add a dummy item.
69
- *
70
- * @reference: https://github.com/pacocoursey/cmdk/issues/171
71
- */
72
- selectFirstItem?: boolean
73
- /** Allow user to create option when there is no option matched. */
74
- creatable?: boolean
75
- /** Props of `Command` */
76
- commandProps?: React.ComponentPropsWithoutRef<typeof Command>
77
- /** Props of `CommandInput` */
78
- inputProps?: Omit<
79
- React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>,
80
- 'value' | 'placeholder' | 'disabled'
81
- >
82
- /** hide the clear all button. */
83
- hideClearAllButton?: boolean
84
- }
85
-
86
- export interface MultipleSelectorRef {
87
- selectedValue: Option[]
88
- input: HTMLInputElement
89
- focus: () => void
90
- reset: () => void
91
- }
92
-
93
- export function useDebounce<T>(value: T, delay?: number): T {
94
- const [debouncedValue, setDebouncedValue] = React.useState<T>(value)
95
-
96
- useEffect(() => {
97
- const timer = setTimeout(() => setDebouncedValue(value), delay || 500)
98
-
99
- return () => {
100
- clearTimeout(timer)
101
- }
102
- }, [value, delay])
103
-
104
- return debouncedValue
105
- }
106
-
107
- function transToGroupOption(options: Option[], groupBy?: string) {
108
- if (options.length === 0) {
109
- return {}
110
- }
111
- if (!groupBy) {
112
- return {
113
- '': options,
114
- }
115
- }
116
-
117
- const groupOption: GroupOption = {}
118
- options.forEach((option) => {
119
- const key = (option[groupBy] as string) || ''
120
- if (!groupOption[key]) {
121
- groupOption[key] = []
122
- }
123
- groupOption[key].push(option)
124
- })
125
- return groupOption
126
- }
127
-
128
- function removePickedOption(groupOption: GroupOption, picked: Option[]) {
129
- const cloneOption = JSON.parse(JSON.stringify(groupOption)) as GroupOption
130
-
131
- for (const [key, value] of Object.entries(cloneOption)) {
132
- cloneOption[key] = value.filter(
133
- (val) => !picked.find((p) => p.value === val.value)
134
- )
135
- }
136
- return cloneOption
137
- }
138
-
139
- function isOptionsExist(groupOption: GroupOption, targetOption: Option[]) {
140
- for (const [, value] of Object.entries(groupOption)) {
141
- if (
142
- value.some((option) => targetOption.find((p) => p.value === option.value))
143
- ) {
144
- return true
145
- }
146
- }
147
- return false
148
- }
149
-
150
- /**
151
- * The `CommandEmpty` of shadcn/ui will cause the cmdk empty not rendering correctly.
152
- * So we create one and copy the `Empty` implementation from `cmdk`.
153
- *
154
- * @reference: https://github.com/hsuanyi-chou/shadcn-ui-expansions/issues/34#issuecomment-1949561607
155
- **/
156
- const CommandEmpty = forwardRef<
157
- HTMLDivElement,
158
- React.ComponentProps<typeof CommandPrimitive.Empty>
159
- >(({ className, ...props }, forwardedRef) => {
160
- const render = useCommandState((state) => state.filtered.count === 0)
161
-
162
- if (!render) return null
163
-
164
- return (
165
- <div
166
- ref={forwardedRef}
167
- className={cn('px-2 py-4 text-center text-sm', className)}
168
- cmdk-empty=""
169
- role="presentation"
170
- {...props}
171
- />
172
- )
173
- })
174
-
175
- CommandEmpty.displayName = 'CommandEmpty'
176
-
177
- const MultipleSelector = React.forwardRef<
178
- MultipleSelectorRef,
179
- MultipleSelectorProps
180
- >(
181
- (
182
- {
183
- value,
184
- onChange,
185
- placeholder,
186
- defaultOptions: arrayDefaultOptions = [],
187
- options: arrayOptions,
188
- delay,
189
- onSearch,
190
- onSearchSync,
191
- loadingIndicator,
192
- emptyIndicator,
193
- maxSelected = Number.MAX_SAFE_INTEGER,
194
- onMaxSelected,
195
- hidePlaceholderWhenSelected,
196
- disabled,
197
- groupBy,
198
- className,
199
- badgeClassName,
200
- selectFirstItem = true,
201
- creatable = false,
202
- triggerSearchOnFocus = false,
203
- commandProps,
204
- inputProps,
205
- hideClearAllButton = false,
206
- }: MultipleSelectorProps,
207
- ref: React.Ref<MultipleSelectorRef>
208
- ) => {
209
- const inputRef = React.useRef<HTMLInputElement>(null)
210
- const [open, setOpen] = React.useState(false)
211
- const [onScrollbar, setOnScrollbar] = React.useState(false)
212
- const [isLoading, setIsLoading] = React.useState(false)
213
- const dropdownRef = React.useRef<HTMLDivElement>(null) // Added this
214
-
215
- const [selected, setSelected] = React.useState<Option[]>(value || [])
216
- const [options, setOptions] = React.useState<GroupOption>(
217
- transToGroupOption(arrayDefaultOptions, groupBy)
218
- )
219
- const [inputValue, setInputValue] = React.useState('')
220
- const debouncedSearchTerm = useDebounce(inputValue, delay || 500)
221
-
222
- React.useImperativeHandle(
223
- ref,
224
- () => ({
225
- selectedValue: [...selected],
226
- input: inputRef.current as HTMLInputElement,
227
- focus: () => inputRef?.current?.focus(),
228
- reset: () => setSelected([]),
229
- }),
230
- [selected]
231
- )
232
-
233
- const handleClickOutside = (event: MouseEvent | TouchEvent) => {
234
- if (
235
- dropdownRef.current &&
236
- !dropdownRef.current.contains(event.target as Node) &&
237
- inputRef.current &&
238
- !inputRef.current.contains(event.target as Node)
239
- ) {
240
- setOpen(false)
241
- inputRef.current.blur()
242
- }
243
- }
244
-
245
- const handleUnselect = React.useCallback(
246
- (option: Option) => {
247
- const newOptions = selected.filter((s) => s.value !== option.value)
248
- setSelected(newOptions)
249
- onChange?.(newOptions)
250
- },
251
- [onChange, selected]
252
- )
253
-
254
- const handleKeyDown = React.useCallback(
255
- (e: React.KeyboardEvent<HTMLDivElement>) => {
256
- const input = inputRef.current
257
- if (input) {
258
- if (e.key === 'Delete' || e.key === 'Backspace') {
259
- if (input.value === '' && selected.length > 0) {
260
- const lastSelectOption = selected[selected.length - 1]
261
- // If last item is fixed, we should not remove it.
262
- if (!lastSelectOption.fixed) {
263
- handleUnselect(selected[selected.length - 1])
264
- }
265
- }
266
- }
267
- // This is not a default behavior of the <input /> field
268
- if (e.key === 'Escape') {
269
- input.blur()
270
- }
271
- }
272
- },
273
- [handleUnselect, selected]
274
- )
275
-
276
- useEffect(() => {
277
- if (open) {
278
- document.addEventListener('mousedown', handleClickOutside)
279
- document.addEventListener('touchend', handleClickOutside)
280
- } else {
281
- document.removeEventListener('mousedown', handleClickOutside)
282
- document.removeEventListener('touchend', handleClickOutside)
283
- }
284
-
285
- return () => {
286
- document.removeEventListener('mousedown', handleClickOutside)
287
- document.removeEventListener('touchend', handleClickOutside)
288
- }
289
- }, [open])
290
-
291
- useEffect(() => {
292
- if (value) {
293
- setSelected(value)
294
- }
295
- }, [value])
296
-
297
- useEffect(() => {
298
- /** If `onSearch` is provided, do not trigger options updated. */
299
- if (!arrayOptions || onSearch) {
300
- return
301
- }
302
- const newOption = transToGroupOption(arrayOptions || [], groupBy)
303
- if (JSON.stringify(newOption) !== JSON.stringify(options)) {
304
- setOptions(newOption)
305
- }
306
- }, [arrayDefaultOptions, arrayOptions, groupBy, onSearch, options])
307
-
308
- useEffect(() => {
309
- /** sync search */
310
-
311
- const doSearchSync = () => {
312
- const res = onSearchSync?.(debouncedSearchTerm)
313
- setOptions(transToGroupOption(res || [], groupBy))
314
- }
315
-
316
- const exec = async () => {
317
- if (!onSearchSync || !open) return
318
-
319
- if (triggerSearchOnFocus) {
320
- doSearchSync()
321
- }
322
-
323
- if (debouncedSearchTerm) {
324
- doSearchSync()
325
- }
326
- }
327
-
328
- void exec()
329
- // eslint-disable-next-line react-hooks/exhaustive-deps
330
- }, [debouncedSearchTerm, groupBy, open, triggerSearchOnFocus])
331
-
332
- useEffect(() => {
333
- /** async search */
334
-
335
- const doSearch = async () => {
336
- setIsLoading(true)
337
- const res = await onSearch?.(debouncedSearchTerm)
338
- setOptions(transToGroupOption(res || [], groupBy))
339
- setIsLoading(false)
340
- }
341
-
342
- const exec = async () => {
343
- if (!onSearch || !open) return
344
-
345
- if (triggerSearchOnFocus) {
346
- await doSearch()
347
- }
348
-
349
- if (debouncedSearchTerm) {
350
- await doSearch()
351
- }
352
- }
353
-
354
- void exec()
355
- // eslint-disable-next-line react-hooks/exhaustive-deps
356
- }, [debouncedSearchTerm, groupBy, open, triggerSearchOnFocus])
357
-
358
- const CreatableItem = () => {
359
- if (!creatable) return undefined
360
- if (
361
- isOptionsExist(options, [{ value: inputValue, label: inputValue }]) ||
362
- selected.find((s) => s.value === inputValue)
363
- ) {
364
- return undefined
365
- }
366
-
367
- const Item = (
368
- <CommandItem
369
- value={inputValue}
370
- className="cursor-pointer"
371
- onMouseDown={(e) => {
372
- e.preventDefault()
373
- e.stopPropagation()
374
- }}
375
- onSelect={(value: string) => {
376
- if (selected.length >= maxSelected) {
377
- onMaxSelected?.(selected.length)
378
- return
379
- }
380
- setInputValue('')
381
- const newOptions = [...selected, { value, label: value }]
382
- setSelected(newOptions)
383
- onChange?.(newOptions)
384
- }}
385
- >
386
- {`Create "${inputValue}"`}
387
- </CommandItem>
388
- )
389
-
390
- // For normal creatable
391
- if (!onSearch && inputValue.length > 0) {
392
- return Item
393
- }
394
-
395
- // For async search creatable. avoid showing creatable item before loading at first.
396
- if (onSearch && debouncedSearchTerm.length > 0 && !isLoading) {
397
- return Item
398
- }
399
-
400
- return undefined
401
- }
402
-
403
- const EmptyItem = React.useCallback(() => {
404
- if (!emptyIndicator) return undefined
405
-
406
- // For async search that showing emptyIndicator
407
- if (onSearch && !creatable && Object.keys(options).length === 0) {
408
- return (
409
- <CommandItem value="-" disabled>
410
- {emptyIndicator}
411
- </CommandItem>
412
- )
413
- }
414
-
415
- return <CommandEmpty>{emptyIndicator}</CommandEmpty>
416
- }, [creatable, emptyIndicator, onSearch, options])
417
-
418
- const selectables = React.useMemo<GroupOption>(
419
- () => removePickedOption(options, selected),
420
- [options, selected]
421
- )
422
-
423
- /** Avoid Creatable Selector freezing or lagging when paste a long string. */
424
- const commandFilter = React.useCallback(() => {
425
- if (commandProps?.filter) {
426
- return commandProps.filter
427
- }
428
-
429
- if (creatable) {
430
- return (value: string, search: string) => {
431
- return value.toLowerCase().includes(search.toLowerCase()) ? 1 : -1
432
- }
433
- }
434
- // Using default filter in `cmdk`. We don&lsquo;t have to provide it.
435
- return undefined
436
- }, [creatable, commandProps?.filter])
437
-
438
- return (
439
- <Command
440
- ref={dropdownRef}
441
- {...commandProps}
442
- onKeyDown={(e) => {
443
- handleKeyDown(e)
444
- commandProps?.onKeyDown?.(e)
445
- }}
446
- className={cn(
447
- 'h-auto overflow-visible bg-transparent',
448
- commandProps?.className
449
- )}
450
- shouldFilter={
451
- commandProps?.shouldFilter !== undefined
452
- ? commandProps.shouldFilter
453
- : !onSearch
454
- } // When onSearch is provided, we don&lsquo;t want to filter the options. You can still override it.
455
- filter={commandFilter()}
456
- >
457
- <div
458
- className={cn(
459
- 'relative min-h-[38px] rounded-lg border border-input text-sm transition-shadow focus-within:border-ring focus-within:outline-none focus-within:ring-[3px] focus-within:ring-ring/20 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50',
460
- {
461
- 'p-1': selected.length !== 0,
462
- 'cursor-text': !disabled && selected.length !== 0,
463
- },
464
- !hideClearAllButton && 'pe-9',
465
- className
466
- )}
467
- onClick={() => {
468
- if (disabled) return
469
- inputRef?.current?.focus()
470
- }}
471
- >
472
- <div className="flex flex-wrap gap-1">
473
- {selected.map((option) => {
474
- return (
475
- <div
476
- key={option.value}
477
- className={cn(
478
- 'animate-fadeIn relative inline-flex h-7 cursor-default items-center rounded-md border border-solid bg-background pe-7 pl-2 ps-2 text-xs font-medium text-secondary-foreground transition-all hover:bg-background disabled:cursor-not-allowed disabled:opacity-50 data-[fixed]:pe-2',
479
- badgeClassName
480
- )}
481
- data-fixed={option.fixed}
482
- data-disabled={disabled || undefined}
483
- >
484
- {option.label}
485
- <button
486
- className="absolute -inset-y-px -end-px flex size-7 items-center justify-center rounded-e-lg border border-transparent p-0 text-muted-foreground/80 outline-0 transition-colors hover:text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring/70"
487
- onKeyDown={(e) => {
488
- if (e.key === 'Enter') {
489
- handleUnselect(option)
490
- }
491
- }}
492
- onMouseDown={(e) => {
493
- e.preventDefault()
494
- e.stopPropagation()
495
- }}
496
- onClick={() => handleUnselect(option)}
497
- aria-label="Remove"
498
- >
499
- <X size={14} strokeWidth={2} aria-hidden="true" />
500
- </button>
501
- </div>
502
- )
503
- })}
504
- {/* Avoid having the "Search" Icon */}
505
- <CommandPrimitive.Input
506
- {...inputProps}
507
- ref={inputRef}
508
- value={inputValue}
509
- disabled={disabled}
510
- onValueChange={(value) => {
511
- setInputValue(value)
512
- inputProps?.onValueChange?.(value)
513
- }}
514
- onBlur={(event) => {
515
- if (!onScrollbar) {
516
- setOpen(false)
517
- }
518
- inputProps?.onBlur?.(event)
519
- }}
520
- onFocus={(event) => {
521
- setOpen(true)
522
- if (triggerSearchOnFocus) {
523
- onSearch?.(debouncedSearchTerm)
524
- }
525
- inputProps?.onFocus?.(event)
526
- }}
527
- placeholder={
528
- hidePlaceholderWhenSelected && selected.length !== 0
529
- ? ''
530
- : placeholder
531
- }
532
- className={cn(
533
- 'flex-1 bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed',
534
- {
535
- 'w-full': hidePlaceholderWhenSelected,
536
- 'px-3 py-2': selected.length === 0,
537
- 'ml-1': selected.length !== 0,
538
- },
539
- inputProps?.className
540
- )}
541
- />
542
- <button
543
- type="button"
544
- onClick={() => {
545
- setSelected(selected.filter((s) => s.fixed))
546
- onChange?.(selected.filter((s) => s.fixed))
547
- }}
548
- className={cn(
549
- 'absolute end-0 top-0 flex size-9 items-center justify-center rounded-lg border border-transparent text-muted-foreground/80 transition-colors hover:text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring/70',
550
- (hideClearAllButton ||
551
- disabled ||
552
- selected.length < 1 ||
553
- selected.filter((s) => s.fixed).length === selected.length) &&
554
- 'hidden'
555
- )}
556
- aria-label="Clear all"
557
- >
558
- <X size={16} strokeWidth={2} aria-hidden="true" />
559
- </button>
560
- </div>
561
- </div>
562
- <div className="relative">
563
- <div
564
- className={cn(
565
- 'absolute top-2 z-10 w-full overflow-hidden rounded-lg border border-input',
566
- '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',
567
- !open && 'hidden'
568
- )}
569
- data-state={open ? 'open' : 'closed'}
570
- >
571
- {open && (
572
- <CommandList
573
- className="bg-popover text-popover-foreground shadow-lg shadow-black/5 outline-none"
574
- onMouseLeave={() => {
575
- setOnScrollbar(false)
576
- }}
577
- onMouseEnter={() => {
578
- setOnScrollbar(true)
579
- }}
580
- onMouseUp={() => {
581
- inputRef?.current?.focus()
582
- }}
583
- >
584
- {isLoading ? (
585
- <>{loadingIndicator}</>
586
- ) : (
587
- <>
588
- {EmptyItem()}
589
- {CreatableItem()}
590
- {!selectFirstItem && (
591
- <CommandItem value="-" className="hidden" />
592
- )}
593
- {Object.entries(selectables).map(([key, dropdowns]) => (
594
- <CommandGroup
595
- key={key}
596
- heading={key}
597
- className="h-full overflow-auto"
598
- >
599
- <>
600
- {dropdowns.map((option) => {
601
- return (
602
- <CommandItem
603
- key={option.value}
604
- value={option.value}
605
- disabled={option.disable}
606
- onMouseDown={(e) => {
607
- e.preventDefault()
608
- e.stopPropagation()
609
- }}
610
- onSelect={() => {
611
- if (selected.length >= maxSelected) {
612
- onMaxSelected?.(selected.length)
613
- return
614
- }
615
- setInputValue('')
616
- const newOptions = [...selected, option]
617
- setSelected(newOptions)
618
- onChange?.(newOptions)
619
- }}
620
- className={cn(
621
- 'cursor-pointer',
622
- option.disable &&
623
- 'cursor-not-allowed opacity-50'
624
- )}
625
- >
626
- {option.label}
627
- </CommandItem>
628
- )
629
- })}
630
- </>
631
- </CommandGroup>
632
- ))}
633
- </>
634
- )}
635
- </CommandList>
636
- )}
637
- </div>
638
- </div>
639
- </Command>
640
- )
641
- }
642
- )
643
-
644
- MultipleSelector.displayName = 'MultipleSelector'
645
- export default MultipleSelector
@@ -1,31 +0,0 @@
1
- 'use client'
2
-
3
- import * as React from 'react'
4
- import * as PopoverPrimitive from '@radix-ui/react-popover'
5
-
6
- import { cn } from '@/lib/utils'
7
-
8
- const Popover = PopoverPrimitive.Root
9
-
10
- const PopoverTrigger = PopoverPrimitive.Trigger
11
-
12
- const PopoverContent = React.forwardRef<
13
- React.ElementRef<typeof PopoverPrimitive.Content>,
14
- React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
15
- >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
16
- <PopoverPrimitive.Portal>
17
- <PopoverPrimitive.Content
18
- ref={ref}
19
- align={align}
20
- sideOffset={sideOffset}
21
- className={cn(
22
- 'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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',
23
- className
24
- )}
25
- {...props}
26
- />
27
- </PopoverPrimitive.Portal>
28
- ))
29
- PopoverContent.displayName = PopoverPrimitive.Content.displayName
30
-
31
- export { Popover, PopoverTrigger, PopoverContent }