tembro 2.0.5 → 2.0.6
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/components/data-table/data-table-pagination.cjs +1 -1
- package/dist/components/data-table/data-table-pagination.js +4 -4
- package/dist/components/data-table/data-table.cjs +1 -1
- package/dist/components/data-table/data-table.d.ts +2 -2
- package/dist/components/data-table/data-table.js +2 -1
- package/dist/components/display/carousel.cjs +1 -1
- package/dist/components/display/carousel.d.ts +7 -1
- package/dist/components/display/carousel.js +44 -44
- package/dist/components/display/smart-card.d.ts +1 -1
- package/dist/components/display/status-legend.d.ts +1 -1
- package/dist/components/form/form-input.cjs +1 -1
- package/dist/components/form/form-input.d.ts +3 -4
- package/dist/components/form/form-input.js +257 -257
- package/dist/components/form/form-select.cjs +1 -1
- package/dist/components/form/form-select.d.ts +2 -2
- package/dist/components/form/form-select.js +6 -6
- package/dist/components/inputs/async-select.d.ts +3 -0
- package/dist/components/inputs/search-input.d.ts +3 -0
- package/dist/components/inputs/simple-select.cjs +1 -1
- package/dist/components/inputs/simple-select.d.ts +10 -32
- package/dist/components/inputs/simple-select.js +5 -101
- package/dist/components/ui/card/index.cjs +1 -1
- package/dist/components/ui/card/index.d.ts +13 -2
- package/dist/components/ui/card/index.js +74 -49
- package/dist/components/ui/input/index.cjs +1 -1
- package/dist/components/ui/input/index.d.ts +8 -0
- package/dist/components/ui/input/index.js +63 -30
- package/dist/components/ui/select/index.cjs +1 -1
- package/dist/components/ui/select/index.d.ts +33 -1
- package/dist/components/ui/select/index.js +154 -48
- package/dist/showcase/create-demo.cjs +5 -4
- package/dist/showcase/create-demo.js +2 -2
- package/dist/showcase/premium/data-table/showcase.cjs +1 -1
- package/dist/showcase/premium/data-table/showcase.js +1 -1
- package/dist/showcase/premium/data-table-parts/showcase.cjs +1 -1
- package/dist/showcase/premium/data-table-parts/showcase.js +3 -1
- package/dist/showcase/premium/select/mock.cjs +8 -9
- package/dist/showcase/premium/select/mock.js +1 -1
- package/dist/showcase/premium/select/showcase.cjs +1 -1
- package/dist/showcase/premium/select/showcase.js +76 -86
- package/dist/showcase/registry-specific.cjs +1 -1
- package/dist/showcase/registry-specific.js +78 -74
- package/dist/showcase/render-registry-preview.cjs +1 -1
- package/dist/showcase/render-registry-preview.js +91 -87
- package/dist/showcase/tembro-registry.json.cjs +1 -1
- package/dist/showcase/tembro-registry.json.js +1 -1
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +1 -1
- package/packages/cli/vendor/src/components/data-table/data-table-pagination.tsx +7 -7
- package/packages/cli/vendor/src/components/data-table/data-table.tsx +21 -20
- package/packages/cli/vendor/src/components/display/carousel.tsx +18 -6
- package/packages/cli/vendor/src/components/display/smart-card.tsx +1 -1
- package/packages/cli/vendor/src/components/display/status-legend.tsx +1 -1
- package/packages/cli/vendor/src/components/form/form-input.tsx +9 -12
- package/packages/cli/vendor/src/components/form/form-select.tsx +4 -4
- package/packages/cli/vendor/src/components/inputs/async-select.tsx +11 -8
- package/packages/cli/vendor/src/components/inputs/search-input.tsx +5 -2
- package/packages/cli/vendor/src/components/inputs/simple-select.tsx +20 -190
- package/packages/cli/vendor/src/components/ui/card/index.tsx +95 -25
- package/packages/cli/vendor/src/components/ui/input/index.tsx +98 -0
- package/packages/cli/vendor/src/components/ui/select/index.tsx +227 -34
- package/packages/cli/vendor/src/showcase/create-demo.tsx +16 -15
- package/packages/cli/vendor/src/showcase/premium/data-table/showcase.tsx +3 -3
- package/packages/cli/vendor/src/showcase/premium/data-table-parts/showcase.tsx +3 -3
- package/packages/cli/vendor/src/showcase/premium/select/mock.ts +1 -1
- package/packages/cli/vendor/src/showcase/premium/select/showcase.tsx +13 -21
- package/packages/cli/vendor/src/showcase/registry-specific.tsx +7 -8
- package/packages/cli/vendor/src/showcase/render-registry-preview.tsx +5 -6
- package/packages/cli/vendor/src/showcase/tembro-registry.json +1 -1
- package/registry.json +1 -1
|
@@ -25,6 +25,12 @@ export type CarouselProps = React.ComponentProps<"div"> & {
|
|
|
25
25
|
playLabel?: string
|
|
26
26
|
pauseLabel?: string
|
|
27
27
|
onAutoplayChange?: (playing: boolean) => void
|
|
28
|
+
viewportClassName?: string
|
|
29
|
+
controlsClassName?: string
|
|
30
|
+
dotsClassName?: string
|
|
31
|
+
arrowClassName?: string
|
|
32
|
+
dotClassName?: string
|
|
33
|
+
activeDotClassName?: string
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
export type CarouselItemProps = React.ComponentProps<"div">
|
|
@@ -56,6 +62,12 @@ function Carousel({
|
|
|
56
62
|
playLabel = "Start autoplay",
|
|
57
63
|
pauseLabel = "Pause autoplay",
|
|
58
64
|
onAutoplayChange,
|
|
65
|
+
viewportClassName,
|
|
66
|
+
controlsClassName,
|
|
67
|
+
dotsClassName,
|
|
68
|
+
arrowClassName,
|
|
69
|
+
dotClassName,
|
|
70
|
+
activeDotClassName,
|
|
59
71
|
className,
|
|
60
72
|
children,
|
|
61
73
|
...props
|
|
@@ -151,7 +163,7 @@ function Carousel({
|
|
|
151
163
|
"relative overflow-hidden rounded-[var(--aui-card-radius,var(--radius-lg))] border border-[color:var(--aui-card-border,var(--border))] bg-card shadow-[var(--aui-card-shadow,0_12px_32px_rgba(15,23,42,0.08))]",
|
|
152
164
|
variant === "hero" && "min-h-72",
|
|
153
165
|
variant === "minimal" && "rounded-[var(--radius-md)] shadow-none"
|
|
154
|
-
)}>
|
|
166
|
+
, viewportClassName)}>
|
|
155
167
|
<div className="transition-transform duration-300 ease-out" aria-live="polite">
|
|
156
168
|
{items[activeIndex]}
|
|
157
169
|
</div>
|
|
@@ -161,7 +173,7 @@ function Carousel({
|
|
|
161
173
|
type="button"
|
|
162
174
|
variant={variant === "hero" ? "default" : "secondary"}
|
|
163
175
|
size="icon-sm"
|
|
164
|
-
className="absolute left-3 top-1/2 -translate-y-1/2 rounded-full shadow-lg"
|
|
176
|
+
className={cn("absolute left-3 top-1/2 -translate-y-1/2 rounded-full shadow-lg", arrowClassName)}
|
|
165
177
|
disabled={!canGoPrevious}
|
|
166
178
|
aria-label={previousLabel}
|
|
167
179
|
onClick={() => setActiveIndex(activeIndex - 1)}
|
|
@@ -172,7 +184,7 @@ function Carousel({
|
|
|
172
184
|
type="button"
|
|
173
185
|
variant={variant === "hero" ? "default" : "secondary"}
|
|
174
186
|
size="icon-sm"
|
|
175
|
-
className="absolute right-3 top-1/2 -translate-y-1/2 rounded-full shadow-lg"
|
|
187
|
+
className={cn("absolute right-3 top-1/2 -translate-y-1/2 rounded-full shadow-lg", arrowClassName)}
|
|
176
188
|
disabled={!canGoNext}
|
|
177
189
|
aria-label={nextLabel}
|
|
178
190
|
onClick={() => setActiveIndex(activeIndex + 1)}
|
|
@@ -183,7 +195,7 @@ function Carousel({
|
|
|
183
195
|
) : null}
|
|
184
196
|
</div>
|
|
185
197
|
{showDots && items.length > 1 && (
|
|
186
|
-
<div className="flex flex-wrap items-center justify-center gap-2" role="tablist" aria-label={`${ariaLabel} slides`}>
|
|
198
|
+
<div className={cn("flex flex-wrap items-center justify-center gap-2", dotsClassName)} role="tablist" aria-label={`${ariaLabel} slides`}>
|
|
187
199
|
{items.map((_, itemIndex) => (
|
|
188
200
|
<button
|
|
189
201
|
key={itemIndex}
|
|
@@ -193,7 +205,7 @@ function Carousel({
|
|
|
193
205
|
aria-selected={itemIndex === activeIndex}
|
|
194
206
|
className={cn(
|
|
195
207
|
"h-2.5 rounded-full bg-muted-foreground/30 transition-[width,background-color,opacity] hover:bg-muted-foreground/55",
|
|
196
|
-
itemIndex === activeIndex ? "w-7 bg-primary" : "w-2.5"
|
|
208
|
+
itemIndex === activeIndex ? cn("w-7 bg-primary", activeDotClassName) : cn("w-2.5", dotClassName)
|
|
197
209
|
)}
|
|
198
210
|
onClick={() => setActiveIndex(itemIndex)}
|
|
199
211
|
/>
|
|
@@ -203,7 +215,7 @@ function Carousel({
|
|
|
203
215
|
type="button"
|
|
204
216
|
variant="ghost"
|
|
205
217
|
size="sm"
|
|
206
|
-
className="ml-2 rounded-full"
|
|
218
|
+
className={cn("ml-2 rounded-full", controlsClassName)}
|
|
207
219
|
onClick={() => {
|
|
208
220
|
const nextPlaying = !autoplayEnabled
|
|
209
221
|
setAutoplayStopped(false)
|
|
@@ -38,7 +38,7 @@ export type InfoCardRenderContext = {
|
|
|
38
38
|
footer?: React.ReactNode
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export type InfoCardProps = Omit<React.ComponentProps<typeof Card>, "title" | "content" | "size"> & InfoCardRenderContext & {
|
|
41
|
+
export type InfoCardProps = Omit<React.ComponentProps<typeof Card>, "title" | "content" | "size" | "orientation"> & InfoCardRenderContext & {
|
|
42
42
|
orientation?: InfoCardOrientation
|
|
43
43
|
variant?: InfoCardVariant
|
|
44
44
|
size?: InfoCardSize
|
|
@@ -18,7 +18,7 @@ export type StatusLegendItem = {
|
|
|
18
18
|
className?: string
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export type StatusLegendProps = React.ComponentProps<typeof Card> & {
|
|
21
|
+
export type StatusLegendProps = Omit<React.ComponentProps<typeof Card>, "orientation"> & {
|
|
22
22
|
title?: React.ReactNode
|
|
23
23
|
description?: React.ReactNode
|
|
24
24
|
actions?: React.ReactNode
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type DateRangeValue,
|
|
9
9
|
} from "@/components/inputs/date-range-input"
|
|
10
10
|
import { DateInput, type DateInputProps } from "@/components/inputs/date-input"
|
|
11
|
-
import { Input, type InputTextProps } from "@/components/ui/input"
|
|
11
|
+
import { Input, type InputClearableProps, type InputSearchProps, type InputTextProps } from "@/components/ui/input"
|
|
12
12
|
import { MaskedInput, type MaskedInputProps } from "@/components/inputs/masked-input"
|
|
13
13
|
import { MoneyInput, type MoneyInputProps } from "@/components/inputs/money-input"
|
|
14
14
|
import { NumberInput, type NumberInputProps } from "@/components/inputs/number-input"
|
|
@@ -18,11 +18,6 @@ import {
|
|
|
18
18
|
type PhoneInputProps,
|
|
19
19
|
} from "@/components/inputs/phone-input"
|
|
20
20
|
import { QuantityInput, type QuantityInputProps } from "@/components/inputs/quantity-input"
|
|
21
|
-
import { SearchInput, type SearchInputProps } from "@/components/inputs/search-input"
|
|
22
|
-
import {
|
|
23
|
-
ClearableInput,
|
|
24
|
-
type ClearableInputProps,
|
|
25
|
-
} from "@/components/inputs/clearable-input"
|
|
26
21
|
import {
|
|
27
22
|
FormFieldShell,
|
|
28
23
|
type FormFieldShellControlProps,
|
|
@@ -76,7 +71,7 @@ export type FormTextInputProps<
|
|
|
76
71
|
export type FormInputSearchVariantProps<
|
|
77
72
|
TFieldValues extends FieldValues = FieldValues,
|
|
78
73
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
79
|
-
> = Omit<
|
|
74
|
+
> = Omit<InputSearchProps, "name" | "value" | "defaultValue" | "onValueChange" | "kind"> &
|
|
80
75
|
FormControlledFieldProps<TFieldValues, TName> & {
|
|
81
76
|
kind: "search"
|
|
82
77
|
onValueChange?: (value: string) => void
|
|
@@ -134,7 +129,7 @@ export type FormInputDateRangeVariantProps<
|
|
|
134
129
|
export type FormInputClearableVariantProps<
|
|
135
130
|
TFieldValues extends FieldValues = FieldValues,
|
|
136
131
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
137
|
-
> = Omit<
|
|
132
|
+
> = Omit<InputClearableProps, "name" | "value" | "defaultValue" | "onValueChange" | "kind"> &
|
|
138
133
|
FormControlledFieldProps<TFieldValues, TName> & {
|
|
139
134
|
kind: "clearable"
|
|
140
135
|
emptyValue?: unknown
|
|
@@ -275,8 +270,9 @@ function FormInput<
|
|
|
275
270
|
error={error}
|
|
276
271
|
htmlFor={id ?? inputId}
|
|
277
272
|
>
|
|
278
|
-
<
|
|
279
|
-
{...searchProps}
|
|
273
|
+
<Input
|
|
274
|
+
{...(searchProps as Omit<InputSearchProps, "kind">)}
|
|
275
|
+
type="search"
|
|
280
276
|
id={id ?? inputId}
|
|
281
277
|
name={field.name}
|
|
282
278
|
ref={field.ref}
|
|
@@ -605,8 +601,9 @@ function FormInput<
|
|
|
605
601
|
error={error}
|
|
606
602
|
htmlFor={id ?? inputId}
|
|
607
603
|
>
|
|
608
|
-
<
|
|
609
|
-
{...clearableProps}
|
|
604
|
+
<Input
|
|
605
|
+
{...(clearableProps as Omit<InputClearableProps, "kind">)}
|
|
606
|
+
clearable
|
|
610
607
|
id={id ?? inputId}
|
|
611
608
|
name={field.name}
|
|
612
609
|
ref={field.ref}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type AsyncSelectOption,
|
|
7
7
|
type AsyncSelectProps,
|
|
8
8
|
} from "@/components/inputs/async-select"
|
|
9
|
-
import {
|
|
9
|
+
import { Select, type SelectProps } from "@/components/ui/select"
|
|
10
10
|
import {
|
|
11
11
|
FormFieldShell,
|
|
12
12
|
type FormFieldShellControlProps,
|
|
@@ -27,7 +27,7 @@ type FormSelectBaseProps<
|
|
|
27
27
|
export type FormSimpleSelectProps<
|
|
28
28
|
TFieldValues extends FieldValues = FieldValues,
|
|
29
29
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
30
|
-
> = Omit<
|
|
30
|
+
> = Omit<SelectProps, "children" | "value" | "onValueChange" | "disabled"> &
|
|
31
31
|
FormSelectBaseProps<TFieldValues, TName> & {
|
|
32
32
|
kind?: "simple"
|
|
33
33
|
emptyValue?: unknown
|
|
@@ -196,9 +196,9 @@ function FormSelect<
|
|
|
196
196
|
errorId={resolvedIds.errorId}
|
|
197
197
|
error={error}
|
|
198
198
|
>
|
|
199
|
-
<
|
|
199
|
+
<Select
|
|
200
200
|
{...simpleProps}
|
|
201
|
-
value={field.value == null ?
|
|
201
|
+
value={field.value == null ? undefined : String(field.value)}
|
|
202
202
|
disabled={disabled || readOnly}
|
|
203
203
|
aria-describedby={resolvedIds.describedBy}
|
|
204
204
|
aria-errormessage={error ? resolvedIds.errorId : undefined}
|
|
@@ -109,11 +109,11 @@ export type AsyncSelectMultiModeProps<
|
|
|
109
109
|
isMulti: true
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
export type AsyncMultiSelectProps<
|
|
113
|
-
TValue extends string = string,
|
|
114
|
-
TData = unknown,
|
|
115
|
-
TOption extends AsyncSelectOption<TValue, TData> = AsyncSelectOption<TValue, TData>,
|
|
116
|
-
> = Omit<React.ComponentProps<"div">, "onChange"> & {
|
|
112
|
+
export type AsyncMultiSelectProps<
|
|
113
|
+
TValue extends string = string,
|
|
114
|
+
TData = unknown,
|
|
115
|
+
TOption extends AsyncSelectOption<TValue, TData> = AsyncSelectOption<TValue, TData>,
|
|
116
|
+
> = Omit<React.ComponentProps<"div">, "onChange"> & {
|
|
117
117
|
value?: TValue[]
|
|
118
118
|
selectedOptions?: TOption[]
|
|
119
119
|
onValueChange?: (value: TValue[], options: TOption[]) => void
|
|
@@ -148,8 +148,8 @@ export type AsyncMultiSelectProps<
|
|
|
148
148
|
searchClassName?: string
|
|
149
149
|
optionClassName?: string
|
|
150
150
|
tagClassName?: string
|
|
151
|
-
invalid?: boolean
|
|
152
|
-
}
|
|
151
|
+
invalid?: boolean
|
|
152
|
+
}
|
|
153
153
|
|
|
154
154
|
type AsyncSelectCacheEntry<
|
|
155
155
|
TValue extends string,
|
|
@@ -778,7 +778,10 @@ function AsyncSelect<
|
|
|
778
778
|
)
|
|
779
779
|
}
|
|
780
780
|
|
|
781
|
-
|
|
781
|
+
/**
|
|
782
|
+
* @deprecated Prefer `AsyncSelect` with `isMulti` for new usage.
|
|
783
|
+
*/
|
|
784
|
+
function AsyncMultiSelect<
|
|
782
785
|
TValue extends string = string,
|
|
783
786
|
TData = unknown,
|
|
784
787
|
TOption extends AsyncSelectOption<TValue, TData> = AsyncSelectOption<TValue, TData>,
|
|
@@ -14,8 +14,11 @@ export type SearchInputProps = Omit<ClearableInputProps, "leadingIcon" | "type"
|
|
|
14
14
|
onValueChange?: (value: string) => void
|
|
15
15
|
onDebouncedValueChange?: (value: string) => void
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @deprecated Prefer `Input` with `type="search"` and search-related props for new usage.
|
|
20
|
+
*/
|
|
21
|
+
const SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(
|
|
19
22
|
(
|
|
20
23
|
{
|
|
21
24
|
searchIcon,
|
|
@@ -1,193 +1,23 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { CheckIcon, LoaderCircleIcon, SearchIcon, XIcon } from "lucide-react"
|
|
3
|
-
|
|
4
1
|
import {
|
|
5
2
|
Select,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
SelectTrigger,
|
|
9
|
-
SelectValue,
|
|
3
|
+
type SelectOption,
|
|
4
|
+
type SelectProps,
|
|
10
5
|
} from "@/components/ui/select"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
searchPlaceholder?: string
|
|
30
|
-
emptyLabel?: React.ReactNode
|
|
31
|
-
clearLabel?: string
|
|
32
|
-
size?: "sm" | "default" | "lg"
|
|
33
|
-
clearable?: boolean
|
|
34
|
-
searchable?: boolean
|
|
35
|
-
loading?: boolean
|
|
36
|
-
loadingLabel?: React.ReactNode
|
|
37
|
-
disabled?: boolean
|
|
38
|
-
showSelectedDescription?: boolean
|
|
39
|
-
triggerClassName?: string
|
|
40
|
-
contentClassName?: string
|
|
41
|
-
itemClassName?: string
|
|
42
|
-
searchClassName?: string
|
|
43
|
-
renderOption?: (option: SimpleSelectOption, state: { selected: boolean }) => React.ReactNode
|
|
44
|
-
invalid?: boolean
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function optionMatchesSearch(option: SimpleSelectOption, search: string) {
|
|
48
|
-
const normalized = search.trim().toLowerCase()
|
|
49
|
-
if (!normalized) return true
|
|
50
|
-
|
|
51
|
-
const labelText = typeof option.label === "string" || typeof option.label === "number" ? String(option.label) : option.value
|
|
52
|
-
const haystack = [labelText, option.value, ...(option.keywords ?? [])].join(" ").toLowerCase()
|
|
53
|
-
return haystack.includes(normalized)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function SimpleSelect({
|
|
57
|
-
value,
|
|
58
|
-
onValueChange,
|
|
59
|
-
options,
|
|
60
|
-
placeholder = "Select",
|
|
61
|
-
searchPlaceholder = "Search options...",
|
|
62
|
-
emptyLabel = "No options found",
|
|
63
|
-
clearLabel = "Clear selection",
|
|
64
|
-
size = "default",
|
|
65
|
-
clearable = false,
|
|
66
|
-
searchable = false,
|
|
67
|
-
loading = false,
|
|
68
|
-
loadingLabel = "Loading options...",
|
|
69
|
-
disabled = false,
|
|
70
|
-
showSelectedDescription = true,
|
|
71
|
-
triggerClassName,
|
|
72
|
-
contentClassName,
|
|
73
|
-
itemClassName,
|
|
74
|
-
searchClassName,
|
|
75
|
-
renderOption,
|
|
76
|
-
invalid,
|
|
77
|
-
onOpenChange,
|
|
78
|
-
...props
|
|
79
|
-
}: SimpleSelectProps) {
|
|
80
|
-
const [search, setSearch] = React.useState("")
|
|
81
|
-
const selectedOption = options.find((option) => option.value === value)
|
|
82
|
-
const filteredOptions = options.filter((option) => optionMatchesSearch(option, search))
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<Select
|
|
86
|
-
value={value}
|
|
87
|
-
onValueChange={(val) => onValueChange?.(val as string)}
|
|
88
|
-
disabled={disabled || loading}
|
|
89
|
-
onOpenChange={(open, eventDetails) => {
|
|
90
|
-
if (!open) setSearch("")
|
|
91
|
-
onOpenChange?.(open, eventDetails)
|
|
92
|
-
}}
|
|
93
|
-
{...props}
|
|
94
|
-
>
|
|
95
|
-
<SelectTrigger
|
|
96
|
-
size={size}
|
|
97
|
-
aria-invalid={invalid || undefined}
|
|
98
|
-
className={cn(
|
|
99
|
-
"w-full",
|
|
100
|
-
triggerClassName
|
|
101
|
-
)}
|
|
102
|
-
>
|
|
103
|
-
<SelectValue placeholder={placeholder}>
|
|
104
|
-
{selectedOption ? (
|
|
105
|
-
<span className="flex min-w-0 flex-col items-start">
|
|
106
|
-
<span className="truncate">{selectedOption.label}</span>
|
|
107
|
-
{showSelectedDescription && selectedOption.description ? (
|
|
108
|
-
<span className="truncate text-[11px] font-medium text-muted-foreground">{selectedOption.description}</span>
|
|
109
|
-
) : null}
|
|
110
|
-
</span>
|
|
111
|
-
) : null}
|
|
112
|
-
</SelectValue>
|
|
113
|
-
{loading ? <LoaderCircleIcon className="size-4 animate-spin text-muted-foreground" /> : null}
|
|
114
|
-
{clearable && value && !disabled && !loading ? (
|
|
115
|
-
<span
|
|
116
|
-
role="button"
|
|
117
|
-
tabIndex={0}
|
|
118
|
-
aria-label={clearLabel}
|
|
119
|
-
className="ml-1 rounded-[var(--radius-sm)] border border-border/65 p-1 text-muted-foreground transition-colors hover:border-border hover:bg-muted/55 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
120
|
-
onClick={(event) => {
|
|
121
|
-
event.preventDefault()
|
|
122
|
-
stopInteractivePropagation(event)
|
|
123
|
-
onValueChange?.(undefined)
|
|
124
|
-
}}
|
|
125
|
-
onMouseDown={stopInteractivePropagation}
|
|
126
|
-
onDoubleClick={stopInteractivePropagation}
|
|
127
|
-
onKeyDown={(event) => {
|
|
128
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
129
|
-
event.preventDefault()
|
|
130
|
-
onValueChange?.(undefined)
|
|
131
|
-
}
|
|
132
|
-
}}
|
|
133
|
-
>
|
|
134
|
-
<XIcon className="size-3.5" />
|
|
135
|
-
</span>
|
|
136
|
-
) : null}
|
|
137
|
-
</SelectTrigger>
|
|
138
|
-
<SelectContent
|
|
139
|
-
className={cn(
|
|
140
|
-
"w-(--anchor-width)",
|
|
141
|
-
contentClassName
|
|
142
|
-
)}
|
|
143
|
-
>
|
|
144
|
-
{searchable ? (
|
|
145
|
-
<div data-slot="select-search" className="sticky top-0 z-10 mb-1 flex items-center gap-2 rounded-[var(--radius-md)] border border-[color:var(--aui-card-border,var(--border))] bg-popover px-2.5 py-2 text-sm">
|
|
146
|
-
<SearchIcon className="size-4 text-muted-foreground" />
|
|
147
|
-
<input
|
|
148
|
-
value={search}
|
|
149
|
-
onChange={(event) => setSearch(event.target.value)}
|
|
150
|
-
placeholder={searchPlaceholder}
|
|
151
|
-
className={cn("min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted-foreground", searchClassName)}
|
|
152
|
-
/>
|
|
153
|
-
</div>
|
|
154
|
-
) : null}
|
|
155
|
-
|
|
156
|
-
{loading ? (
|
|
157
|
-
<div data-slot="select-state" className="flex items-center gap-2 rounded-[var(--radius-md)] border border-[color:var(--aui-card-border,var(--border))] bg-[color:color-mix(in_oklch,var(--muted),transparent_55%)] px-3 py-2.5 text-sm text-muted-foreground">
|
|
158
|
-
<LoaderCircleIcon className="size-4 animate-spin" />
|
|
159
|
-
{loadingLabel}
|
|
160
|
-
</div>
|
|
161
|
-
) : filteredOptions.length === 0 ? (
|
|
162
|
-
<div data-slot="select-state" className="rounded-[var(--radius-md)] border border-[color:var(--aui-card-border,var(--border))] bg-[color:color-mix(in_oklch,var(--muted),transparent_55%)] px-3 py-2.5 text-sm text-muted-foreground">{emptyLabel}</div>
|
|
163
|
-
) : (
|
|
164
|
-
filteredOptions.map((option) => {
|
|
165
|
-
const selected = option.value === value
|
|
166
|
-
return (
|
|
167
|
-
<SelectItem
|
|
168
|
-
key={option.value}
|
|
169
|
-
value={option.value}
|
|
170
|
-
disabled={option.disabled}
|
|
171
|
-
className={cn("rounded-[var(--radius-md)]", itemClassName)}
|
|
172
|
-
>
|
|
173
|
-
{renderOption ? (
|
|
174
|
-
renderOption(option, { selected })
|
|
175
|
-
) : (
|
|
176
|
-
<span className="flex min-w-0 flex-1 flex-col">
|
|
177
|
-
<span className="flex min-w-0 items-center gap-2">
|
|
178
|
-
<span className="truncate">{option.label}</span>
|
|
179
|
-
{selected ? <CheckIcon className="ml-auto size-3.5 text-primary" /> : null}
|
|
180
|
-
</span>
|
|
181
|
-
{option.description && <span className="truncate text-xs text-muted-foreground">{option.description}</span>}
|
|
182
|
-
</span>
|
|
183
|
-
)}
|
|
184
|
-
</SelectItem>
|
|
185
|
-
)
|
|
186
|
-
})
|
|
187
|
-
)}
|
|
188
|
-
</SelectContent>
|
|
189
|
-
</Select>
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export { SimpleSelect }
|
|
6
|
+
|
|
7
|
+
export type SimpleSelectOption = SelectOption
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @deprecated Prefer `Select` with `options` for new usage.
|
|
11
|
+
*/
|
|
12
|
+
export type SimpleSelectProps = Omit<SelectProps, "children"> & {
|
|
13
|
+
options: SimpleSelectOption[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Prefer `Select` with `options` for new usage.
|
|
18
|
+
*/
|
|
19
|
+
function SimpleSelect(props: SimpleSelectProps) {
|
|
20
|
+
return <Select {...props} />
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { SimpleSelect }
|
|
@@ -65,14 +65,28 @@ type CardSurfaceSlots = {
|
|
|
65
65
|
media?: React.ReactNode
|
|
66
66
|
content?: React.ReactNode
|
|
67
67
|
footer?: React.ReactNode
|
|
68
|
+
mediaClassName?: string
|
|
69
|
+
bodyClassName?: string
|
|
68
70
|
headerClassName?: string
|
|
71
|
+
eyebrowClassName?: string
|
|
69
72
|
titleClassName?: string
|
|
70
73
|
descriptionClassName?: string
|
|
71
74
|
contentClassName?: string
|
|
72
75
|
footerClassName?: string
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
export type
|
|
78
|
+
export type CardOrientation = "vertical" | "horizontal"
|
|
79
|
+
export type CardMediaAspect = "auto" | "square" | "video" | "poster" | "wide"
|
|
80
|
+
export type CardPadding = "none" | "sm" | "default" | "lg"
|
|
81
|
+
|
|
82
|
+
export type CardProps = Omit<React.ComponentProps<"div">, keyof CardSurfaceSlots> &
|
|
83
|
+
VariantProps<typeof cardVariants> &
|
|
84
|
+
CardSurfaceSlots & {
|
|
85
|
+
orientation?: CardOrientation
|
|
86
|
+
mediaPosition?: "start" | "end"
|
|
87
|
+
mediaAspect?: CardMediaAspect
|
|
88
|
+
padding?: CardPadding
|
|
89
|
+
}
|
|
76
90
|
|
|
77
91
|
function hasSurfaceContent(props: CardSurfaceSlots) {
|
|
78
92
|
return (
|
|
@@ -96,6 +110,10 @@ function Card({
|
|
|
96
110
|
interactive,
|
|
97
111
|
selected,
|
|
98
112
|
disabled,
|
|
113
|
+
orientation = "vertical",
|
|
114
|
+
mediaPosition = "start",
|
|
115
|
+
mediaAspect = "auto",
|
|
116
|
+
padding = "default",
|
|
99
117
|
tabIndex,
|
|
100
118
|
eyebrow,
|
|
101
119
|
title,
|
|
@@ -105,7 +123,10 @@ function Card({
|
|
|
105
123
|
media,
|
|
106
124
|
content,
|
|
107
125
|
footer,
|
|
126
|
+
mediaClassName,
|
|
127
|
+
bodyClassName,
|
|
108
128
|
headerClassName,
|
|
129
|
+
eyebrowClassName,
|
|
109
130
|
titleClassName,
|
|
110
131
|
descriptionClassName,
|
|
111
132
|
contentClassName,
|
|
@@ -126,6 +147,36 @@ function Card({
|
|
|
126
147
|
const resolvedContent = content ?? children
|
|
127
148
|
const hasHeader = eyebrow != null || title != null || description != null || badge != null || action != null
|
|
128
149
|
const hasFooter = footer != null
|
|
150
|
+
const mediaNode = media ? (
|
|
151
|
+
<div
|
|
152
|
+
data-slot="card-media"
|
|
153
|
+
data-media-position={mediaPosition}
|
|
154
|
+
data-media-aspect={mediaAspect}
|
|
155
|
+
className={cn(
|
|
156
|
+
"overflow-hidden",
|
|
157
|
+
orientation === "horizontal" ? "shrink-0 self-stretch" : "",
|
|
158
|
+
mediaAspect === "square" && (orientation === "horizontal" ? "w-40" : "aspect-square"),
|
|
159
|
+
mediaAspect === "video" && (orientation === "horizontal" ? "w-56" : "aspect-video"),
|
|
160
|
+
mediaAspect === "poster" && (orientation === "horizontal" ? "w-36" : "aspect-[3/4]"),
|
|
161
|
+
mediaAspect === "wide" && (orientation === "horizontal" ? "w-72" : "aspect-[16/7]"),
|
|
162
|
+
mediaClassName
|
|
163
|
+
)}
|
|
164
|
+
>
|
|
165
|
+
{media}
|
|
166
|
+
</div>
|
|
167
|
+
) : null
|
|
168
|
+
const surfacePaddingClassName = {
|
|
169
|
+
none: "",
|
|
170
|
+
sm: "px-4",
|
|
171
|
+
default: "px-(--card-spacing)",
|
|
172
|
+
lg: "px-6",
|
|
173
|
+
} satisfies Record<CardPadding, string>
|
|
174
|
+
const bodyPaddingClassName = {
|
|
175
|
+
none: "",
|
|
176
|
+
sm: "pb-4",
|
|
177
|
+
default: "",
|
|
178
|
+
lg: "pb-6",
|
|
179
|
+
} satisfies Record<CardPadding, string>
|
|
129
180
|
|
|
130
181
|
return (
|
|
131
182
|
<div
|
|
@@ -139,37 +190,56 @@ function Card({
|
|
|
139
190
|
data-selected={selected || undefined}
|
|
140
191
|
data-disabled={disabled || undefined}
|
|
141
192
|
data-has-footer={hasFooter || undefined}
|
|
193
|
+
data-orientation={orientation}
|
|
194
|
+
data-padding={padding}
|
|
142
195
|
aria-disabled={disabled || undefined}
|
|
143
196
|
tabIndex={interactive && !disabled ? tabIndex ?? 0 : tabIndex}
|
|
144
|
-
className={cn(
|
|
197
|
+
className={cn(
|
|
198
|
+
cardVariants({ variant, size, density, tone, interactive, selected, disabled }),
|
|
199
|
+
orientation === "horizontal" && "flex-row py-0",
|
|
200
|
+
interactive && "hover:border-[color:var(--aui-control-hover-border,var(--ring))] hover:shadow-[var(--aui-card-shadow-hover,var(--aui-card-shadow,0_12px_32px_rgba(15,23,42,0.08)))]",
|
|
201
|
+
selected && "border-[color:var(--aui-control-border-strong,var(--ring))] shadow-[var(--aui-card-shadow-hover,var(--aui-card-shadow,0_12px_32px_rgba(15,23,42,0.08)))]",
|
|
202
|
+
className
|
|
203
|
+
)}
|
|
145
204
|
{...props}
|
|
146
205
|
>
|
|
147
206
|
{surfaceMode ? (
|
|
148
207
|
<>
|
|
149
|
-
{
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
208
|
+
{mediaPosition === "start" ? mediaNode : null}
|
|
209
|
+
<div
|
|
210
|
+
data-slot="card-body"
|
|
211
|
+
className={cn(
|
|
212
|
+
"flex min-w-0 flex-1 flex-col",
|
|
213
|
+
orientation === "horizontal" ? "py-(--card-spacing)" : "",
|
|
214
|
+
bodyPaddingClassName[padding],
|
|
215
|
+
bodyClassName
|
|
216
|
+
)}
|
|
217
|
+
>
|
|
218
|
+
{hasHeader ? (
|
|
219
|
+
<CardHeader className={cn(surfacePaddingClassName[padding], headerClassName)}>
|
|
220
|
+
<div className="min-w-0 space-y-1">
|
|
221
|
+
{eyebrow != null ? (
|
|
222
|
+
<div className={cn("text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground/80", eyebrowClassName)}>
|
|
223
|
+
{eyebrow}
|
|
224
|
+
</div>
|
|
225
|
+
) : null}
|
|
226
|
+
{title != null ? <CardTitle className={titleClassName}>{title}</CardTitle> : null}
|
|
227
|
+
{description != null ? (
|
|
228
|
+
<CardDescription className={descriptionClassName}>{description}</CardDescription>
|
|
229
|
+
) : null}
|
|
230
|
+
</div>
|
|
231
|
+
{badge != null || action != null ? (
|
|
232
|
+
<CardAction className="flex items-center gap-2">
|
|
233
|
+
{badge}
|
|
234
|
+
{action}
|
|
235
|
+
</CardAction>
|
|
161
236
|
) : null}
|
|
162
|
-
</
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
) : null}
|
|
169
|
-
</CardHeader>
|
|
170
|
-
) : null}
|
|
171
|
-
{resolvedContent != null ? <CardContent className={contentClassName}>{resolvedContent}</CardContent> : null}
|
|
172
|
-
{hasFooter ? <CardFooter className={footerClassName}>{footer}</CardFooter> : null}
|
|
237
|
+
</CardHeader>
|
|
238
|
+
) : null}
|
|
239
|
+
{resolvedContent != null ? <CardContent className={cn(surfacePaddingClassName[padding], contentClassName)}>{resolvedContent}</CardContent> : null}
|
|
240
|
+
{hasFooter ? <CardFooter className={cn(surfacePaddingClassName[padding], footerClassName)}>{footer}</CardFooter> : null}
|
|
241
|
+
</div>
|
|
242
|
+
{mediaPosition === "end" ? mediaNode : null}
|
|
173
243
|
</>
|
|
174
244
|
) : (
|
|
175
245
|
children
|