azamat-ui-kit-cli 0.2.2 → 0.3.3
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 +11 -0
- package/dist/index.cjs +452 -0
- package/package.json +2 -2
- package/vendor/src/components/actions/action-menu.tsx +21 -18
- package/vendor/src/components/calendar/calendar.tsx +153 -102
- package/vendor/src/components/calendar/date-picker.tsx +24 -14
- package/vendor/src/components/calendar/date-range-picker.tsx +137 -58
- package/vendor/src/components/charts/charts.tsx +32 -21
- package/vendor/src/components/command/command-palette.tsx +68 -57
- package/vendor/src/components/data-table/data-table-bulk-actions.tsx +23 -20
- package/vendor/src/components/data-table/data-table-column-visibility-menu.tsx +21 -10
- package/vendor/src/components/data-table/data-table-pagination.tsx +6 -6
- package/vendor/src/components/data-table/data-table-toolbar.tsx +72 -44
- package/vendor/src/components/data-table/data-table.tsx +15 -11
- package/vendor/src/components/data-table/table-export-menu.tsx +1 -1
- package/vendor/src/components/data-table/table-import-button.tsx +1 -1
- package/vendor/src/components/display/data-state.tsx +20 -8
- package/vendor/src/components/display/index.ts +19 -15
- package/vendor/src/components/display/metric-card.tsx +35 -0
- package/vendor/src/components/display/progress-circle.tsx +24 -0
- package/vendor/src/components/display/smart-card.tsx +49 -27
- package/vendor/src/components/display/status-dot.tsx +45 -0
- package/vendor/src/components/display/user-card.tsx +30 -0
- package/vendor/src/components/feedback/alert.tsx +21 -11
- package/vendor/src/components/feedback/empty-state.tsx +2 -2
- package/vendor/src/components/feedback/loading-state.tsx +2 -2
- package/vendor/src/components/feedback/page-state.tsx +19 -15
- package/vendor/src/components/feedback/status-badge.tsx +43 -43
- package/vendor/src/components/form/form-app-input.tsx +147 -0
- package/vendor/src/components/form/form-date-input.tsx +16 -19
- package/vendor/src/components/form/form-field-shell.tsx +11 -8
- package/vendor/src/components/form/form-field-utils.ts +76 -0
- package/vendor/src/components/form/form-input.tsx +423 -44
- package/vendor/src/components/form/form-number-input.tsx +16 -15
- package/vendor/src/components/form/form-phone-input.tsx +15 -9
- package/vendor/src/components/form/form-search-input.tsx +16 -19
- package/vendor/src/components/form/form-select.tsx +4 -3
- package/vendor/src/components/form/public.ts +16 -14
- package/vendor/src/components/form/smart-form-shell.tsx +13 -12
- package/vendor/src/components/inputs/app-input.tsx +27 -0
- package/vendor/src/components/inputs/async-select.tsx +113 -84
- package/vendor/src/components/inputs/clearable-input.tsx +81 -61
- package/vendor/src/components/inputs/date-input.tsx +21 -17
- package/vendor/src/components/inputs/date-range-input.tsx +10 -10
- package/vendor/src/components/inputs/index.ts +1 -0
- package/vendor/src/components/inputs/input-decorator.tsx +101 -57
- package/vendor/src/components/inputs/masked-input.tsx +20 -20
- package/vendor/src/components/inputs/money-input.tsx +2 -2
- package/vendor/src/components/inputs/number-input.tsx +29 -19
- package/vendor/src/components/inputs/password-input.tsx +82 -45
- package/vendor/src/components/inputs/phone-input.tsx +24 -2
- package/vendor/src/components/inputs/quantity-input.tsx +2 -2
- package/vendor/src/components/inputs/search-input.tsx +54 -3
- package/vendor/src/components/inputs/simple-select.tsx +110 -22
- package/vendor/src/components/layout/app-shell.tsx +2 -2
- package/vendor/src/components/layout/index.ts +5 -4
- package/vendor/src/components/layout/page-header.tsx +79 -35
- package/vendor/src/components/layout/public.ts +12 -10
- package/vendor/src/components/layout/section-header.tsx +56 -0
- package/vendor/src/components/layout/stack.tsx +106 -0
- package/vendor/src/components/layout/stat-card.tsx +66 -29
- package/vendor/src/components/navigation/index.ts +1 -0
- package/vendor/src/components/navigation/nav-tabs.tsx +60 -0
- package/vendor/src/components/navigation/page-tabs.tsx +41 -26
- package/vendor/src/components/navigation/pagination.tsx +14 -10
- package/vendor/src/components/overlay/alert-dialog.tsx +65 -0
- package/vendor/src/components/overlay/drawer.tsx +71 -0
- package/vendor/src/components/overlay/index.ts +4 -2
- package/vendor/src/components/patterns/data-view.tsx +13 -8
- package/vendor/src/components/ui/badge.tsx +96 -52
- package/vendor/src/components/ui/button.tsx +99 -61
- package/vendor/src/components/ui/card.tsx +84 -25
- package/vendor/src/components/ui/checkbox.tsx +68 -68
- package/vendor/src/components/ui/command.tsx +32 -32
- package/vendor/src/components/ui/dialog.tsx +135 -138
- package/vendor/src/components/ui/dropdown-menu.tsx +21 -21
- package/vendor/src/components/ui/hover-card.tsx +49 -0
- package/vendor/src/components/ui/input-primitive.tsx +24 -0
- package/vendor/src/components/ui/input.tsx +191 -20
- package/vendor/src/components/ui/kbd.tsx +33 -0
- package/vendor/src/components/ui/popover.tsx +11 -11
- package/vendor/src/components/ui/radio-group.tsx +102 -0
- package/vendor/src/components/ui/right-click-menu.tsx +60 -0
- package/vendor/src/components/ui/scroll-box.tsx +27 -0
- package/vendor/src/components/ui/segmented-control.tsx +21 -17
- package/vendor/src/components/ui/select.tsx +187 -189
- package/vendor/src/components/ui/skeleton.tsx +2 -2
- package/vendor/src/components/ui/switch.tsx +60 -60
- package/vendor/src/components/ui/table.tsx +114 -114
- package/vendor/src/components/ui/tabs.tsx +2 -2
- package/vendor/src/components/ui/textarea.tsx +1 -1
- package/vendor/src/components/upload/file-dropzone.tsx +38 -0
- package/vendor/src/components/upload/file-upload.tsx +4 -4
- package/vendor/src/components/upload/image-upload.tsx +22 -19
- package/vendor/src/components/upload/index.ts +2 -0
- package/vendor/src/families/catalog.ts +1 -0
- package/vendor/src/families/docs-groups.ts +10 -1
- package/vendor/src/families/member-metadata.ts +24 -0
- package/vendor/src/families/member-snippets.ts +41 -2
- package/vendor/src/families/migration-map.ts +3 -0
- package/vendor/src/index.ts +23 -18
- package/vendor/templates/styles/globals.css +253 -0
- package/dist/index.js +0 -432
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export { FormSearchInput }
|
|
1
|
+
import { FormAppInput, type FormAppInputProps } from "@/components/form/form-app-input"
|
|
2
|
+
import type { FieldPath, FieldValues } from "react-hook-form"
|
|
3
|
+
|
|
4
|
+
export type FormSearchInputProps<
|
|
5
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
6
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
7
|
+
> = Omit<FormAppInputProps<TFieldValues, TName>, "kind">
|
|
8
|
+
|
|
9
|
+
function FormSearchInput<
|
|
10
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
11
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
12
|
+
>(props: FormSearchInputProps<TFieldValues, TName>) {
|
|
13
|
+
return <FormAppInput {...props} kind="search" />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { FormSearchInput }
|
|
@@ -93,6 +93,7 @@ function FormSelect<
|
|
|
93
93
|
>(props: FormSelectProps<TFieldValues, TName, TValue, TData, TOption>) {
|
|
94
94
|
const shellProps = buildShellProps(props)
|
|
95
95
|
const kind = props.kind ?? "simple"
|
|
96
|
+
const resolvedFieldClassName = props.fieldClassName ?? "w-full"
|
|
96
97
|
|
|
97
98
|
return (
|
|
98
99
|
<Controller
|
|
@@ -139,7 +140,7 @@ function FormSelect<
|
|
|
139
140
|
field.onChange(nextValue ?? emptyValue)
|
|
140
141
|
onValueChange?.(nextValue, option)
|
|
141
142
|
}}
|
|
142
|
-
triggerClassName={fieldClassName}
|
|
143
|
+
triggerClassName={fieldClassName ?? resolvedFieldClassName}
|
|
143
144
|
/>
|
|
144
145
|
</FormFieldShell>
|
|
145
146
|
)
|
|
@@ -180,9 +181,9 @@ function FormSelect<
|
|
|
180
181
|
disabled={disabled || readOnly}
|
|
181
182
|
onValueChange={(nextValue) => {
|
|
182
183
|
field.onChange(nextValue || emptyValue)
|
|
183
|
-
onValueChange?.(nextValue)
|
|
184
|
+
onValueChange?.(nextValue as string)
|
|
184
185
|
}}
|
|
185
|
-
triggerClassName={fieldClassName}
|
|
186
|
+
triggerClassName={fieldClassName ?? resolvedFieldClassName}
|
|
186
187
|
/>
|
|
187
188
|
</FormFieldShell>
|
|
188
189
|
)
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
export * from "./form-field-shell"
|
|
2
|
-
export * from "./form-
|
|
3
|
-
export * from "./form-
|
|
4
|
-
export * from "./form-
|
|
5
|
-
export * from "./form-
|
|
6
|
-
export * from "./form-
|
|
7
|
-
export * from "./form-
|
|
8
|
-
export * from "./form-
|
|
9
|
-
export * from "./form-
|
|
10
|
-
export * from "./form-
|
|
11
|
-
export * from "./form-
|
|
12
|
-
export * from "./form-
|
|
13
|
-
export * from "./form-date-
|
|
14
|
-
export * from "./form-date-range-
|
|
1
|
+
export * from "./form-field-shell"
|
|
2
|
+
export * from "./form-field-utils"
|
|
3
|
+
export * from "./form-app-input"
|
|
4
|
+
export * from "./form-input"
|
|
5
|
+
export * from "./form-select"
|
|
6
|
+
export * from "./form-async-select"
|
|
7
|
+
export * from "./form-textarea"
|
|
8
|
+
export * from "./form-switch"
|
|
9
|
+
export * from "./form-search-input"
|
|
10
|
+
export * from "./form-password-input"
|
|
11
|
+
export * from "./form-number-input"
|
|
12
|
+
export * from "./form-phone-input"
|
|
13
|
+
export * from "./form-date-input"
|
|
14
|
+
export * from "./form-date-range-input"
|
|
15
|
+
export * from "./form-date-picker"
|
|
16
|
+
export * from "./form-date-range-picker"
|
|
@@ -6,14 +6,15 @@ import { cn } from "@/lib/utils"
|
|
|
6
6
|
|
|
7
7
|
/** @deprecated Prefer `FormFamily.Section`/`FormFamily.Actions` composition or `FormFamily.Builder` for new public usage. */
|
|
8
8
|
export type SmartFormSection = {
|
|
9
|
-
key: string
|
|
10
|
-
title?: React.ReactNode
|
|
11
|
-
description?: React.ReactNode
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
key: string
|
|
10
|
+
title?: React.ReactNode
|
|
11
|
+
description?: React.ReactNode
|
|
12
|
+
content?: React.ReactNode
|
|
13
|
+
fields?: React.ReactNode
|
|
14
|
+
children?: React.ReactNode
|
|
15
|
+
columns?: 1 | 2 | 3
|
|
16
|
+
hidden?: boolean
|
|
17
|
+
}
|
|
17
18
|
|
|
18
19
|
/** @deprecated Prefer `FormFamily` entry members for new public usage. */
|
|
19
20
|
export type SmartFormShellProps = React.ComponentProps<"form"> & {
|
|
@@ -45,10 +46,10 @@ function SmartFormShell({ title, description, sections, actions, loading = false
|
|
|
45
46
|
))}
|
|
46
47
|
<div className={cn("grid gap-4", contentClassName)}>
|
|
47
48
|
{visibleSections?.map((section) => renderSection?.(section) ?? (
|
|
48
|
-
<FormSection key={section.key} title={section.title} description={section.description} columns={section.columns}>
|
|
49
|
-
{section.content ?? section.fields}
|
|
50
|
-
</FormSection>
|
|
51
|
-
))}
|
|
49
|
+
<FormSection key={section.key} title={section.title} description={section.description} columns={section.columns}>
|
|
50
|
+
{section.children ?? section.content ?? section.fields}
|
|
51
|
+
</FormSection>
|
|
52
|
+
))}
|
|
52
53
|
{children}
|
|
53
54
|
</div>
|
|
54
55
|
{actions ?? <FormActions loading={loading} disabled={disabled} submitLabel={submitLabel} cancelLabel={cancelLabel} onCancel={onCancel} />}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Input,
|
|
5
|
+
type InputKind,
|
|
6
|
+
type InputProps,
|
|
7
|
+
} from "@/components/ui/input"
|
|
8
|
+
|
|
9
|
+
export type AppInputKind = InputKind
|
|
10
|
+
export type AppInputProps = InputProps
|
|
11
|
+
export type UniversalInputProps = InputProps
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Use {@link Input} with `kind` instead.
|
|
15
|
+
*/
|
|
16
|
+
const AppInput = React.forwardRef<HTMLInputElement | HTMLDivElement, AppInputProps>((props, ref) => {
|
|
17
|
+
return <Input ref={ref as React.ForwardedRef<HTMLInputElement | HTMLDivElement>} {...props} />
|
|
18
|
+
})
|
|
19
|
+
AppInput.displayName = "AppInput"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated Use {@link Input} with `kind` instead.
|
|
23
|
+
*/
|
|
24
|
+
const UniversalInput = AppInput
|
|
25
|
+
|
|
26
|
+
export { AppInput, UniversalInput }
|
|
27
|
+
|
|
@@ -304,7 +304,16 @@ function AsyncStateMessage({
|
|
|
304
304
|
className?: string
|
|
305
305
|
children: React.ReactNode
|
|
306
306
|
}) {
|
|
307
|
-
return
|
|
307
|
+
return (
|
|
308
|
+
<div
|
|
309
|
+
className={cn(
|
|
310
|
+
"rounded-[min(var(--radius-xl),16px)] border border-border/70 bg-[linear-gradient(180deg,color-mix(in_oklch,var(--muted),white_10%),color-mix(in_oklch,var(--muted),transparent_8%))] px-3 py-3 text-sm text-muted-foreground shadow-[inset_0_1px_0_rgba(255,255,255,0.08)]",
|
|
311
|
+
className
|
|
312
|
+
)}
|
|
313
|
+
>
|
|
314
|
+
{children}
|
|
315
|
+
</div>
|
|
316
|
+
)
|
|
308
317
|
}
|
|
309
318
|
|
|
310
319
|
function getOptionLabelText<TValue extends string, TData>(option: AsyncSelectOption<TValue, TData>) {
|
|
@@ -334,14 +343,14 @@ function AsyncOptionButton<
|
|
|
334
343
|
}) {
|
|
335
344
|
return (
|
|
336
345
|
<button
|
|
337
|
-
type="button"
|
|
338
|
-
disabled={option.disabled}
|
|
339
|
-
className={cn(
|
|
340
|
-
"flex w-full items-
|
|
341
|
-
selected && "bg-
|
|
342
|
-
optionClassName
|
|
343
|
-
)}
|
|
344
|
-
onClick={() => onSelect(option)}
|
|
346
|
+
type="button"
|
|
347
|
+
disabled={option.disabled}
|
|
348
|
+
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] hover:border-border/70 hover:bg-accent/60 hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50",
|
|
350
|
+
selected && "border-primary/18 bg-primary/8 text-foreground shadow-[inset_0_1px_0_rgba(255,255,255,0.12)]",
|
|
351
|
+
optionClassName
|
|
352
|
+
)}
|
|
353
|
+
onClick={() => onSelect(option)}
|
|
345
354
|
>
|
|
346
355
|
<span className="flex size-4 shrink-0 items-center justify-center">
|
|
347
356
|
{selected && <CheckIcon className="size-4" />}
|
|
@@ -375,12 +384,12 @@ function AsyncCreateButton({
|
|
|
375
384
|
onCreate: () => void
|
|
376
385
|
}) {
|
|
377
386
|
return (
|
|
378
|
-
<button
|
|
379
|
-
type="button"
|
|
380
|
-
disabled={isCreating}
|
|
381
|
-
className="flex w-full items-center gap-2 rounded-
|
|
382
|
-
onClick={onCreate}
|
|
383
|
-
>
|
|
387
|
+
<button
|
|
388
|
+
type="button"
|
|
389
|
+
disabled={isCreating}
|
|
390
|
+
className="flex w-full items-center gap-2 rounded-[min(var(--radius-xl),16px)] border border-dashed border-border/80 px-3 py-2.5 text-left text-sm outline-none transition-[background-color,border-color,color,box-shadow] hover:border-primary/25 hover:bg-primary/6 hover:text-foreground disabled:pointer-events-none disabled:opacity-50"
|
|
391
|
+
onClick={onCreate}
|
|
392
|
+
>
|
|
384
393
|
<span className="flex size-4 shrink-0 items-center justify-center">
|
|
385
394
|
{isCreating ? <Loader2Icon className="size-4 animate-spin" /> : <PlusIcon className="size-4" />}
|
|
386
395
|
</span>
|
|
@@ -575,14 +584,17 @@ function AsyncSelect<
|
|
|
575
584
|
<PopoverTrigger
|
|
576
585
|
render={
|
|
577
586
|
<Button
|
|
578
|
-
type="button"
|
|
579
|
-
variant="outline"
|
|
580
|
-
disabled={disabled}
|
|
581
|
-
aria-expanded={open}
|
|
582
|
-
className={cn(
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
587
|
+
type="button"
|
|
588
|
+
variant="outline"
|
|
589
|
+
disabled={disabled}
|
|
590
|
+
aria-expanded={open}
|
|
591
|
+
className={cn(
|
|
592
|
+
"min-h-11 w-full justify-between border-border/80 bg-[linear-gradient(180deg,color-mix(in_oklch,var(--background),white_12%),var(--background))] shadow-[inset_0_1px_0_rgba(255,255,255,0.14),0_1px_0_rgba(255,255,255,0.06)]",
|
|
593
|
+
triggerClassName
|
|
594
|
+
)}
|
|
595
|
+
/>
|
|
596
|
+
}
|
|
597
|
+
>
|
|
586
598
|
<span className="min-w-0 flex-1 text-left">
|
|
587
599
|
{currentOption ? (
|
|
588
600
|
<span className="flex min-w-0 flex-col">
|
|
@@ -602,7 +614,7 @@ function AsyncSelect<
|
|
|
602
614
|
<span
|
|
603
615
|
role="button"
|
|
604
616
|
tabIndex={0}
|
|
605
|
-
className="rounded-
|
|
617
|
+
className="rounded-full border border-border/65 p-1 text-muted-foreground transition-colors hover:border-border hover:bg-muted/55 hover:text-foreground"
|
|
606
618
|
aria-label={labels?.clear ?? "Clear"}
|
|
607
619
|
onClick={handleClear}
|
|
608
620
|
onKeyDown={(event) => {
|
|
@@ -618,21 +630,24 @@ function AsyncSelect<
|
|
|
618
630
|
<ChevronsUpDownIcon className="size-4 opacity-60" />
|
|
619
631
|
</span>
|
|
620
632
|
</PopoverTrigger>
|
|
621
|
-
<PopoverContent
|
|
622
|
-
align="start"
|
|
623
|
-
className={cn(
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
633
|
+
<PopoverContent
|
|
634
|
+
align="start"
|
|
635
|
+
className={cn(
|
|
636
|
+
"w-(--anchor-width) gap-3 rounded-[var(--radius-2xl)] border-border/80 bg-[linear-gradient(180deg,color-mix(in_oklch,var(--popover),white_10%),var(--popover))] p-3.5 shadow-[0_24px_80px_rgba(15,23,42,0.18)] backdrop-blur",
|
|
637
|
+
contentClassName
|
|
638
|
+
)}
|
|
639
|
+
>
|
|
640
|
+
<div className="relative">
|
|
641
|
+
<SearchIcon className="pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
642
|
+
<Input
|
|
643
|
+
value={search}
|
|
644
|
+
onChange={(event) => setSearch(event.target.value)}
|
|
645
|
+
placeholder={labels?.searchPlaceholder ?? "Search..."}
|
|
646
|
+
className={cn("border-border/75 bg-[linear-gradient(180deg,color-mix(in_oklch,var(--background),white_12%),var(--background))] pl-8 shadow-[inset_0_1px_0_rgba(255,255,255,0.12),0_1px_0_rgba(255,255,255,0.05)]", searchClassName)}
|
|
647
|
+
/>
|
|
648
|
+
</div>
|
|
649
|
+
|
|
650
|
+
<div className="max-h-64 space-y-1 overflow-y-auto pr-1">
|
|
636
651
|
{searchTooShort && flatOptions.length === 0 && (
|
|
637
652
|
renderMinSearch?.(state) ?? (
|
|
638
653
|
<AsyncStateMessage>
|
|
@@ -677,9 +692,9 @@ function AsyncSelect<
|
|
|
677
692
|
optionGroups.map((group, groupIndex) => (
|
|
678
693
|
<div key={groupIndex}>
|
|
679
694
|
{group.label && (
|
|
680
|
-
<div className="sticky top-0 z-10 bg-popover px-2 py-1 text-xs font-medium text-muted-foreground">
|
|
681
|
-
{group.label}
|
|
682
|
-
</div>
|
|
695
|
+
<div className="sticky top-0 z-10 bg-popover px-2 py-1 text-xs font-medium text-muted-foreground">
|
|
696
|
+
{group.label}
|
|
697
|
+
</div>
|
|
683
698
|
)}
|
|
684
699
|
{group.options.map((option) => (
|
|
685
700
|
<AsyncOptionButton
|
|
@@ -969,7 +984,10 @@ function AsyncMultiSelect<
|
|
|
969
984
|
variant="outline"
|
|
970
985
|
disabled={disabled}
|
|
971
986
|
aria-expanded={open}
|
|
972
|
-
className={cn(
|
|
987
|
+
className={cn(
|
|
988
|
+
"min-h-11 w-full justify-between border-border/80 bg-[linear-gradient(180deg,color-mix(in_oklch,var(--background),white_12%),var(--background))] shadow-[inset_0_1px_0_rgba(255,255,255,0.14),0_1px_0_rgba(255,255,255,0.06)]",
|
|
989
|
+
triggerClassName
|
|
990
|
+
)}
|
|
973
991
|
onKeyDown={handleTriggerKeyDown}
|
|
974
992
|
/>
|
|
975
993
|
}
|
|
@@ -978,12 +996,12 @@ function AsyncMultiSelect<
|
|
|
978
996
|
{currentOptions.length > 0 ? (
|
|
979
997
|
currentOptions.map((option) => (
|
|
980
998
|
<span
|
|
981
|
-
key={option.value}
|
|
982
|
-
className={cn(
|
|
983
|
-
"inline-flex max-w-full items-center gap-1 rounded-
|
|
984
|
-
tagClassName
|
|
985
|
-
)}
|
|
986
|
-
>
|
|
999
|
+
key={option.value}
|
|
1000
|
+
className={cn(
|
|
1001
|
+
"inline-flex max-w-full items-center gap-1 rounded-full border border-border/70 bg-[linear-gradient(180deg,color-mix(in_oklch,var(--muted),white_10%),color-mix(in_oklch,var(--muted),transparent_8%))] px-2 py-1 text-xs text-foreground shadow-[0_1px_0_rgba(255,255,255,0.04)]",
|
|
1002
|
+
tagClassName
|
|
1003
|
+
)}
|
|
1004
|
+
>
|
|
987
1005
|
<span className="flex min-w-0 flex-col">
|
|
988
1006
|
<span className="truncate">
|
|
989
1007
|
{renderTag?.(option, { remove: () => removeOption(option) }) ??
|
|
@@ -998,7 +1016,7 @@ function AsyncMultiSelect<
|
|
|
998
1016
|
<span
|
|
999
1017
|
role="button"
|
|
1000
1018
|
tabIndex={0}
|
|
1001
|
-
className="rounded-
|
|
1019
|
+
className="rounded-full text-muted-foreground transition-colors hover:text-foreground"
|
|
1002
1020
|
aria-label={`Remove ${getOptionLabelText(option)}`}
|
|
1003
1021
|
onClick={(event) => {
|
|
1004
1022
|
event.stopPropagation()
|
|
@@ -1022,7 +1040,7 @@ function AsyncMultiSelect<
|
|
|
1022
1040
|
<span
|
|
1023
1041
|
role="button"
|
|
1024
1042
|
tabIndex={0}
|
|
1025
|
-
className="rounded-
|
|
1043
|
+
className="rounded-full border border-border/65 p-1 text-muted-foreground transition-colors hover:border-border hover:bg-muted/55 hover:text-foreground"
|
|
1026
1044
|
aria-label={labels?.clearAll ?? labels?.clear ?? "Clear all"}
|
|
1027
1045
|
onClick={handleClear}
|
|
1028
1046
|
onKeyDown={(event) => {
|
|
@@ -1038,41 +1056,52 @@ function AsyncMultiSelect<
|
|
|
1038
1056
|
<ChevronsUpDownIcon className="size-4 opacity-60" />
|
|
1039
1057
|
</span>
|
|
1040
1058
|
</PopoverTrigger>
|
|
1041
|
-
<PopoverContent
|
|
1042
|
-
align="start"
|
|
1043
|
-
className={cn(
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
+
<PopoverContent
|
|
1060
|
+
align="start"
|
|
1061
|
+
className={cn(
|
|
1062
|
+
"w-(--anchor-width) gap-3 rounded-[var(--radius-2xl)] border-border/80 bg-[linear-gradient(180deg,color-mix(in_oklch,var(--popover),white_10%),var(--popover))] p-3.5 shadow-[0_24px_80px_rgba(15,23,42,0.18)] backdrop-blur",
|
|
1063
|
+
contentClassName
|
|
1064
|
+
)}
|
|
1065
|
+
>
|
|
1066
|
+
<div className="relative">
|
|
1067
|
+
<SearchIcon className="pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
1068
|
+
<Input
|
|
1069
|
+
value={search}
|
|
1070
|
+
onChange={(event) => setSearch(event.target.value)}
|
|
1071
|
+
placeholder={labels?.searchPlaceholder ?? "Search..."}
|
|
1072
|
+
className={cn("border-border/75 bg-[linear-gradient(180deg,color-mix(in_oklch,var(--background),white_12%),var(--background))] pl-8 shadow-[inset_0_1px_0_rgba(255,255,255,0.12),0_1px_0_rgba(255,255,255,0.05)]", searchClassName)}
|
|
1073
|
+
/>
|
|
1074
|
+
</div>
|
|
1075
|
+
|
|
1076
|
+
<div className="flex flex-wrap items-center justify-between gap-2 rounded-[min(var(--radius-xl),16px)] border border-border/70 bg-[linear-gradient(180deg,color-mix(in_oklch,var(--muted),white_10%),color-mix(in_oklch,var(--muted),transparent_8%))] px-3 py-2 text-xs text-muted-foreground">
|
|
1077
|
+
{hasValue && labels?.selectedCount && <span>{labels.selectedCount(values.length)}</span>}
|
|
1078
|
+
{isMaxReached &&
|
|
1079
|
+
(renderMaxSelected?.(state) ?? (
|
|
1059
1080
|
<span>{labels?.maxSelected?.(maxSelected ?? values.length) ?? `Maximum ${maxSelected} selected`}</span>
|
|
1060
1081
|
))}
|
|
1061
|
-
<div className="ml-auto flex items-center gap-2">
|
|
1062
|
-
{canSelectAll && (
|
|
1063
|
-
<button
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1082
|
+
<div className="ml-auto flex items-center gap-2">
|
|
1083
|
+
{canSelectAll && (
|
|
1084
|
+
<button
|
|
1085
|
+
type="button"
|
|
1086
|
+
className="rounded-full border border-border/75 px-2.5 py-1 font-medium text-foreground transition-colors hover:bg-background"
|
|
1087
|
+
onClick={handleSelectAllVisible}
|
|
1088
|
+
>
|
|
1089
|
+
{labels?.selectAll ?? "Select all"}
|
|
1090
|
+
</button>
|
|
1091
|
+
)}
|
|
1092
|
+
{canClear && (
|
|
1093
|
+
<button
|
|
1094
|
+
type="button"
|
|
1095
|
+
className="rounded-full border border-border/75 px-2.5 py-1 font-medium text-foreground transition-colors hover:bg-background"
|
|
1096
|
+
onClick={() => onValueChange?.([], [])}
|
|
1097
|
+
>
|
|
1098
|
+
{labels?.clearAll ?? labels?.clear ?? "Clear all"}
|
|
1099
|
+
</button>
|
|
1100
|
+
)}
|
|
1101
|
+
</div>
|
|
1102
|
+
</div>
|
|
1103
|
+
|
|
1104
|
+
<div className="max-h-64 space-y-1 overflow-y-auto pr-1">
|
|
1076
1105
|
{searchTooShort && flatOptions.length === 0 && (
|
|
1077
1106
|
renderMinSearch?.(state) ?? (
|
|
1078
1107
|
<AsyncStateMessage>
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { XIcon } from "lucide-react"
|
|
3
|
-
|
|
4
|
-
import { InputDecorator } from "@/components/inputs/input-decorator"
|
|
5
|
-
import { createInputChangeHandler, getInputValue } from "@/components/inputs/input-value"
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { XIcon } from "lucide-react"
|
|
6
3
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
import { InputDecorator } from "@/components/inputs/input-decorator"
|
|
5
|
+
import { createInputChangeHandler, getInputValue } from "@/components/inputs/input-value"
|
|
6
|
+
|
|
7
|
+
export type ClearableInputProps = Omit<
|
|
8
|
+
React.ComponentProps<typeof InputDecorator>,
|
|
9
|
+
"value" | "onChange"
|
|
10
|
+
> & {
|
|
11
|
+
value?: string | number | null
|
|
12
12
|
onChange?: React.ChangeEventHandler<HTMLInputElement>
|
|
13
13
|
onValueChange?: (value: string) => void
|
|
14
14
|
onClear?: () => void
|
|
15
15
|
clearable?: boolean
|
|
16
16
|
clearLabel?: string
|
|
17
|
+
clearOnEscape?: boolean
|
|
18
|
+
focusAfterClear?: boolean
|
|
17
19
|
leadingIcon?: React.ReactNode
|
|
18
20
|
trailing?: React.ReactNode
|
|
19
21
|
wrapperClassName?: string
|
|
@@ -22,57 +24,75 @@ export type ClearableInputProps = Omit<
|
|
|
22
24
|
|
|
23
25
|
const ClearableInput = React.forwardRef<HTMLInputElement, ClearableInputProps>(
|
|
24
26
|
(
|
|
25
|
-
{
|
|
26
|
-
value,
|
|
27
|
-
onChange,
|
|
28
|
-
onValueChange,
|
|
29
|
-
onClear,
|
|
30
|
-
clearable = true,
|
|
31
|
-
clearLabel = "Clear",
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
27
|
+
{
|
|
28
|
+
value,
|
|
29
|
+
onChange,
|
|
30
|
+
onValueChange,
|
|
31
|
+
onClear,
|
|
32
|
+
clearable = true,
|
|
33
|
+
clearLabel = "Clear",
|
|
34
|
+
clearOnEscape = true,
|
|
35
|
+
focusAfterClear = true,
|
|
36
|
+
leadingIcon,
|
|
37
|
+
trailing,
|
|
38
|
+
disabled,
|
|
39
|
+
onKeyDown,
|
|
40
|
+
...props
|
|
41
|
+
},
|
|
42
|
+
ref
|
|
43
|
+
) => {
|
|
44
|
+
const inputRef = React.useRef<HTMLInputElement | null>(null)
|
|
45
|
+
const stringValue = getInputValue(value)
|
|
46
|
+
const canClear = clearable && stringValue.length > 0 && !disabled
|
|
47
|
+
const handleChange = createInputChangeHandler({ onChange, onValueChange })
|
|
48
|
+
|
|
49
|
+
React.useImperativeHandle(ref, () => inputRef.current as HTMLInputElement)
|
|
50
|
+
|
|
51
|
+
const clearValue = () => {
|
|
52
|
+
if (!canClear) return
|
|
53
|
+
onValueChange?.("")
|
|
54
|
+
onClear?.()
|
|
55
|
+
if (focusAfterClear) inputRef.current?.focus()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const handleKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
|
|
59
|
+
onKeyDown?.(event)
|
|
60
|
+
if (event.defaultPrevented) return
|
|
61
|
+
if (clearOnEscape && event.key === "Escape" && canClear) {
|
|
62
|
+
event.preventDefault()
|
|
63
|
+
clearValue()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<InputDecorator
|
|
69
|
+
data-slot="clearable-input"
|
|
70
|
+
ref={inputRef}
|
|
71
|
+
value={stringValue}
|
|
72
|
+
disabled={disabled}
|
|
73
|
+
onChange={handleChange}
|
|
74
|
+
onKeyDown={handleKeyDown}
|
|
75
|
+
leading={leadingIcon}
|
|
76
|
+
trailing={
|
|
77
|
+
<>
|
|
78
|
+
{trailing}
|
|
79
|
+
{canClear && (
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
aria-label={clearLabel}
|
|
83
|
+
className="rounded-sm p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
84
|
+
onClick={clearValue}
|
|
85
|
+
>
|
|
86
|
+
<XIcon className="size-4" />
|
|
87
|
+
</button>
|
|
88
|
+
)}
|
|
89
|
+
</>
|
|
90
|
+
}
|
|
91
|
+
{...props}
|
|
92
|
+
/>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
)
|
|
76
96
|
ClearableInput.displayName = "ClearableInput"
|
|
77
97
|
|
|
78
98
|
export { ClearableInput }
|