@manusiakemos/laravel-tanstack-react 0.1.0 → 0.1.1

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.
@@ -0,0 +1,271 @@
1
+ import type { Table } from '@tanstack/react-table'
2
+ import {
3
+ ChevronLeft,
4
+ ChevronRight,
5
+ ChevronsLeft,
6
+ ChevronsRight,
7
+ } from 'lucide-react'
8
+ import type { ButtonHTMLAttributes, ReactNode, SelectHTMLAttributes } from 'react'
9
+
10
+ import { cn } from '../lib/cn'
11
+ import type { DataTableMeta } from '../types'
12
+ import { Button } from './ui/button'
13
+ import { Select } from './ui/select'
14
+
15
+ export interface DataTablePaginationLabels {
16
+ previous?: ReactNode
17
+ next?: ReactNode
18
+ first?: ReactNode
19
+ last?: ReactNode
20
+ page?: string
21
+ of?: string
22
+ rowsPerPage?: string
23
+ /** Renderer for the row-count summary. */
24
+ summary?: (info: {
25
+ pageIndex: number
26
+ pageCount: number
27
+ pageSize: number
28
+ filtered: number
29
+ total: number | null
30
+ }) => ReactNode
31
+ }
32
+
33
+ export interface DataTablePaginationClassNames {
34
+ root?: string
35
+ info?: string
36
+ controls?: string
37
+ button?: string
38
+ select?: string
39
+ pageSize?: string
40
+ }
41
+
42
+ export interface DataTablePaginationRenderProps<TData> {
43
+ table: Table<TData>
44
+ meta: DataTableMeta | null
45
+ pageIndex: number
46
+ pageCount: number
47
+ pageSize: number
48
+ canPreviousPage: boolean
49
+ canNextPage: boolean
50
+ goToFirst: () => void
51
+ goToPrevious: () => void
52
+ goToNext: () => void
53
+ goToLast: () => void
54
+ setPageSize: (size: number) => void
55
+ }
56
+
57
+ export interface DataTablePaginationProps<TData> {
58
+ table: Table<TData>
59
+ /** Meta from `useDataTable`. Used for the row-count summary. */
60
+ meta?: DataTableMeta | null
61
+ /** Show the page-size selector. Defaults to false. */
62
+ showPageSize?: boolean
63
+ /** Available page size options when `showPageSize` is true. */
64
+ pageSizeOptions?: number[]
65
+ /** Show first/last page buttons. Defaults to false. */
66
+ showFirstLast?: boolean
67
+ /** Hide the "Page X of Y · N rows" summary. */
68
+ hideSummary?: boolean
69
+ /** Override individual labels / the summary renderer. */
70
+ labels?: DataTablePaginationLabels
71
+ /** Class for the root container. */
72
+ className?: string
73
+ /** Fine-grained class names for sub-elements. */
74
+ classNames?: DataTablePaginationClassNames
75
+ /** Extra props for the prev/next/first/last buttons. */
76
+ buttonProps?: Omit<
77
+ ButtonHTMLAttributes<HTMLButtonElement>,
78
+ 'onClick' | 'disabled' | 'className' | 'type'
79
+ >
80
+ /** Extra props for the page-size <select>. */
81
+ selectProps?: Omit<
82
+ SelectHTMLAttributes<HTMLSelectElement>,
83
+ 'value' | 'onChange' | 'className'
84
+ >
85
+ /**
86
+ * Full render override. Receives the pagination API so any custom UI can
87
+ * still drive the table.
88
+ */
89
+ render?: (props: DataTablePaginationRenderProps<TData>) => ReactNode
90
+ }
91
+
92
+ const DEFAULT_PAGE_SIZES = [10, 25, 50, 100]
93
+
94
+ /**
95
+ * Shadcn-styled pagination controls (prev/next, optional first/last, optional
96
+ * page-size selector, row-count summary) wired to a TanStack `Table`.
97
+ */
98
+ export function DataTablePagination<TData>(props: DataTablePaginationProps<TData>) {
99
+ const {
100
+ table,
101
+ meta = null,
102
+ showPageSize = false,
103
+ pageSizeOptions = DEFAULT_PAGE_SIZES,
104
+ showFirstLast = false,
105
+ hideSummary = false,
106
+ labels,
107
+ className,
108
+ classNames,
109
+ buttonProps,
110
+ selectProps,
111
+ render,
112
+ } = props
113
+
114
+ const state = table.getState().pagination
115
+ const pageIndex = state.pageIndex
116
+ const pageSize = state.pageSize
117
+ const pageCount = table.getPageCount()
118
+ const canPreviousPage = table.getCanPreviousPage()
119
+ const canNextPage = table.getCanNextPage()
120
+
121
+ const goToFirst = () => table.setPageIndex(0)
122
+ const goToPrevious = () => table.previousPage()
123
+ const goToNext = () => table.nextPage()
124
+ const goToLast = () => table.setPageIndex(Math.max(0, pageCount - 1))
125
+ const setPageSize = (size: number) => table.setPageSize(size)
126
+
127
+ if (render) {
128
+ return (
129
+ <>
130
+ {render({
131
+ table,
132
+ meta,
133
+ pageIndex,
134
+ pageCount,
135
+ pageSize,
136
+ canPreviousPage,
137
+ canNextPage,
138
+ goToFirst,
139
+ goToPrevious,
140
+ goToNext,
141
+ goToLast,
142
+ setPageSize,
143
+ })}
144
+ </>
145
+ )
146
+ }
147
+
148
+ const filtered = meta?.filtered ?? 0
149
+ const total = meta?.total ?? null
150
+
151
+ const summaryNode = labels?.summary
152
+ ? labels.summary({ pageIndex, pageCount, pageSize, filtered, total })
153
+ : (
154
+ <>
155
+ {labels?.page ?? 'Page'} {pageIndex + 1} {labels?.of ?? 'of'}{' '}
156
+ {Math.max(1, pageCount)}
157
+ {meta ? ` · ${filtered} rows` : null}
158
+ </>
159
+ )
160
+
161
+ return (
162
+ <div
163
+ className={cn(
164
+ 'flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between',
165
+ className,
166
+ classNames?.root,
167
+ )}
168
+ >
169
+ {!hideSummary && (
170
+ <div
171
+ className={cn(
172
+ 'text-sm text-muted-foreground',
173
+ classNames?.info,
174
+ )}
175
+ >
176
+ {summaryNode}
177
+ </div>
178
+ )}
179
+
180
+ <div
181
+ className={cn(
182
+ 'flex items-center gap-2',
183
+ classNames?.controls,
184
+ )}
185
+ >
186
+ {showPageSize && (
187
+ <label
188
+ className={cn(
189
+ 'flex items-center gap-2 text-sm text-muted-foreground',
190
+ classNames?.pageSize,
191
+ )}
192
+ >
193
+ <span>{labels?.rowsPerPage ?? 'Rows per page:'}</span>
194
+ <Select
195
+ value={pageSize}
196
+ onChange={(e) => setPageSize(Number(e.target.value))}
197
+ className={cn('h-9 w-[5rem]', classNames?.select)}
198
+ {...selectProps}
199
+ >
200
+ {pageSizeOptions.map((size) => (
201
+ <option key={size} value={size}>
202
+ {size}
203
+ </option>
204
+ ))}
205
+ </Select>
206
+ </label>
207
+ )}
208
+
209
+ {showFirstLast && (
210
+ <Button
211
+ variant="outline"
212
+ size="icon"
213
+ onClick={goToFirst}
214
+ disabled={!canPreviousPage}
215
+ className={classNames?.button}
216
+ aria-label="Go to first page"
217
+ {...buttonProps}
218
+ >
219
+ {labels?.first ?? <ChevronsLeft className="h-4 w-4" />}
220
+ </Button>
221
+ )}
222
+
223
+ <Button
224
+ variant="outline"
225
+ onClick={goToPrevious}
226
+ disabled={!canPreviousPage}
227
+ className={classNames?.button}
228
+ aria-label="Go to previous page"
229
+ {...buttonProps}
230
+ >
231
+ {labels?.previous ?? (
232
+ <>
233
+ <ChevronLeft className="h-4 w-4" />
234
+ <span>Previous</span>
235
+ </>
236
+ )}
237
+ </Button>
238
+
239
+ <Button
240
+ variant="outline"
241
+ onClick={goToNext}
242
+ disabled={!canNextPage}
243
+ className={classNames?.button}
244
+ aria-label="Go to next page"
245
+ {...buttonProps}
246
+ >
247
+ {labels?.next ?? (
248
+ <>
249
+ <span>Next</span>
250
+ <ChevronRight className="h-4 w-4" />
251
+ </>
252
+ )}
253
+ </Button>
254
+
255
+ {showFirstLast && (
256
+ <Button
257
+ variant="outline"
258
+ size="icon"
259
+ onClick={goToLast}
260
+ disabled={!canNextPage}
261
+ className={classNames?.button}
262
+ aria-label="Go to last page"
263
+ {...buttonProps}
264
+ >
265
+ {labels?.last ?? <ChevronsRight className="h-4 w-4" />}
266
+ </Button>
267
+ )}
268
+ </div>
269
+ </div>
270
+ )
271
+ }
@@ -0,0 +1,173 @@
1
+ import { Search as SearchIcon } from 'lucide-react'
2
+ import type { Table } from '@tanstack/react-table'
3
+ import {
4
+ type FormEvent,
5
+ type InputHTMLAttributes,
6
+ type ReactNode,
7
+ useEffect,
8
+ useState,
9
+ } from 'react'
10
+
11
+ import { cn } from '../lib/cn'
12
+ import { useDebouncedValue } from '../utils/useDebouncedValue'
13
+ import { Button } from './ui/button'
14
+ import { Input } from './ui/input'
15
+
16
+ export interface DataTableSearchRenderProps {
17
+ /** Current uncommitted input value (pre-debounce / pre-submit). */
18
+ value: string
19
+ /** Update the input value (debounced commit if `debounce` is on). */
20
+ setValue: (next: string) => void
21
+ /** Immediately commit the current value to the table's global filter. */
22
+ submit: () => void
23
+ /** The value currently applied to the table's global filter. */
24
+ appliedValue: string
25
+ }
26
+
27
+ export type DataTableSearchDebounce = boolean | number
28
+
29
+ export interface DataTableSearchProps<TData> {
30
+ table: Table<TData>
31
+ /**
32
+ * Debounce behavior:
33
+ * - `true` → debounced with default delay (300ms)
34
+ * - number → debounced with the given delay in ms
35
+ * - `false` → no debounce; instead the component renders a Search button
36
+ * and only commits to the table on submit (button click / Enter)
37
+ *
38
+ * Defaults to `true`.
39
+ */
40
+ debounce?: DataTableSearchDebounce
41
+ /** Placeholder text for the default input. */
42
+ placeholder?: string
43
+ /** Label for the search submit button (when `debounce` is `false`). */
44
+ submitLabel?: ReactNode
45
+ /** Class for the root wrapper element (ignored when `render` is provided). */
46
+ className?: string
47
+ /** Class merged into the underlying shadcn Input. */
48
+ inputClassName?: string
49
+ /** Class merged into the submit Button (when `debounce` is `false`). */
50
+ buttonClassName?: string
51
+ /** Extra props spread onto the underlying Input. */
52
+ inputProps?: Omit<
53
+ InputHTMLAttributes<HTMLInputElement>,
54
+ 'value' | 'onChange' | 'type' | 'className'
55
+ >
56
+ /** Fired whenever the search value is committed to the table. */
57
+ onSearch?: (value: string) => void
58
+ /**
59
+ * Full render override. Receives the controlled value, setter, and a manual
60
+ * `submit()` so you can drop in any custom UI.
61
+ */
62
+ render?: (props: DataTableSearchRenderProps) => ReactNode
63
+ }
64
+
65
+ const DEFAULT_DEBOUNCE_MS = 300
66
+
67
+ /**
68
+ * Debounced (or manual-submit) global search wired to a TanStack `Table`.
69
+ *
70
+ * @example Debounced (default)
71
+ * <DataTableSearch table={table} placeholder="Search users..." />
72
+ *
73
+ * @example Manual submit (search button)
74
+ * <DataTableSearch table={table} debounce={false} placeholder="Search..." />
75
+ *
76
+ * @example Custom UI
77
+ * <DataTableSearch
78
+ * table={table}
79
+ * render={({ value, setValue, submit }) => (
80
+ * <MyCombobox value={value} onChange={setValue} onSubmit={submit} />
81
+ * )}
82
+ * />
83
+ */
84
+ export function DataTableSearch<TData>(props: DataTableSearchProps<TData>) {
85
+ const {
86
+ table,
87
+ debounce = true,
88
+ placeholder = 'Search...',
89
+ submitLabel,
90
+ className,
91
+ inputClassName,
92
+ buttonClassName,
93
+ inputProps,
94
+ onSearch,
95
+ render,
96
+ } = props
97
+
98
+ const isDebounced = debounce !== false
99
+ const debounceMs =
100
+ typeof debounce === 'number' ? debounce : DEFAULT_DEBOUNCE_MS
101
+
102
+ const appliedValue = (table.getState().globalFilter as string | undefined) ?? ''
103
+ const [value, setValue] = useState(appliedValue)
104
+ const debounced = useDebouncedValue(value, debounceMs)
105
+
106
+ const commit = (next: string) => {
107
+ if (next === appliedValue) return
108
+ table.setGlobalFilter(next)
109
+ onSearch?.(next)
110
+ }
111
+
112
+ // Debounced auto-commit. Disabled entirely when `debounce={false}`.
113
+ useEffect(() => {
114
+ if (!isDebounced) return
115
+ commit(debounced)
116
+ // eslint-disable-next-line react-hooks/exhaustive-deps
117
+ }, [debounced, isDebounced, table])
118
+
119
+ // Keep local state in sync if the table's filter is changed elsewhere.
120
+ useEffect(() => {
121
+ setValue(appliedValue)
122
+ }, [appliedValue])
123
+
124
+ const submit = () => commit(value)
125
+
126
+ if (render) {
127
+ return <>{render({ value, setValue, submit, appliedValue })}</>
128
+ }
129
+
130
+ const handleFormSubmit = (e: FormEvent<HTMLFormElement>) => {
131
+ e.preventDefault()
132
+ submit()
133
+ }
134
+
135
+ // Debounced mode → just an input, no form / button needed.
136
+ if (isDebounced) {
137
+ return (
138
+ <Input
139
+ type="search"
140
+ value={value}
141
+ onChange={(e) => setValue(e.target.value)}
142
+ placeholder={placeholder}
143
+ className={cn(className, inputClassName)}
144
+ {...inputProps}
145
+ />
146
+ )
147
+ }
148
+
149
+ // Manual mode → input + submit button, wrapped in a form so Enter submits.
150
+ return (
151
+ <form
152
+ onSubmit={handleFormSubmit}
153
+ className={cn('flex w-full max-w-md items-center gap-2', className)}
154
+ >
155
+ <Input
156
+ type="search"
157
+ value={value}
158
+ onChange={(e) => setValue(e.target.value)}
159
+ placeholder={placeholder}
160
+ className={inputClassName}
161
+ {...inputProps}
162
+ />
163
+ <Button type="submit" className={buttonClassName}>
164
+ {submitLabel ?? (
165
+ <>
166
+ <SearchIcon className="h-4 w-4" aria-hidden="true" />
167
+ <span>Search</span>
168
+ </>
169
+ )}
170
+ </Button>
171
+ </form>
172
+ )
173
+ }
@@ -0,0 +1,153 @@
1
+ import type { Table as TanStackTable } from '@tanstack/react-table'
2
+ import type { ReactNode } from 'react'
3
+
4
+ import { cn } from '../../lib/cn'
5
+ import type { DataTableMeta } from '../../types'
6
+ import { DataTable, type DataTableProps } from '../DataTable'
7
+ import {
8
+ DataTablePagination,
9
+ type DataTablePaginationProps,
10
+ } from '../DataTablePagination'
11
+ import {
12
+ DataTableSearch,
13
+ type DataTableSearchProps,
14
+ } from '../DataTableSearch'
15
+
16
+ export interface DataTableSplitLayoutClassNames {
17
+ root?: string
18
+ toolbar?: string
19
+ toolbarLeft?: string
20
+ toolbarRight?: string
21
+ body?: string
22
+ footer?: string
23
+ }
24
+
25
+ export interface DataTableSplitLayoutProps<TData> {
26
+ table: TanStackTable<TData>
27
+ meta?: DataTableMeta | null
28
+ loading?: boolean
29
+
30
+ /**
31
+ * Right-side toolbar slot — typically one or more `<DataTableFilter />`s.
32
+ * Rendered to the right of the search input on the same row.
33
+ */
34
+ filters?: ReactNode
35
+ /**
36
+ * Optional far-right toolbar slot for page-level actions (e.g. an
37
+ * "+ Add" button). Appears after `filters`.
38
+ */
39
+ actions?: ReactNode
40
+
41
+ /**
42
+ * Override the entire search slot. By default, a `<DataTableSearch />`
43
+ * wired to `table` is rendered with sensible defaults.
44
+ */
45
+ search?: ReactNode
46
+
47
+ /** Forwarded to the auto-rendered `<DataTableSearch />`. */
48
+ searchProps?: Omit<DataTableSearchProps<TData>, 'table'>
49
+ /** Forwarded to the auto-rendered `<DataTable />`. */
50
+ tableProps?: Omit<DataTableProps<TData>, 'table' | 'loading'>
51
+ /** Forwarded to the auto-rendered `<DataTablePagination />`. */
52
+ paginationProps?: Omit<DataTablePaginationProps<TData>, 'table' | 'meta'>
53
+
54
+ className?: string
55
+ classNames?: DataTableSplitLayoutClassNames
56
+ }
57
+
58
+ /**
59
+ * Pre-built "split toolbar" layout: search on the left, filters/actions on
60
+ * the right; pagination info on the left, controls on the right.
61
+ *
62
+ * @example
63
+ * <DataTableSplitLayout
64
+ * table={table}
65
+ * meta={meta}
66
+ * loading={loading}
67
+ * searchProps={{ placeholder: 'Search users...' }}
68
+ * filters={
69
+ * <>
70
+ * <DataTableFilter table={table} columnId="status" options={statusOpts} />
71
+ * <DataTableFilter table={table} columnId="role" type="multiselect" options={roleOpts} />
72
+ * </>
73
+ * }
74
+ * actions={<Button>+ Add user</Button>}
75
+ * paginationProps={{ showPageSize: true, showFirstLast: true }}
76
+ * />
77
+ */
78
+ export function DataTableSplitLayout<TData>(
79
+ props: DataTableSplitLayoutProps<TData>,
80
+ ) {
81
+ const {
82
+ table,
83
+ meta = null,
84
+ loading = false,
85
+ filters,
86
+ actions,
87
+ search,
88
+ searchProps,
89
+ tableProps,
90
+ paginationProps,
91
+ className,
92
+ classNames,
93
+ } = props
94
+
95
+ const searchNode = search ?? (
96
+ <DataTableSearch
97
+ table={table}
98
+ placeholder="Search..."
99
+ className="w-full max-w-md"
100
+ {...searchProps}
101
+ />
102
+ )
103
+
104
+ return (
105
+ <div
106
+ className={cn(
107
+ 'flex flex-col gap-4',
108
+ className,
109
+ classNames?.root,
110
+ )}
111
+ >
112
+ <div
113
+ className={cn(
114
+ 'flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between',
115
+ classNames?.toolbar,
116
+ )}
117
+ >
118
+ <div
119
+ className={cn(
120
+ 'flex-1 min-w-0',
121
+ classNames?.toolbarLeft,
122
+ )}
123
+ >
124
+ {searchNode}
125
+ </div>
126
+
127
+ {(filters || actions) && (
128
+ <div
129
+ className={cn(
130
+ 'flex flex-wrap items-center gap-2',
131
+ classNames?.toolbarRight,
132
+ )}
133
+ >
134
+ {filters}
135
+ {actions}
136
+ </div>
137
+ )}
138
+ </div>
139
+
140
+ <div className={classNames?.body}>
141
+ <DataTable table={table} loading={loading} {...tableProps} />
142
+ </div>
143
+
144
+ <div className={classNames?.footer}>
145
+ <DataTablePagination
146
+ table={table}
147
+ meta={meta}
148
+ {...paginationProps}
149
+ />
150
+ </div>
151
+ </div>
152
+ )
153
+ }
@@ -0,0 +1,49 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority'
2
+ import { type ButtonHTMLAttributes, forwardRef } from 'react'
3
+
4
+ import { cn } from '../../lib/cn'
5
+
6
+ export const buttonVariants = cva(
7
+ 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
12
+ destructive:
13
+ 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
14
+ outline:
15
+ 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
16
+ secondary:
17
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
18
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
19
+ link: 'text-primary underline-offset-4 hover:underline',
20
+ },
21
+ size: {
22
+ default: 'h-10 px-4 py-2',
23
+ sm: 'h-9 rounded-md px-3',
24
+ lg: 'h-11 rounded-md px-8',
25
+ icon: 'h-10 w-10',
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ variant: 'default',
30
+ size: 'default',
31
+ },
32
+ },
33
+ )
34
+
35
+ export interface ButtonProps
36
+ extends ButtonHTMLAttributes<HTMLButtonElement>,
37
+ VariantProps<typeof buttonVariants> {}
38
+
39
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
40
+ ({ className, variant, size, type = 'button', ...props }, ref) => (
41
+ <button
42
+ ref={ref}
43
+ type={type}
44
+ className={cn(buttonVariants({ variant, size }), className)}
45
+ {...props}
46
+ />
47
+ ),
48
+ )
49
+ Button.displayName = 'Button'
@@ -0,0 +1,20 @@
1
+ import { forwardRef, type InputHTMLAttributes } from 'react'
2
+
3
+ import { cn } from '../../lib/cn'
4
+
5
+ export type InputProps = InputHTMLAttributes<HTMLInputElement>
6
+
7
+ export const Input = forwardRef<HTMLInputElement, InputProps>(
8
+ ({ className, type = 'text', ...props }, ref) => (
9
+ <input
10
+ ref={ref}
11
+ type={type}
12
+ className={cn(
13
+ 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ ),
19
+ )
20
+ Input.displayName = 'Input'
@@ -0,0 +1,27 @@
1
+ import { forwardRef, type SelectHTMLAttributes } from 'react'
2
+
3
+ import { cn } from '../../lib/cn'
4
+
5
+ export type SelectProps = SelectHTMLAttributes<HTMLSelectElement>
6
+
7
+ /**
8
+ * Shadcn-styled native <select>. We use the native element (not Radix) to
9
+ * keep the dependency footprint minimal; consumers who want a Radix-based
10
+ * popover select can swap it in via the `render` prop on the parent
11
+ * component.
12
+ */
13
+ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
14
+ ({ className, multiple, ...props }, ref) => (
15
+ <select
16
+ ref={ref}
17
+ multiple={multiple}
18
+ className={cn(
19
+ 'flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
20
+ multiple ? 'min-h-[6rem]' : 'h-10',
21
+ className,
22
+ )}
23
+ {...props}
24
+ />
25
+ ),
26
+ )
27
+ Select.displayName = 'Select'