azamat-ui-kit-cli 0.3.13 → 0.3.14
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/dist/index.cjs +807 -107
- package/package.json +1 -1
- package/vendor/src/components/data-table/data-table-pagination.tsx +1 -1
- package/vendor/src/components/data-table/data-table-toolbar.tsx +13 -12
- package/vendor/src/components/data-table/data-table.tsx +14 -14
- package/vendor/src/components/display/smart-card.tsx +17 -14
- package/vendor/src/components/form/form-input.tsx +3 -1
- package/vendor/src/components/form/form-textarea.tsx +15 -12
- package/vendor/src/components/inputs/async-select.tsx +106 -47
- package/vendor/src/components/inputs/clearable-input.tsx +1 -0
- package/vendor/src/components/inputs/input-chrome.tsx +1 -1
- package/vendor/src/components/inputs/input-decorator.tsx +16 -8
- package/vendor/src/components/inputs/simple-select.tsx +28 -28
- package/vendor/src/components/layout/app-sidebar.tsx +454 -154
- package/vendor/src/components/layout/breadcrumbs.tsx +67 -22
- package/vendor/src/components/layout/sidebar-nav.tsx +316 -128
- package/vendor/src/components/overlay/confirm-dialog.tsx +31 -20
- package/vendor/src/components/ui/badge.tsx +33 -32
- package/vendor/src/components/ui/button.tsx +15 -17
- package/vendor/src/components/ui/card.tsx +26 -25
- package/vendor/src/components/ui/dialog.tsx +6 -3
- package/vendor/src/components/ui/dropdown-menu.tsx +9 -9
- package/vendor/src/components/ui/input-primitive.tsx +1 -1
- package/vendor/src/components/ui/input.tsx +105 -2
- package/vendor/src/components/ui/popover.tsx +1 -1
- package/vendor/src/components/ui/select.tsx +3 -3
- package/vendor/src/components/ui/table.tsx +4 -4
- package/vendor/src/components/ui/tabs.tsx +2 -2
- package/vendor/src/families/member-metadata.ts +3 -3
- package/vendor/templates/styles/globals.css +706 -6
package/package.json
CHANGED
|
@@ -49,7 +49,7 @@ function DataTablePagination({
|
|
|
49
49
|
<div
|
|
50
50
|
data-slot="data-table-pagination"
|
|
51
51
|
className={cn(
|
|
52
|
-
"flex flex-col gap-3 border-t
|
|
52
|
+
"flex flex-col gap-3 border-t px-4 py-3 sm:flex-row sm:items-center sm:justify-between",
|
|
53
53
|
className
|
|
54
54
|
)}
|
|
55
55
|
{...props}
|
|
@@ -3,12 +3,12 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|
|
3
3
|
|
|
4
4
|
import { cn } from "@/lib/utils"
|
|
5
5
|
|
|
6
|
-
const dataTableToolbarVariants = cva("flex flex-col", {
|
|
6
|
+
const dataTableToolbarVariants = cva("flex flex-col", {
|
|
7
7
|
variants: {
|
|
8
8
|
variant: {
|
|
9
|
-
default: "
|
|
9
|
+
default: "",
|
|
10
10
|
plain: "border-transparent bg-transparent shadow-none",
|
|
11
|
-
soft: "
|
|
11
|
+
soft: "",
|
|
12
12
|
},
|
|
13
13
|
density: {
|
|
14
14
|
compact: "gap-3 p-3",
|
|
@@ -62,10 +62,11 @@ function DataTableToolbar({
|
|
|
62
62
|
const hasSelection = selectedCount > 0 && Boolean(selectionActions)
|
|
63
63
|
|
|
64
64
|
return (
|
|
65
|
-
<div
|
|
66
|
-
data-slot="data-table-toolbar"
|
|
67
|
-
data-
|
|
68
|
-
|
|
65
|
+
<div
|
|
66
|
+
data-slot="data-table-toolbar"
|
|
67
|
+
data-variant={variant ?? "plain"}
|
|
68
|
+
data-density={density ?? "default"}
|
|
69
|
+
className={cn(dataTableToolbarVariants({ variant, density }), className)}
|
|
69
70
|
{...props}
|
|
70
71
|
>
|
|
71
72
|
{(hasHeading || actions) && (
|
|
@@ -89,11 +90,11 @@ function DataTableToolbar({
|
|
|
89
90
|
{children}
|
|
90
91
|
</div>
|
|
91
92
|
|
|
92
|
-
{hasSelection && (
|
|
93
|
-
<div className="flex shrink-0 items-center gap-2 rounded-full border
|
|
94
|
-
<span className="text-muted-foreground">{selectedLabel(selectedCount, totalCount)}</span>
|
|
95
|
-
{selectionActions}
|
|
96
|
-
</div>
|
|
93
|
+
{hasSelection && (
|
|
94
|
+
<div data-slot="data-table-selection-bar" className="flex shrink-0 items-center gap-2 rounded-full border px-2.5 py-1.5 text-sm backdrop-blur">
|
|
95
|
+
<span className="text-muted-foreground">{selectedLabel(selectedCount, totalCount)}</span>
|
|
96
|
+
{selectionActions}
|
|
97
|
+
</div>
|
|
97
98
|
)}
|
|
98
99
|
</div>
|
|
99
100
|
)}
|
|
@@ -225,16 +225,17 @@ function DataTable<TData, TValue = unknown>({
|
|
|
225
225
|
rowClassName,
|
|
226
226
|
...props
|
|
227
227
|
}: DataTableProps<TData, TValue>) {
|
|
228
|
-
const resolvedColumns = React.useMemo<ColumnDef<TData,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
228
|
+
const resolvedColumns = React.useMemo<ColumnDef<TData, unknown>[]>(() => {
|
|
229
|
+
const baseColumns = columns as ColumnDef<TData, unknown>[]
|
|
230
|
+
if (!rowActions || features?.rowActions === false) return baseColumns
|
|
231
|
+
|
|
232
|
+
return [
|
|
233
|
+
...baseColumns,
|
|
234
|
+
createDataTableActionsColumn<TData>({
|
|
235
|
+
getActions: rowActions,
|
|
236
|
+
}) as ColumnDef<TData, unknown>,
|
|
237
|
+
]
|
|
238
|
+
}, [columns, features?.rowActions, rowActions])
|
|
238
239
|
|
|
239
240
|
const paginationConfig = pagination === false ? undefined : pagination
|
|
240
241
|
const controlledPagination = paginationConfig
|
|
@@ -408,7 +409,7 @@ function DataTable<TData, TValue = unknown>({
|
|
|
408
409
|
data-striped={striped || undefined}
|
|
409
410
|
data-bordered={bordered || undefined}
|
|
410
411
|
className={cn(
|
|
411
|
-
"overflow-hidden rounded-[var(--radius-2xl)] border
|
|
412
|
+
"overflow-hidden rounded-[var(--radius-2xl)] border backdrop-blur",
|
|
412
413
|
!bordered && "border-border",
|
|
413
414
|
renderMobileCard && "hidden md:block",
|
|
414
415
|
tableWrapperClassName
|
|
@@ -419,7 +420,7 @@ function DataTable<TData, TValue = unknown>({
|
|
|
419
420
|
className={cn("text-[0.95rem]", tableClassName)}
|
|
420
421
|
>
|
|
421
422
|
<TableHeader
|
|
422
|
-
className={cn(stickyHeader && "sticky top-0 z-10
|
|
423
|
+
className={cn(stickyHeader && "sticky top-0 z-10 shadow-sm backdrop-blur")}
|
|
423
424
|
>
|
|
424
425
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
425
426
|
<TableRow key={headerGroup.id}>
|
|
@@ -430,7 +431,7 @@ function DataTable<TData, TValue = unknown>({
|
|
|
430
431
|
className={cn(
|
|
431
432
|
densityHeadClassName[density],
|
|
432
433
|
"text-muted-foreground",
|
|
433
|
-
stickyHeader && "
|
|
434
|
+
stickyHeader && "backdrop-blur",
|
|
434
435
|
bordered && "border-r last:border-r-0",
|
|
435
436
|
getHeaderCellClassName(header, headerCellClassName)
|
|
436
437
|
)}
|
|
@@ -460,7 +461,6 @@ function DataTable<TData, TValue = unknown>({
|
|
|
460
461
|
className={cn(
|
|
461
462
|
onRowClick && !rowDisabled && "cursor-pointer",
|
|
462
463
|
!rowDisabled && "transition-colors",
|
|
463
|
-
striped && rowIndex % 2 === 1 && "bg-muted/20",
|
|
464
464
|
rowDisabled && "pointer-events-none opacity-55",
|
|
465
465
|
getRowClassName(row, rowClassName)
|
|
466
466
|
)}
|
|
@@ -47,11 +47,12 @@ export type SmartCardRenderContext = {
|
|
|
47
47
|
/** @deprecated Prefer `InfoCardProps` via `InfoCard` or `CardFamily.Info` for new public usage. */
|
|
48
48
|
export type SmartCardProps = Omit<React.ComponentProps<typeof Card>, "title" | "content" | "size"> & SmartCardRenderContext & {
|
|
49
49
|
orientation?: SmartCardOrientation
|
|
50
|
-
variant?: SmartCardVariant
|
|
51
|
-
size?: SmartCardSize
|
|
52
|
-
density?: SmartCardDensity
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
variant?: SmartCardVariant
|
|
51
|
+
size?: SmartCardSize
|
|
52
|
+
density?: SmartCardDensity
|
|
53
|
+
compact?: boolean
|
|
54
|
+
loading?: boolean
|
|
55
|
+
disabled?: boolean
|
|
55
56
|
selected?: boolean
|
|
56
57
|
interactive?: boolean
|
|
57
58
|
classNames?: SmartCardClassNames
|
|
@@ -93,10 +94,11 @@ function SmartCard({
|
|
|
93
94
|
content,
|
|
94
95
|
footer,
|
|
95
96
|
orientation = "vertical",
|
|
96
|
-
variant = "outline",
|
|
97
|
-
size = "md",
|
|
98
|
-
density
|
|
99
|
-
|
|
97
|
+
variant = "outline",
|
|
98
|
+
size = "md",
|
|
99
|
+
density,
|
|
100
|
+
compact,
|
|
101
|
+
loading = false,
|
|
100
102
|
disabled = false,
|
|
101
103
|
selected = false,
|
|
102
104
|
interactive,
|
|
@@ -109,9 +111,10 @@ function SmartCard({
|
|
|
109
111
|
children,
|
|
110
112
|
onClick,
|
|
111
113
|
...props
|
|
112
|
-
}: SmartCardProps) {
|
|
113
|
-
const ctx: SmartCardRenderContext = { eyebrow, title, description, media, icon, status, actions, meta, content, footer }
|
|
114
|
-
const clickable = Boolean(onClick || interactive)
|
|
114
|
+
}: SmartCardProps) {
|
|
115
|
+
const ctx: SmartCardRenderContext = { eyebrow, title, description, media, icon, status, actions, meta, content, footer }
|
|
116
|
+
const clickable = Boolean(onClick || interactive)
|
|
117
|
+
const resolvedDensity = compact ? "compact" : density ?? "default"
|
|
115
118
|
|
|
116
119
|
return (
|
|
117
120
|
<Card
|
|
@@ -131,7 +134,7 @@ function SmartCard({
|
|
|
131
134
|
{...props}
|
|
132
135
|
>
|
|
133
136
|
{loading ? (
|
|
134
|
-
<div className={cn("grid gap-3", densityClassName[
|
|
137
|
+
<div className={cn("grid gap-3", densityClassName[resolvedDensity])}>
|
|
135
138
|
<Skeleton className="h-5 w-1/2" />
|
|
136
139
|
<SkeletonText rows={3} />
|
|
137
140
|
</div>
|
|
@@ -150,7 +153,7 @@ function SmartCard({
|
|
|
150
153
|
{media}
|
|
151
154
|
</div>
|
|
152
155
|
))}
|
|
153
|
-
<div data-slot="smart-card-body" className={cn("grid min-w-0 flex-1 gap-3", densityClassName[
|
|
156
|
+
<div data-slot="smart-card-body" className={cn("grid min-w-0 flex-1 gap-3", densityClassName[resolvedDensity], classNames?.body)}>
|
|
154
157
|
{renderHeader?.(ctx) ?? (
|
|
155
158
|
<div data-slot="smart-card-header" className={cn("flex items-start justify-between gap-3", classNames?.header)}>
|
|
156
159
|
<div className="flex min-w-0 items-start gap-3">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
2
|
import * as React from "react"
|
|
3
|
-
import { Controller, type Control, type FieldPath, type FieldValues } from "react-hook-form"
|
|
3
|
+
import { Controller, type Control, type FieldPath, type FieldValues, type RegisterOptions } from "react-hook-form"
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
DateRangeInput,
|
|
@@ -57,6 +57,7 @@ type FormControlledFieldProps<
|
|
|
57
57
|
> = FormFieldShellControlProps & {
|
|
58
58
|
control: Control<TFieldValues>
|
|
59
59
|
name: TName
|
|
60
|
+
rules?: RegisterOptions<TFieldValues, TName>
|
|
60
61
|
fieldClassName?: string
|
|
61
62
|
id?: string
|
|
62
63
|
}
|
|
@@ -226,6 +227,7 @@ function FormInput<
|
|
|
226
227
|
<Controller
|
|
227
228
|
control={props.control}
|
|
228
229
|
name={props.name}
|
|
230
|
+
rules={props.rules}
|
|
229
231
|
render={({ field, fieldState }) => {
|
|
230
232
|
const error = fieldState.error?.message
|
|
231
233
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
|
-
import { Controller, type Control, type FieldPath, type FieldValues } from "react-hook-form"
|
|
2
|
+
import { Controller, type Control, type FieldPath, type FieldValues, type RegisterOptions } from "react-hook-form"
|
|
3
3
|
|
|
4
4
|
import { FormFieldShell, type FormFieldShellControlProps } from "@/components/form/form-field-shell"
|
|
5
5
|
import { Textarea } from "@/components/ui/textarea"
|
|
@@ -9,10 +9,11 @@ export type FormTextareaProps<
|
|
|
9
9
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
10
10
|
> = Omit<React.ComponentProps<typeof Textarea>, "name" | "value" | "defaultValue"> &
|
|
11
11
|
FormFieldShellControlProps & {
|
|
12
|
-
control: Control<TFieldValues>
|
|
13
|
-
name: TName
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
control: Control<TFieldValues>
|
|
13
|
+
name: TName
|
|
14
|
+
rules?: RegisterOptions<TFieldValues, TName>
|
|
15
|
+
fieldClassName?: string
|
|
16
|
+
transformIn?: (value: unknown) => string | number | readonly string[] | undefined
|
|
16
17
|
transformOut?: (event: React.ChangeEvent<HTMLTextAreaElement>) => unknown
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -20,9 +21,10 @@ function FormTextarea<
|
|
|
20
21
|
TFieldValues extends FieldValues = FieldValues,
|
|
21
22
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
22
23
|
>({
|
|
23
|
-
control,
|
|
24
|
-
name,
|
|
25
|
-
|
|
24
|
+
control,
|
|
25
|
+
name,
|
|
26
|
+
rules,
|
|
27
|
+
label,
|
|
26
28
|
description,
|
|
27
29
|
required,
|
|
28
30
|
className,
|
|
@@ -50,10 +52,11 @@ function FormTextarea<
|
|
|
50
52
|
const textareaId = id ?? name
|
|
51
53
|
|
|
52
54
|
return (
|
|
53
|
-
<Controller
|
|
54
|
-
control={control}
|
|
55
|
-
name={name}
|
|
56
|
-
|
|
55
|
+
<Controller
|
|
56
|
+
control={control}
|
|
57
|
+
name={name}
|
|
58
|
+
rules={rules}
|
|
59
|
+
render={({ field, fieldState }) => (
|
|
57
60
|
<FormFieldShell
|
|
58
61
|
label={label}
|
|
59
62
|
description={description}
|
|
@@ -282,7 +282,7 @@ function getCachedOptionGroups<
|
|
|
282
282
|
return entry.groups
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
function setCachedOptionGroups<
|
|
285
|
+
function setCachedOptionGroups<
|
|
286
286
|
TValue extends string,
|
|
287
287
|
TData,
|
|
288
288
|
TOption extends AsyncSelectOption<TValue, TData>,
|
|
@@ -291,11 +291,15 @@ function setCachedOptionGroups<
|
|
|
291
291
|
key: string,
|
|
292
292
|
groups: AsyncSelectOptionGroup<TValue, TData, TOption>[]
|
|
293
293
|
) {
|
|
294
|
-
cache.set(key, {
|
|
295
|
-
groups,
|
|
296
|
-
createdAt: Date.now(),
|
|
297
|
-
})
|
|
298
|
-
}
|
|
294
|
+
cache.set(key, {
|
|
295
|
+
groups,
|
|
296
|
+
createdAt: Date.now(),
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function normalizeAsyncSearch(search: string) {
|
|
301
|
+
return search.trim().toLowerCase()
|
|
302
|
+
}
|
|
299
303
|
|
|
300
304
|
function AsyncStateMessage({
|
|
301
305
|
className,
|
|
@@ -306,8 +310,9 @@ function AsyncStateMessage({
|
|
|
306
310
|
}) {
|
|
307
311
|
return (
|
|
308
312
|
<div
|
|
313
|
+
data-slot="async-select-state"
|
|
309
314
|
className={cn(
|
|
310
|
-
"rounded-[min(var(--radius-xl),16px)] border
|
|
315
|
+
"rounded-[min(var(--radius-xl),16px)] border px-3 py-3 text-sm text-muted-foreground",
|
|
311
316
|
className
|
|
312
317
|
)}
|
|
313
318
|
>
|
|
@@ -342,12 +347,14 @@ function AsyncOptionButton<
|
|
|
342
347
|
onSelect: (option: TOption) => void
|
|
343
348
|
}) {
|
|
344
349
|
return (
|
|
345
|
-
<button
|
|
350
|
+
<button
|
|
346
351
|
type="button"
|
|
347
352
|
disabled={option.disabled}
|
|
353
|
+
data-slot="async-select-option"
|
|
354
|
+
data-selected={selected || undefined}
|
|
355
|
+
data-disabled={option.disabled || undefined}
|
|
348
356
|
className={cn(
|
|
349
|
-
"flex w-full items-start gap-2 rounded-[min(var(--radius-xl),16px)] border border-transparent px-3 py-2.5 text-left text-sm outline-none transition-[background-color,border-color,color,box-shadow]
|
|
350
|
-
selected && "border-primary/18 bg-primary/8 text-foreground shadow-[inset_0_1px_0_rgba(255,255,255,0.12)]",
|
|
357
|
+
"flex w-full items-start gap-2 rounded-[min(var(--radius-xl),16px)] border border-transparent px-3 py-2.5 text-left text-sm outline-none transition-[background-color,border-color,color,box-shadow] disabled:pointer-events-none disabled:opacity-50",
|
|
351
358
|
optionClassName
|
|
352
359
|
)}
|
|
353
360
|
onClick={() => onSelect(option)}
|
|
@@ -387,7 +394,8 @@ function AsyncCreateButton({
|
|
|
387
394
|
<button
|
|
388
395
|
type="button"
|
|
389
396
|
disabled={isCreating}
|
|
390
|
-
|
|
397
|
+
data-slot="async-select-create"
|
|
398
|
+
className="flex w-full items-center gap-2 rounded-[min(var(--radius-xl),16px)] border border-dashed px-3 py-2.5 text-left text-sm outline-none transition-[background-color,border-color,color,box-shadow] disabled:pointer-events-none disabled:opacity-50"
|
|
391
399
|
onClick={onCreate}
|
|
392
400
|
>
|
|
393
401
|
<span className="flex size-4 shrink-0 items-center justify-center">
|
|
@@ -462,10 +470,14 @@ function AsyncSelect<
|
|
|
462
470
|
)
|
|
463
471
|
const canClear = clearable && Boolean(value) && !disabled
|
|
464
472
|
const searchTooShort = searchKey.length < minSearchLength
|
|
465
|
-
const canCreate =
|
|
466
|
-
!searchTooShort &&
|
|
467
|
-
Boolean(onCreateOption) &&
|
|
468
|
-
(showCreateOption ?? defaultShowCreateOption)(search, flatOptions)
|
|
473
|
+
const canCreate =
|
|
474
|
+
!searchTooShort &&
|
|
475
|
+
Boolean(onCreateOption) &&
|
|
476
|
+
(showCreateOption ?? defaultShowCreateOption)(search, flatOptions)
|
|
477
|
+
|
|
478
|
+
React.useEffect(() => {
|
|
479
|
+
cacheRef.current.clear()
|
|
480
|
+
}, [loadOptions])
|
|
469
481
|
|
|
470
482
|
React.useEffect(() => {
|
|
471
483
|
setOptionGroups(resolvedDefaultGroups)
|
|
@@ -561,17 +573,36 @@ function AsyncSelect<
|
|
|
561
573
|
event.stopPropagation()
|
|
562
574
|
clearSelection()
|
|
563
575
|
}
|
|
576
|
+
|
|
577
|
+
const handleTriggerKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
|
578
|
+
if (disabled) return
|
|
579
|
+
|
|
580
|
+
if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
|
|
581
|
+
event.preventDefault()
|
|
582
|
+
setOpen(true)
|
|
583
|
+
return
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if ((event.key === "Backspace" || event.key === "Delete") && value) {
|
|
587
|
+
event.preventDefault()
|
|
588
|
+
clearSelection()
|
|
589
|
+
}
|
|
590
|
+
}
|
|
564
591
|
|
|
565
592
|
const handleCreate = async () => {
|
|
566
593
|
if (!onCreateOption || !search.trim() || searchTooShort) return
|
|
567
594
|
|
|
568
595
|
setIsCreating(true)
|
|
569
596
|
|
|
570
|
-
try {
|
|
571
|
-
const createdOption = await onCreateOption(search.trim())
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
597
|
+
try {
|
|
598
|
+
const createdOption = await onCreateOption(search.trim())
|
|
599
|
+
const normalizedSearch = normalizeAsyncSearch(search)
|
|
600
|
+
setOptionGroups((previousGroups) => [{ options: [createdOption] }, ...previousGroups])
|
|
601
|
+
if (cacheOptions && normalizedSearch) {
|
|
602
|
+
setCachedOptionGroups(cacheRef.current, normalizedSearch, [{ options: [createdOption] }])
|
|
603
|
+
}
|
|
604
|
+
onValueChange?.(createdOption.value, createdOption)
|
|
605
|
+
setSearch("")
|
|
575
606
|
setOpen(false)
|
|
576
607
|
} finally {
|
|
577
608
|
setIsCreating(false)
|
|
@@ -583,15 +614,17 @@ function AsyncSelect<
|
|
|
583
614
|
<Popover open={open} onOpenChange={setOpen}>
|
|
584
615
|
<PopoverTrigger
|
|
585
616
|
render={
|
|
586
|
-
<Button
|
|
617
|
+
<Button
|
|
587
618
|
type="button"
|
|
588
619
|
variant="outline"
|
|
589
620
|
disabled={disabled}
|
|
590
621
|
aria-expanded={open}
|
|
622
|
+
data-slot="async-select-trigger"
|
|
591
623
|
className={cn(
|
|
592
|
-
"min-h-11 w-full justify-between
|
|
624
|
+
"min-h-11 w-full justify-between",
|
|
593
625
|
triggerClassName
|
|
594
626
|
)}
|
|
627
|
+
onKeyDown={handleTriggerKeyDown}
|
|
595
628
|
/>
|
|
596
629
|
}
|
|
597
630
|
>
|
|
@@ -632,18 +665,20 @@ function AsyncSelect<
|
|
|
632
665
|
</PopoverTrigger>
|
|
633
666
|
<PopoverContent
|
|
634
667
|
align="start"
|
|
668
|
+
data-slot="async-select-content"
|
|
635
669
|
className={cn(
|
|
636
|
-
"w-(--anchor-width) gap-3 rounded-[var(--radius-2xl)]
|
|
670
|
+
"w-(--anchor-width) gap-3 rounded-[var(--radius-2xl)] p-3.5",
|
|
637
671
|
contentClassName
|
|
638
672
|
)}
|
|
639
673
|
>
|
|
640
|
-
<div className="relative">
|
|
674
|
+
<div data-slot="async-select-search-wrap" className="relative">
|
|
641
675
|
<SearchIcon className="pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
642
676
|
<Input
|
|
677
|
+
data-slot="async-select-search"
|
|
643
678
|
value={search}
|
|
644
679
|
onChange={(event) => setSearch(event.target.value)}
|
|
645
680
|
placeholder={labels?.searchPlaceholder ?? "Search..."}
|
|
646
|
-
className={cn("
|
|
681
|
+
className={cn("pl-8", searchClassName)}
|
|
647
682
|
/>
|
|
648
683
|
</div>
|
|
649
684
|
|
|
@@ -795,7 +830,11 @@ function AsyncMultiSelect<
|
|
|
795
830
|
(showCreateOption ?? defaultShowCreateOption)(search, flatOptions)
|
|
796
831
|
const visibleSelectableOptions = flatOptions.filter((option) => !option.disabled)
|
|
797
832
|
const unselectedVisibleOptions = visibleSelectableOptions.filter((option) => !selectedSet.has(option.value))
|
|
798
|
-
const canSelectAll = showSelectAll && unselectedVisibleOptions.length > 0 && !isMaxReached
|
|
833
|
+
const canSelectAll = showSelectAll && unselectedVisibleOptions.length > 0 && !isMaxReached
|
|
834
|
+
|
|
835
|
+
React.useEffect(() => {
|
|
836
|
+
cacheRef.current.clear()
|
|
837
|
+
}, [loadOptions])
|
|
799
838
|
|
|
800
839
|
React.useEffect(() => {
|
|
801
840
|
setOptionGroups(resolvedDefaultGroups)
|
|
@@ -907,7 +946,7 @@ function AsyncMultiSelect<
|
|
|
907
946
|
}
|
|
908
947
|
|
|
909
948
|
const handleTagRemoveKeyDown = (event: React.KeyboardEvent<HTMLElement>, option: TOption) => {
|
|
910
|
-
if (event.key !== "Enter" && event.key !== " ") return
|
|
949
|
+
if (event.key !== "Enter" && event.key !== " " && event.key !== "Backspace" && event.key !== "Delete") return
|
|
911
950
|
|
|
912
951
|
event.preventDefault()
|
|
913
952
|
event.stopPropagation()
|
|
@@ -915,7 +954,15 @@ function AsyncMultiSelect<
|
|
|
915
954
|
}
|
|
916
955
|
|
|
917
956
|
const handleTriggerKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
|
918
|
-
if (disabled
|
|
957
|
+
if (disabled) return
|
|
958
|
+
|
|
959
|
+
if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
|
|
960
|
+
event.preventDefault()
|
|
961
|
+
setOpen(true)
|
|
962
|
+
return
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
if (search.length > 0 || values.length === 0) return
|
|
919
966
|
|
|
920
967
|
if (event.key === "Backspace" || event.key === "Delete") {
|
|
921
968
|
event.preventDefault()
|
|
@@ -953,17 +1000,21 @@ function AsyncMultiSelect<
|
|
|
953
1000
|
|
|
954
1001
|
setIsCreating(true)
|
|
955
1002
|
|
|
956
|
-
try {
|
|
957
|
-
const createdOption = await onCreateOption(search.trim())
|
|
958
|
-
const
|
|
959
|
-
const
|
|
960
|
-
|
|
1003
|
+
try {
|
|
1004
|
+
const createdOption = await onCreateOption(search.trim())
|
|
1005
|
+
const normalizedSearch = normalizeAsyncSearch(search)
|
|
1006
|
+
const nextKnownOptions = mergeUniqueOptions(allKnownOptions, [createdOption])
|
|
1007
|
+
const nextValues = selectedSet.has(createdOption.value)
|
|
1008
|
+
? values
|
|
961
1009
|
: isMaxReached
|
|
962
1010
|
? values
|
|
963
1011
|
: [...values, createdOption.value]
|
|
964
|
-
|
|
965
|
-
setOptionGroups((previousGroups) => [{ options: [createdOption] }, ...previousGroups])
|
|
966
|
-
|
|
1012
|
+
|
|
1013
|
+
setOptionGroups((previousGroups) => [{ options: [createdOption] }, ...previousGroups])
|
|
1014
|
+
if (cacheOptions && normalizedSearch) {
|
|
1015
|
+
setCachedOptionGroups(cacheRef.current, normalizedSearch, [{ options: [createdOption] }])
|
|
1016
|
+
}
|
|
1017
|
+
emitChange(nextValues, nextKnownOptions)
|
|
967
1018
|
setSearch("")
|
|
968
1019
|
|
|
969
1020
|
if (closeOnSelect) {
|
|
@@ -984,8 +1035,9 @@ function AsyncMultiSelect<
|
|
|
984
1035
|
variant="outline"
|
|
985
1036
|
disabled={disabled}
|
|
986
1037
|
aria-expanded={open}
|
|
1038
|
+
data-slot="async-select-trigger"
|
|
987
1039
|
className={cn(
|
|
988
|
-
"min-h-11 w-full justify-between
|
|
1040
|
+
"min-h-11 w-full justify-between",
|
|
989
1041
|
triggerClassName
|
|
990
1042
|
)}
|
|
991
1043
|
onKeyDown={handleTriggerKeyDown}
|
|
@@ -995,10 +1047,11 @@ function AsyncMultiSelect<
|
|
|
995
1047
|
<span className="flex min-w-0 flex-1 flex-wrap gap-1 text-left">
|
|
996
1048
|
{currentOptions.length > 0 ? (
|
|
997
1049
|
currentOptions.map((option) => (
|
|
998
|
-
<span
|
|
1050
|
+
<span
|
|
999
1051
|
key={option.value}
|
|
1052
|
+
data-slot="async-select-tag"
|
|
1000
1053
|
className={cn(
|
|
1001
|
-
"inline-flex max-w-full items-center gap-1 rounded-full border
|
|
1054
|
+
"inline-flex max-w-full items-center gap-1 rounded-full border px-2 py-1 text-xs text-foreground",
|
|
1002
1055
|
tagClassName
|
|
1003
1056
|
)}
|
|
1004
1057
|
>
|
|
@@ -1016,6 +1069,7 @@ function AsyncMultiSelect<
|
|
|
1016
1069
|
<span
|
|
1017
1070
|
role="button"
|
|
1018
1071
|
tabIndex={0}
|
|
1072
|
+
data-slot="async-select-tag-remove"
|
|
1019
1073
|
className="rounded-full text-muted-foreground transition-colors hover:text-foreground"
|
|
1020
1074
|
aria-label={`Remove ${getOptionLabelText(option)}`}
|
|
1021
1075
|
onClick={(event) => {
|
|
@@ -1040,7 +1094,8 @@ function AsyncMultiSelect<
|
|
|
1040
1094
|
<span
|
|
1041
1095
|
role="button"
|
|
1042
1096
|
tabIndex={0}
|
|
1043
|
-
|
|
1097
|
+
data-slot="async-select-clear"
|
|
1098
|
+
className="rounded-full border p-1 text-muted-foreground transition-colors hover:text-foreground"
|
|
1044
1099
|
aria-label={labels?.clearAll ?? labels?.clear ?? "Clear all"}
|
|
1045
1100
|
onClick={handleClear}
|
|
1046
1101
|
onKeyDown={(event) => {
|
|
@@ -1058,22 +1113,24 @@ function AsyncMultiSelect<
|
|
|
1058
1113
|
</PopoverTrigger>
|
|
1059
1114
|
<PopoverContent
|
|
1060
1115
|
align="start"
|
|
1116
|
+
data-slot="async-select-content"
|
|
1061
1117
|
className={cn(
|
|
1062
|
-
"w-(--anchor-width) gap-3 rounded-[var(--radius-2xl)]
|
|
1118
|
+
"w-(--anchor-width) gap-3 rounded-[var(--radius-2xl)] p-3.5",
|
|
1063
1119
|
contentClassName
|
|
1064
1120
|
)}
|
|
1065
1121
|
>
|
|
1066
|
-
<div className="relative">
|
|
1122
|
+
<div data-slot="async-select-search-wrap" className="relative">
|
|
1067
1123
|
<SearchIcon className="pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
1068
1124
|
<Input
|
|
1125
|
+
data-slot="async-select-search"
|
|
1069
1126
|
value={search}
|
|
1070
1127
|
onChange={(event) => setSearch(event.target.value)}
|
|
1071
1128
|
placeholder={labels?.searchPlaceholder ?? "Search..."}
|
|
1072
|
-
className={cn("
|
|
1129
|
+
className={cn("pl-8", searchClassName)}
|
|
1073
1130
|
/>
|
|
1074
1131
|
</div>
|
|
1075
1132
|
|
|
1076
|
-
<div className="flex flex-wrap items-center justify-between gap-2 rounded-[min(var(--radius-xl),16px)] border
|
|
1133
|
+
<div data-slot="async-select-meta" className="flex flex-wrap items-center justify-between gap-2 rounded-[min(var(--radius-xl),16px)] border px-3 py-2 text-xs text-muted-foreground">
|
|
1077
1134
|
{hasValue && labels?.selectedCount && <span>{labels.selectedCount(values.length)}</span>}
|
|
1078
1135
|
{isMaxReached &&
|
|
1079
1136
|
(renderMaxSelected?.(state) ?? (
|
|
@@ -1083,6 +1140,7 @@ function AsyncMultiSelect<
|
|
|
1083
1140
|
{canSelectAll && (
|
|
1084
1141
|
<button
|
|
1085
1142
|
type="button"
|
|
1143
|
+
data-slot="async-select-meta-action"
|
|
1086
1144
|
className="rounded-full border border-border/75 px-2.5 py-1 font-medium text-foreground transition-colors hover:bg-background"
|
|
1087
1145
|
onClick={handleSelectAllVisible}
|
|
1088
1146
|
>
|
|
@@ -1092,6 +1150,7 @@ function AsyncMultiSelect<
|
|
|
1092
1150
|
{canClear && (
|
|
1093
1151
|
<button
|
|
1094
1152
|
type="button"
|
|
1153
|
+
data-slot="async-select-meta-action"
|
|
1095
1154
|
className="rounded-full border border-border/75 px-2.5 py-1 font-medium text-foreground transition-colors hover:bg-background"
|
|
1096
1155
|
onClick={() => onValueChange?.([], [])}
|
|
1097
1156
|
>
|
|
@@ -1146,9 +1205,9 @@ function AsyncMultiSelect<
|
|
|
1146
1205
|
optionGroups.map((group, groupIndex) => (
|
|
1147
1206
|
<div key={groupIndex}>
|
|
1148
1207
|
{group.label && (
|
|
1149
|
-
<div className="sticky top-0 z-10 bg-popover px-2 py-1 text-xs font-medium text-muted-foreground">
|
|
1150
|
-
{group.label}
|
|
1151
|
-
</div>
|
|
1208
|
+
<div className="sticky top-0 z-10 bg-popover px-2 py-1 text-xs font-medium text-muted-foreground">
|
|
1209
|
+
<span data-slot="async-select-group-label">{group.label}</span>
|
|
1210
|
+
</div>
|
|
1152
1211
|
)}
|
|
1153
1212
|
{group.options.map((option) => (
|
|
1154
1213
|
<AsyncOptionButton
|
|
@@ -79,6 +79,7 @@ const ClearableInput = React.forwardRef<HTMLInputElement, ClearableInputProps>(
|
|
|
79
79
|
{canClear && (
|
|
80
80
|
<button
|
|
81
81
|
type="button"
|
|
82
|
+
data-slot="clearable-input-clear"
|
|
82
83
|
aria-label={clearLabel}
|
|
83
84
|
className="inline-flex size-7 items-center justify-center rounded-full border border-transparent bg-transparent text-muted-foreground/74 transition hover:border-border/60 hover:bg-muted/58 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30"
|
|
84
85
|
onClick={clearValue}
|
|
@@ -22,7 +22,7 @@ function InputChrome({
|
|
|
22
22
|
<div
|
|
23
23
|
data-slot="input-chrome"
|
|
24
24
|
className={cn(
|
|
25
|
-
"flex h-
|
|
25
|
+
"flex h-10 w-full min-w-0 items-center rounded-[var(--aui-control-radius,var(--radius-lg))] border border-[color:var(--aui-control-border-strong,var(--input))] bg-[color:var(--aui-control-surface,var(--background))] shadow-[var(--aui-control-shadow,none)] transition-[background-color,border-color,box-shadow] hover:border-[color:var(--aui-control-hover-border,var(--ring))] hover:bg-[color:var(--aui-control-surface-hover,var(--background))] focus-within:border-[color:var(--ring)] focus-within:shadow-[var(--aui-control-shadow,none),0_0_0_1px_var(--aui-focus-ring,var(--ring)),0_0_0_5px_var(--aui-focus-ring-soft,transparent)]",
|
|
26
26
|
className
|
|
27
27
|
)}
|
|
28
28
|
{...props}
|