@payglocal_ui/flux-ui 0.2.4 → 0.2.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/index.cjs +195 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -3
- package/dist/index.d.ts +57 -3
- package/dist/index.js +206 -112
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/data-table.tsx +194 -31
- package/src/dropdown-menu.tsx +13 -4
- package/src/index.ts +2 -1
- package/src/select.tsx +20 -3
package/package.json
CHANGED
package/src/data-table.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import type { ReactNode } from "react";
|
|
|
4
4
|
import { cn } from "./utils";
|
|
5
5
|
import { TableRowSkeleton } from "./skeleton";
|
|
6
6
|
import { EmptyState } from "./empty-state";
|
|
7
|
-
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
7
|
+
import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
|
|
8
8
|
import { useState } from "react";
|
|
9
9
|
|
|
10
10
|
export type DataTableDensity = "default" | "comfortable" | "compact";
|
|
@@ -24,6 +24,38 @@ function getPageRange(current: number, total: number): (number | "…")[] {
|
|
|
24
24
|
return pages;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Row expansion: a disclosure column plus a full-width panel rendered directly
|
|
29
|
+
* beneath the expanded row. Use it when the detail belongs *with* the row in
|
|
30
|
+
* the flow of the table (a request's headers, a payload, a breakdown) rather
|
|
31
|
+
* than in a drawer that covers it.
|
|
32
|
+
*
|
|
33
|
+
* Leave `expandedKeys` unset for uncontrolled behaviour (the table remembers
|
|
34
|
+
* which rows are open). Pass `expandedKeys` + `onExpandedChange` to drive it
|
|
35
|
+
* from outside — needed when opening a row triggers a fetch.
|
|
36
|
+
*/
|
|
37
|
+
export type DataTableExpandable<T> = {
|
|
38
|
+
/** The panel shown under an expanded row. */
|
|
39
|
+
render: (row: T, index: number) => ReactNode;
|
|
40
|
+
/**
|
|
41
|
+
* Which rows can open at all. Rows that cannot get no toggle and no chevron,
|
|
42
|
+
* keeping the column's width without implying an affordance that isn't there.
|
|
43
|
+
* Defaults to every row.
|
|
44
|
+
*/
|
|
45
|
+
isExpandable?: (row: T, index: number) => boolean;
|
|
46
|
+
/** Controlled open rows, as `rowKey` values. Omit for uncontrolled. */
|
|
47
|
+
expandedKeys?: string[];
|
|
48
|
+
/** Fires on every open/close in controlled mode. */
|
|
49
|
+
onExpandedChange?: (keys: string[]) => void;
|
|
50
|
+
/**
|
|
51
|
+
* Fires only when a row opens, in both modes — the hook for lazily fetching
|
|
52
|
+
* that row's detail. Not called on close.
|
|
53
|
+
*/
|
|
54
|
+
onExpand?: (row: T, index: number) => void;
|
|
55
|
+
/** Accessible name for the toggle. Default "Toggle row details". */
|
|
56
|
+
toggleLabel?: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
27
59
|
export type Column<T> = {
|
|
28
60
|
key: string;
|
|
29
61
|
header: ReactNode;
|
|
@@ -109,6 +141,17 @@ interface DataTableProps<T> {
|
|
|
109
141
|
footerCountLabels?: { singular: string; plural: string };
|
|
110
142
|
/** With `density="compact"`, use tighter cell gutters (`pl-1.5 pr-2.5` vs `px-3`). Footer keeps normal horizontal padding. */
|
|
111
143
|
snug?: boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Extra control at the far left of the built-in footer, before the
|
|
146
|
+
* "Showing x–y of N" summary — a rows-per-page picker, typically.
|
|
147
|
+
*
|
|
148
|
+
* Without it a grid that needs a page-size control has to abandon the
|
|
149
|
+
* built-in footer and hand-roll one, which is how two different pagers end up
|
|
150
|
+
* in the same app.
|
|
151
|
+
*/
|
|
152
|
+
footerLeading?: ReactNode;
|
|
153
|
+
/** Per-row disclosure panel rendered beneath the row. See `DataTableExpandable`. */
|
|
154
|
+
expandable?: DataTableExpandable<T>;
|
|
112
155
|
}
|
|
113
156
|
|
|
114
157
|
export function DataTable<T>({
|
|
@@ -134,6 +177,8 @@ export function DataTable<T>({
|
|
|
134
177
|
footerSummary = "range",
|
|
135
178
|
footerCountLabels = { singular: "item", plural: "items" },
|
|
136
179
|
snug = false,
|
|
180
|
+
expandable,
|
|
181
|
+
footerLeading,
|
|
137
182
|
}: DataTableProps<T>) {
|
|
138
183
|
const isControlled = controlledPage !== undefined;
|
|
139
184
|
const [internalPage, setInternalPage] = useState(1);
|
|
@@ -153,6 +198,31 @@ export function DataTable<T>({
|
|
|
153
198
|
// width while the table still spans the full container width.
|
|
154
199
|
const hasSpacer = tableLayout === "content";
|
|
155
200
|
|
|
201
|
+
// Row expansion. Uncontrolled by default; `expandedKeys` hands control to the
|
|
202
|
+
// caller, which is what a row whose panel fetches its own data needs.
|
|
203
|
+
// `onExpand` fires only on open, in both modes.
|
|
204
|
+
const [internalExpanded, setInternalExpanded] = useState<string[]>([]);
|
|
205
|
+
const isExpandControlled = expandable?.expandedKeys !== undefined;
|
|
206
|
+
const expandedKeys = isExpandControlled ? expandable!.expandedKeys! : internalExpanded;
|
|
207
|
+
const hasExpand = expandable != null;
|
|
208
|
+
/** Every column the expansion panel has to span. */
|
|
209
|
+
const totalColSpan =
|
|
210
|
+
columns.length + (hasExpand ? 1 : 0) + (hasSpacer ? 1 : 0) + (hasAction ? 1 : 0);
|
|
211
|
+
|
|
212
|
+
/** Whether this row is open — drives both its own styling and the panel. */
|
|
213
|
+
const rowExpanded = (row: T, index: number) =>
|
|
214
|
+
hasExpand &&
|
|
215
|
+
(expandable!.isExpandable?.(row, index) ?? true) &&
|
|
216
|
+
expandedKeys.includes(rowKeys[index]);
|
|
217
|
+
|
|
218
|
+
const toggleExpanded = (key: string, row: T, index: number) => {
|
|
219
|
+
const isOpen = expandedKeys.includes(key);
|
|
220
|
+
const next = isOpen ? expandedKeys.filter((k) => k !== key) : [...expandedKeys, key];
|
|
221
|
+
if (isExpandControlled) expandable!.onExpandedChange?.(next);
|
|
222
|
+
else setInternalExpanded(next);
|
|
223
|
+
if (!isOpen) expandable!.onExpand?.(row, index);
|
|
224
|
+
};
|
|
225
|
+
|
|
156
226
|
const total = totalRows ?? data.length;
|
|
157
227
|
const totalPages = Math.ceil(total / pageSize);
|
|
158
228
|
const paginated = isControlled ? data : data.slice((page - 1) * pageSize, page * pageSize);
|
|
@@ -169,6 +239,16 @@ export function DataTable<T>({
|
|
|
169
239
|
return seen === 0 ? base : `${base}__${seen}`;
|
|
170
240
|
});
|
|
171
241
|
|
|
242
|
+
/**
|
|
243
|
+
* No rows to show. The width hints are dropped in this state: a grid whose
|
|
244
|
+
* columns carry minimums (via `colgroup` in fixed layout, or `cellClassName`
|
|
245
|
+
* in the others) would otherwise have its HEADER row alone force the table
|
|
246
|
+
* past the container and raise a horizontal scrollbar — over an empty region
|
|
247
|
+
* with nothing to scroll to. Headers still render, at their natural width, so
|
|
248
|
+
* the shape of the missing data is still legible.
|
|
249
|
+
*/
|
|
250
|
+
const isEmpty = !isLoading && paginated.length === 0;
|
|
251
|
+
|
|
172
252
|
const comfortable = density === "comfortable";
|
|
173
253
|
const compact = density === "compact";
|
|
174
254
|
const compactCellPad = compact
|
|
@@ -189,6 +269,23 @@ export function DataTable<T>({
|
|
|
189
269
|
: compact
|
|
190
270
|
? "text-[11px] font-semibold text-muted-foreground"
|
|
191
271
|
: "text-[11px] font-semibold text-foreground/75 dark:text-foreground/85";
|
|
272
|
+
/**
|
|
273
|
+
* Expansion geometry, in px, derived from the same padding the cells use:
|
|
274
|
+
*
|
|
275
|
+
* - `expandIndent` lines the panel's content up with the first DATA column
|
|
276
|
+
* (past the 40px disclosure column), so the detail reads as hanging off the
|
|
277
|
+
* row rather than starting outside it.
|
|
278
|
+
* - `expandGuideLeft` is the centre of the chevron, where the vertical
|
|
279
|
+
* connector runs — the cue that the panel belongs to the row above.
|
|
280
|
+
*
|
|
281
|
+
* Both live here rather than in each consumer's panel: a hardcoded indent in
|
|
282
|
+
* a feature silently drifts the moment this padding or the column width
|
|
283
|
+
* changes.
|
|
284
|
+
*/
|
|
285
|
+
const cellPadLeft = comfortable ? 20 : compact ? (snug ? 6 : 12) : 16;
|
|
286
|
+
const expandIndent = 40 + cellPadLeft;
|
|
287
|
+
const expandGuideLeft = cellPadLeft + 10;
|
|
288
|
+
|
|
192
289
|
// Action overlay geometry: the action floats this many px in from the right
|
|
193
290
|
// edge of the viewport (it has no reserved column — it overlays the row).
|
|
194
291
|
const actionGutter = comfortable ? 20 : compact ? 12 : 16;
|
|
@@ -244,8 +341,9 @@ export function DataTable<T>({
|
|
|
244
341
|
width: "100%",
|
|
245
342
|
}}
|
|
246
343
|
>
|
|
247
|
-
{tableLayout === "fixed" && (
|
|
344
|
+
{tableLayout === "fixed" && !isEmpty && (
|
|
248
345
|
<colgroup>
|
|
346
|
+
{hasExpand ? <col style={{ width: 40 }} /> : null}
|
|
249
347
|
{columns.map((col) => (
|
|
250
348
|
<col
|
|
251
349
|
key={col.key}
|
|
@@ -275,6 +373,7 @@ export function DataTable<T>({
|
|
|
275
373
|
headerStyle === "surface" ? "border-border" : "border-border/70"
|
|
276
374
|
)}
|
|
277
375
|
>
|
|
376
|
+
{hasExpand ? <th className={cn(headPad, "w-10 p-0")} aria-hidden /> : null}
|
|
278
377
|
{columns.map((col) => (
|
|
279
378
|
<th
|
|
280
379
|
key={col.key}
|
|
@@ -287,7 +386,8 @@ export function DataTable<T>({
|
|
|
287
386
|
: col.align === "center"
|
|
288
387
|
? "text-center"
|
|
289
388
|
: "text-left",
|
|
290
|
-
|
|
389
|
+
// Width hints live in `cellClassName`; see `isEmpty`.
|
|
390
|
+
!isEmpty && col.cellClassName
|
|
291
391
|
)}
|
|
292
392
|
>
|
|
293
393
|
{col.header}
|
|
@@ -305,19 +405,19 @@ export function DataTable<T>({
|
|
|
305
405
|
Array.from({ length: skeletonRows }).map((_, i) => (
|
|
306
406
|
<TableRowSkeleton
|
|
307
407
|
key={i}
|
|
308
|
-
cols={columns.length}
|
|
408
|
+
cols={columns.length + (hasExpand ? 1 : 0)}
|
|
309
409
|
density={density}
|
|
310
410
|
snug={snug}
|
|
311
411
|
/>
|
|
312
412
|
))
|
|
313
413
|
) : paginated.length === 0 ? (
|
|
314
414
|
<tr>
|
|
315
|
-
<td colSpan={
|
|
415
|
+
<td colSpan={totalColSpan}>
|
|
316
416
|
<EmptyState title={emptyTitle} description={emptyDescription} />
|
|
317
417
|
</td>
|
|
318
418
|
</tr>
|
|
319
419
|
) : (
|
|
320
|
-
paginated.
|
|
420
|
+
paginated.flatMap((row, i) => [
|
|
321
421
|
<tr
|
|
322
422
|
key={rowKeys[i]}
|
|
323
423
|
className={cn(
|
|
@@ -331,7 +431,14 @@ export function DataTable<T>({
|
|
|
331
431
|
// when reached by keyboard. `focus-visible` only, so a
|
|
332
432
|
// mouse click does not leave a ring behind on the row.
|
|
333
433
|
onRowClick &&
|
|
334
|
-
"cursor-pointer focus-visible:outline-none focus-visible:bg-muted/40 dark:focus-visible:bg-muted/25"
|
|
434
|
+
"cursor-pointer focus-visible:outline-none focus-visible:bg-muted/40 dark:focus-visible:bg-muted/25",
|
|
435
|
+
// An open row takes its panel's background and drops the
|
|
436
|
+
// divider beneath it, so the row and its detail read as one
|
|
437
|
+
// block. Hover is pinned to the same value, or moving the
|
|
438
|
+
// mouse over an open row would make it flicker away from
|
|
439
|
+
// the panel it belongs to.
|
|
440
|
+
rowExpanded(row, i) &&
|
|
441
|
+
"border-b-0 bg-muted/40 hover:bg-muted/40 dark:bg-muted/25 dark:hover:bg-muted/25"
|
|
335
442
|
)}
|
|
336
443
|
// Keyboard parity with the mouse: the row is reachable by
|
|
337
444
|
// Tab and activated by Enter / Space, which a bare <tr> with
|
|
@@ -360,6 +467,26 @@ export function DataTable<T>({
|
|
|
360
467
|
: undefined
|
|
361
468
|
}
|
|
362
469
|
>
|
|
470
|
+
{hasExpand ? (
|
|
471
|
+
<td className={cn(cellPad, "w-10 align-middle")}>
|
|
472
|
+
{(expandable!.isExpandable?.(row, i) ?? true) ? (
|
|
473
|
+
<button
|
|
474
|
+
type="button"
|
|
475
|
+
aria-expanded={rowExpanded(row, i)}
|
|
476
|
+
aria-label={expandable!.toggleLabel ?? "Toggle row details"}
|
|
477
|
+
onClick={() => toggleExpanded(rowKeys[i], row, i)}
|
|
478
|
+
className="inline-flex h-5 w-5 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
479
|
+
>
|
|
480
|
+
<ChevronDown
|
|
481
|
+
className={cn(
|
|
482
|
+
"h-3.5 w-3.5 transition-transform duration-150",
|
|
483
|
+
rowExpanded(row, i) && "rotate-180"
|
|
484
|
+
)}
|
|
485
|
+
/>
|
|
486
|
+
</button>
|
|
487
|
+
) : null}
|
|
488
|
+
</td>
|
|
489
|
+
) : null}
|
|
363
490
|
{columns.map((col) => (
|
|
364
491
|
<td
|
|
365
492
|
key={col.key}
|
|
@@ -430,8 +557,38 @@ export function DataTable<T>({
|
|
|
430
557
|
</span>
|
|
431
558
|
</td>
|
|
432
559
|
) : null}
|
|
433
|
-
</tr
|
|
434
|
-
|
|
560
|
+
</tr>,
|
|
561
|
+
|
|
562
|
+
// The panel spans every column, including the toggle, spacer and
|
|
563
|
+
// action cells, so it reads as one band under its row rather
|
|
564
|
+
// than as a cell inside the grid.
|
|
565
|
+
rowExpanded(row, i) ? (
|
|
566
|
+
<tr
|
|
567
|
+
key={`${rowKeys[i]}__panel`}
|
|
568
|
+
className="border-b border-border/60 bg-muted/40 last:border-b-0 dark:bg-muted/25"
|
|
569
|
+
>
|
|
570
|
+
<td colSpan={totalColSpan} className="p-0 align-top">
|
|
571
|
+
<div
|
|
572
|
+
className="relative"
|
|
573
|
+
style={{ paddingLeft: expandIndent, paddingRight: cellPadLeft }}
|
|
574
|
+
>
|
|
575
|
+
{/* Connector: a hairline dropping from the chevron down
|
|
576
|
+
the panel. The row and its detail share a background
|
|
577
|
+
so they read as one block, which on its own leaves
|
|
578
|
+
nothing to say the lower half is derived rather than
|
|
579
|
+
more row content — this is that cue. Stops short of
|
|
580
|
+
the bottom so it reads as hanging, not as a border. */}
|
|
581
|
+
<span
|
|
582
|
+
aria-hidden
|
|
583
|
+
className="absolute top-0 bottom-4 w-px bg-border"
|
|
584
|
+
style={{ left: expandGuideLeft }}
|
|
585
|
+
/>
|
|
586
|
+
{expandable!.render(row, i)}
|
|
587
|
+
</div>
|
|
588
|
+
</td>
|
|
589
|
+
</tr>
|
|
590
|
+
) : null,
|
|
591
|
+
])
|
|
435
592
|
)}
|
|
436
593
|
</tbody>
|
|
437
594
|
</table>
|
|
@@ -442,32 +599,38 @@ export function DataTable<T>({
|
|
|
442
599
|
className={cn(
|
|
443
600
|
"flex items-center gap-4 flex-wrap border-t border-border",
|
|
444
601
|
footerPad,
|
|
445
|
-
footerSummary === "count" && totalPages <= 1
|
|
602
|
+
footerSummary === "count" && totalPages <= 1 && !footerLeading
|
|
446
603
|
? "justify-start"
|
|
447
604
|
: "justify-between"
|
|
448
605
|
)}
|
|
449
606
|
>
|
|
450
|
-
{
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
607
|
+
{/* `footerLeading` (a rows-per-page picker, typically) groups with the
|
|
608
|
+
summary on the left rather than becoming a third item the
|
|
609
|
+
justify-between would fling to its own corner. */}
|
|
610
|
+
<div className="flex items-center gap-3">
|
|
611
|
+
{footerLeading}
|
|
612
|
+
{footerSummary === "count" ? (
|
|
613
|
+
<span className="text-[12px] text-muted-foreground tabular-nums">
|
|
614
|
+
<span className="font-medium text-foreground">{total}</span>{" "}
|
|
615
|
+
{total === 1
|
|
616
|
+
? footerCountLabels.singular
|
|
617
|
+
: footerCountLabels.plural}
|
|
618
|
+
</span>
|
|
619
|
+
) : (
|
|
620
|
+
<span className="text-[12px] text-muted-foreground tabular-nums">
|
|
621
|
+
Showing{" "}
|
|
622
|
+
<span className="text-foreground font-medium">
|
|
623
|
+
{Math.min((page - 1) * pageSize + 1, total)}–
|
|
624
|
+
{Math.min(page * pageSize, total)}
|
|
625
|
+
</span>{" "}
|
|
626
|
+
of{" "}
|
|
627
|
+
<span className="text-foreground font-medium">
|
|
628
|
+
{total.toLocaleString()}
|
|
629
|
+
</span>{" "}
|
|
630
|
+
{total !== 1 ? "results" : "result"}
|
|
631
|
+
</span>
|
|
632
|
+
)}
|
|
633
|
+
</div>
|
|
471
634
|
|
|
472
635
|
{(footerSummary === "range" || footerSummary === "count") &&
|
|
473
636
|
totalPages > 1 && (
|
package/src/dropdown-menu.tsx
CHANGED
|
@@ -19,7 +19,9 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
|
|
19
19
|
<DropdownMenuPrimitive.SubTrigger
|
|
20
20
|
ref={ref}
|
|
21
21
|
className={cn(
|
|
22
|
-
|
|
22
|
+
// Matches DropdownMenuItem: a submenu row sits in the same list as the
|
|
23
|
+
// plain rows, so it cannot be a different size from them.
|
|
24
|
+
"flex cursor-default select-none items-center gap-2 rounded-lg px-2.5 py-1.5 text-[13px] outline-none",
|
|
23
25
|
"focus:bg-muted data-[state=open]:bg-muted",
|
|
24
26
|
inset && "pl-8",
|
|
25
27
|
className
|
|
@@ -74,7 +76,10 @@ const DropdownMenuItem = React.forwardRef<
|
|
|
74
76
|
<DropdownMenuPrimitive.Item
|
|
75
77
|
ref={ref}
|
|
76
78
|
className={cn(
|
|
77
|
-
|
|
79
|
+
// 13px / tighter padding: a menu drops out of a toolbar button or a
|
|
80
|
+
// compact table control, and at 15px with 10px vertical padding it read
|
|
81
|
+
// as a larger, separate UI than the control that opened it.
|
|
82
|
+
"relative flex cursor-default select-none items-center gap-2 rounded-lg px-2.5 py-1.5 text-[13px] outline-none transition-colors",
|
|
78
83
|
"focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
79
84
|
inset && "pl-8",
|
|
80
85
|
className
|
|
@@ -91,7 +96,9 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|
|
91
96
|
<DropdownMenuPrimitive.CheckboxItem
|
|
92
97
|
ref={ref}
|
|
93
98
|
className={cn(
|
|
94
|
-
|
|
99
|
+
// Matches DropdownMenuItem. `pl-8` stays: that gutter is the check /
|
|
100
|
+
// dot indicator's, not padding.
|
|
101
|
+
"relative flex cursor-default select-none items-center rounded-lg py-1.5 pl-8 pr-2.5 text-[13px] outline-none transition-colors",
|
|
95
102
|
"focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
96
103
|
className
|
|
97
104
|
)}
|
|
@@ -115,7 +122,9 @@ const DropdownMenuRadioItem = React.forwardRef<
|
|
|
115
122
|
<DropdownMenuPrimitive.RadioItem
|
|
116
123
|
ref={ref}
|
|
117
124
|
className={cn(
|
|
118
|
-
|
|
125
|
+
// Matches DropdownMenuItem. `pl-8` stays: that gutter is the check /
|
|
126
|
+
// dot indicator's, not padding.
|
|
127
|
+
"relative flex cursor-default select-none items-center rounded-lg py-1.5 pl-8 pr-2.5 text-[13px] outline-none transition-colors",
|
|
119
128
|
"focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
120
129
|
className
|
|
121
130
|
)}
|
package/src/index.ts
CHANGED
|
@@ -194,6 +194,7 @@ export {
|
|
|
194
194
|
SelectScrollUpButton,
|
|
195
195
|
SelectScrollDownButton,
|
|
196
196
|
} from "./select";
|
|
197
|
+
export type { SelectTriggerSize } from "./select";
|
|
197
198
|
export { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator, CommandShortcut } from "./command";
|
|
198
199
|
export type {
|
|
199
200
|
CommandProps,
|
|
@@ -232,7 +233,7 @@ export { Shimmer, StatCardSkeleton, TableRowSkeleton, ChartSkeleton } from "./sk
|
|
|
232
233
|
|
|
233
234
|
// Data display
|
|
234
235
|
export { DataTable } from "./data-table";
|
|
235
|
-
export type { Column, DataTableDensity, DataTableFooterSummary, DataTableHeaderStyle } from "./data-table";
|
|
236
|
+
export type { Column, DataTableDensity, DataTableExpandable, DataTableFooterSummary, DataTableHeaderStyle } from "./data-table";
|
|
236
237
|
export { EmptyState } from "./empty-state";
|
|
237
238
|
export { PageHeader } from "./page-header";
|
|
238
239
|
export { Code, CodeBlock } from "./code";
|
package/src/select.tsx
CHANGED
|
@@ -9,14 +9,31 @@ const Select = SelectPrimitive.Root;
|
|
|
9
9
|
const SelectGroup = SelectPrimitive.Group;
|
|
10
10
|
const SelectValue = SelectPrimitive.Value;
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* `md` (default) is the form-field trigger. `sm` is for dense furniture — a
|
|
14
|
+
* rows-per-page picker in a table footer, a control inside a toolbar — where
|
|
15
|
+
* the full-height field towers over everything beside it.
|
|
16
|
+
*
|
|
17
|
+
* This is a size prop rather than a job for `className` because the base sets
|
|
18
|
+
* `min-h-11`, which beats an `h-8` utility: every caller wanting a short
|
|
19
|
+
* trigger had to override min-height, padding, gap and text size together.
|
|
20
|
+
*/
|
|
21
|
+
export type SelectTriggerSize = "sm" | "md";
|
|
22
|
+
|
|
23
|
+
const selectTriggerSizes: Record<SelectTriggerSize, string> = {
|
|
24
|
+
md: "h-11 min-h-11 gap-2.5 px-4 py-2 text-[15px]",
|
|
25
|
+
sm: "h-7 min-h-7 w-auto gap-1 px-2 py-0 text-[12px]",
|
|
26
|
+
};
|
|
27
|
+
|
|
12
28
|
const SelectTrigger = React.forwardRef<
|
|
13
29
|
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
14
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
|
15
|
-
>(({ className, children, ...props }, ref) => (
|
|
30
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & { size?: SelectTriggerSize }
|
|
31
|
+
>(({ className, children, size = "md", ...props }, ref) => (
|
|
16
32
|
<SelectPrimitive.Trigger
|
|
17
33
|
ref={ref}
|
|
18
34
|
className={cn(
|
|
19
|
-
"flex
|
|
35
|
+
"flex w-full items-center justify-between rounded-lg border border-border bg-card text-foreground shadow-sm outline-none",
|
|
36
|
+
selectTriggerSizes[size],
|
|
20
37
|
"ring-ring/50 focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
21
38
|
"[&>span]:line-clamp-1",
|
|
22
39
|
className
|