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.
Files changed (70) hide show
  1. package/dist/components/data-table/data-table-pagination.cjs +1 -1
  2. package/dist/components/data-table/data-table-pagination.js +4 -4
  3. package/dist/components/data-table/data-table.cjs +1 -1
  4. package/dist/components/data-table/data-table.d.ts +2 -2
  5. package/dist/components/data-table/data-table.js +2 -1
  6. package/dist/components/display/carousel.cjs +1 -1
  7. package/dist/components/display/carousel.d.ts +7 -1
  8. package/dist/components/display/carousel.js +44 -44
  9. package/dist/components/display/smart-card.d.ts +1 -1
  10. package/dist/components/display/status-legend.d.ts +1 -1
  11. package/dist/components/form/form-input.cjs +1 -1
  12. package/dist/components/form/form-input.d.ts +3 -4
  13. package/dist/components/form/form-input.js +257 -257
  14. package/dist/components/form/form-select.cjs +1 -1
  15. package/dist/components/form/form-select.d.ts +2 -2
  16. package/dist/components/form/form-select.js +6 -6
  17. package/dist/components/inputs/async-select.d.ts +3 -0
  18. package/dist/components/inputs/search-input.d.ts +3 -0
  19. package/dist/components/inputs/simple-select.cjs +1 -1
  20. package/dist/components/inputs/simple-select.d.ts +10 -32
  21. package/dist/components/inputs/simple-select.js +5 -101
  22. package/dist/components/ui/card/index.cjs +1 -1
  23. package/dist/components/ui/card/index.d.ts +13 -2
  24. package/dist/components/ui/card/index.js +74 -49
  25. package/dist/components/ui/input/index.cjs +1 -1
  26. package/dist/components/ui/input/index.d.ts +8 -0
  27. package/dist/components/ui/input/index.js +63 -30
  28. package/dist/components/ui/select/index.cjs +1 -1
  29. package/dist/components/ui/select/index.d.ts +33 -1
  30. package/dist/components/ui/select/index.js +154 -48
  31. package/dist/showcase/create-demo.cjs +5 -4
  32. package/dist/showcase/create-demo.js +2 -2
  33. package/dist/showcase/premium/data-table/showcase.cjs +1 -1
  34. package/dist/showcase/premium/data-table/showcase.js +1 -1
  35. package/dist/showcase/premium/data-table-parts/showcase.cjs +1 -1
  36. package/dist/showcase/premium/data-table-parts/showcase.js +3 -1
  37. package/dist/showcase/premium/select/mock.cjs +8 -9
  38. package/dist/showcase/premium/select/mock.js +1 -1
  39. package/dist/showcase/premium/select/showcase.cjs +1 -1
  40. package/dist/showcase/premium/select/showcase.js +76 -86
  41. package/dist/showcase/registry-specific.cjs +1 -1
  42. package/dist/showcase/registry-specific.js +78 -74
  43. package/dist/showcase/render-registry-preview.cjs +1 -1
  44. package/dist/showcase/render-registry-preview.js +91 -87
  45. package/dist/showcase/tembro-registry.json.cjs +1 -1
  46. package/dist/showcase/tembro-registry.json.js +1 -1
  47. package/package.json +1 -1
  48. package/packages/cli/dist/index.cjs +1 -1
  49. package/packages/cli/vendor/src/components/data-table/data-table-pagination.tsx +7 -7
  50. package/packages/cli/vendor/src/components/data-table/data-table.tsx +21 -20
  51. package/packages/cli/vendor/src/components/display/carousel.tsx +18 -6
  52. package/packages/cli/vendor/src/components/display/smart-card.tsx +1 -1
  53. package/packages/cli/vendor/src/components/display/status-legend.tsx +1 -1
  54. package/packages/cli/vendor/src/components/form/form-input.tsx +9 -12
  55. package/packages/cli/vendor/src/components/form/form-select.tsx +4 -4
  56. package/packages/cli/vendor/src/components/inputs/async-select.tsx +11 -8
  57. package/packages/cli/vendor/src/components/inputs/search-input.tsx +5 -2
  58. package/packages/cli/vendor/src/components/inputs/simple-select.tsx +20 -190
  59. package/packages/cli/vendor/src/components/ui/card/index.tsx +95 -25
  60. package/packages/cli/vendor/src/components/ui/input/index.tsx +98 -0
  61. package/packages/cli/vendor/src/components/ui/select/index.tsx +227 -34
  62. package/packages/cli/vendor/src/showcase/create-demo.tsx +16 -15
  63. package/packages/cli/vendor/src/showcase/premium/data-table/showcase.tsx +3 -3
  64. package/packages/cli/vendor/src/showcase/premium/data-table-parts/showcase.tsx +3 -3
  65. package/packages/cli/vendor/src/showcase/premium/select/mock.ts +1 -1
  66. package/packages/cli/vendor/src/showcase/premium/select/showcase.tsx +13 -21
  67. package/packages/cli/vendor/src/showcase/registry-specific.tsx +7 -8
  68. package/packages/cli/vendor/src/showcase/render-registry-preview.tsx +5 -6
  69. package/packages/cli/vendor/src/showcase/tembro-registry.json +1 -1
  70. 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<SearchInputProps, "name" | "value" | "defaultValue" | "onValueChange"> &
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<ClearableInputProps, "name" | "value" | "defaultValue" | "onValueChange"> &
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
- <SearchInput
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
- <ClearableInput
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 { SimpleSelect, type SimpleSelectProps } from "@/components/inputs/simple-select"
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<SimpleSelectProps, "value" | "onValueChange" | "disabled"> &
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
- <SimpleSelect
199
+ <Select
200
200
  {...simpleProps}
201
- value={field.value == null ? "" : String(field.value)}
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
- function AsyncMultiSelect<
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
- const SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(
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
- SelectContent,
7
- SelectItem,
8
- SelectTrigger,
9
- SelectValue,
3
+ type SelectOption,
4
+ type SelectProps,
10
5
  } from "@/components/ui/select"
11
- import { cn, stopInteractivePropagation } from "@/lib/utils"
12
-
13
- export type SimpleSelectOption = {
14
- label: React.ReactNode
15
- value: string
16
- disabled?: boolean
17
- description?: React.ReactNode
18
- keywords?: string[]
19
- }
20
-
21
- export type SimpleSelectProps = Omit<
22
- React.ComponentProps<typeof Select>,
23
- "value" | "onValueChange"
24
- > & {
25
- value?: string
26
- onValueChange?: (value: string | undefined) => void
27
- options: SimpleSelectOption[]
28
- placeholder?: string
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 CardProps = Omit<React.ComponentProps<"div">, keyof CardSurfaceSlots> & VariantProps<typeof cardVariants> & CardSurfaceSlots
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(cardVariants({ variant, size, density, tone, interactive, selected, disabled }), className)}
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
- {media}
150
- {hasHeader ? (
151
- <CardHeader className={headerClassName}>
152
- <div className="min-w-0 space-y-1">
153
- {eyebrow != null ? (
154
- <div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground/80">
155
- {eyebrow}
156
- </div>
157
- ) : null}
158
- {title != null ? <CardTitle className={titleClassName}>{title}</CardTitle> : null}
159
- {description != null ? (
160
- <CardDescription className={descriptionClassName}>{description}</CardDescription>
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
- </div>
163
- {badge != null || action != null ? (
164
- <CardAction className="flex items-center gap-2">
165
- {badge}
166
- {action}
167
- </CardAction>
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