@jinaan/uikit 0.1.38 → 0.1.40
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/data-table.d.ts.map +1 -1
- package/dist/index.js +642 -611
- package/dist/index.js.map +1 -1
- package/dist/table-surface.d.ts +11 -2
- package/dist/table-surface.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/data-table.tsx +113 -45
- package/src/table-surface.tsx +64 -28
package/dist/table-surface.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
1
|
+
import type { ReactNode, Ref } from "react";
|
|
2
2
|
export type TableSurfaceTone = "default" | "muted" | "transparent";
|
|
3
3
|
export interface TableSurfaceAction {
|
|
4
4
|
label: string;
|
|
@@ -29,6 +29,15 @@ interface TableSurfaceProps {
|
|
|
29
29
|
panelClassName?: string;
|
|
30
30
|
/** Minimum panel height; keeps empty states from collapsing. */
|
|
31
31
|
minHeight?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Where horizontal scrolling lives. `surface` (default) scrolls band and
|
|
34
|
+
* panel together — right for short content. `panel` keeps both shells fixed
|
|
35
|
+
* while the caller puts the scroller inside the panel; the band content is
|
|
36
|
+
* then expected to translate in lockstep via `headerContentRef`.
|
|
37
|
+
*/
|
|
38
|
+
scrollMode?: "surface" | "panel";
|
|
39
|
+
/** Panel mode only: ref for the band's translating inner wrapper. */
|
|
40
|
+
headerContentRef?: Ref<HTMLDivElement>;
|
|
32
41
|
}
|
|
33
42
|
/**
|
|
34
43
|
* Layered table surface: an outer shell holding a header band and an inset
|
|
@@ -37,6 +46,6 @@ interface TableSurfaceProps {
|
|
|
37
46
|
* Colors come from the `--table-*` tokens and can be adjusted per surface with
|
|
38
47
|
* `tone` plus `headerClassName` / `panelClassName`.
|
|
39
48
|
*/
|
|
40
|
-
export declare function TableSurface({ title, description, header, children, action, tone, className, headerClassName, panelClassName, minHeight, }: TableSurfaceProps): import("react").JSX.Element;
|
|
49
|
+
export declare function TableSurface({ title, description, header, children, action, tone, className, headerClassName, panelClassName, minHeight, scrollMode, headerContentRef, }: TableSurfaceProps): import("react").JSX.Element;
|
|
41
50
|
export {};
|
|
42
51
|
//# sourceMappingURL=table-surface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table-surface.d.ts","sourceRoot":"","sources":["../src/table-surface.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"table-surface.d.ts","sourceRoot":"","sources":["../src/table-surface.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAM5C,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,OAAO,GAAG,aAAa,CAAC;AAEnE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,aAAa,CAAC;IAC1D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,iBAAiB;IACzB,kCAAkC;IAClC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,uCAAuC;IACvC,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,yDAAyD;IACzD,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,0EAA0E;IAC1E,QAAQ,EAAE,SAAS,CAAC;IACpB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,SAAS,GAAG,kBAAkB,CAAC;IACxC,8EAA8E;IAC9E,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IACjC,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;CACxC;AAyBD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,WAAW,EACX,MAAM,EACN,QAAQ,EACR,MAAM,EACN,IAAgB,EAChB,SAAS,EACT,eAAe,EACf,cAAc,EACd,SAAS,EACT,UAAsB,EACtB,gBAAgB,GACjB,EAAE,iBAAiB,+BAuFnB"}
|
package/package.json
CHANGED
package/src/data-table.tsx
CHANGED
|
@@ -12,7 +12,15 @@ import {
|
|
|
12
12
|
WarningCircle,
|
|
13
13
|
X,
|
|
14
14
|
} from "@phosphor-icons/react";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
useCallback,
|
|
17
|
+
useEffect,
|
|
18
|
+
useLayoutEffect,
|
|
19
|
+
useMemo,
|
|
20
|
+
useRef,
|
|
21
|
+
useState,
|
|
22
|
+
type ReactNode,
|
|
23
|
+
} from "react";
|
|
16
24
|
|
|
17
25
|
import { Avatar, AvatarFallback, AvatarImage } from "./avatar";
|
|
18
26
|
import { Button } from "./button";
|
|
@@ -354,28 +362,63 @@ export function DataTable<T>({
|
|
|
354
362
|
// same pixel widths; both tables share the surface's horizontal scroller.
|
|
355
363
|
const bodyTableRef = useRef<HTMLTableElement | null>(null);
|
|
356
364
|
const bandTableRef = useRef<HTMLTableElement | null>(null);
|
|
365
|
+
const bandContentRef = useRef<HTMLDivElement | null>(null);
|
|
366
|
+
const scrollerRef = useRef<HTMLDivElement | null>(null);
|
|
357
367
|
const [columnWidths, setColumnWidths] = useState<number[]>([]);
|
|
358
368
|
// Deps must be primitives: visibleColumns is a fresh array every render and
|
|
359
369
|
// would re-run this effect (and its setState) in a loop.
|
|
360
370
|
const columnKey = visibleColumns.map((c) => c.id).join("\u0000");
|
|
371
|
+
// Identity of the rendered column set (selection, actions, visibility,
|
|
372
|
+
// order). The pinned colgroup maps positionally onto the columns, so any
|
|
373
|
+
// structural change invalidates the measured widths — otherwise the new
|
|
374
|
+
// column steals its neighbor's width and every column shifts by one.
|
|
375
|
+
const structureKey = `${selectable ? "s" : "-"}|${rowActions ? "a" : "-"}|${columnKey}`;
|
|
376
|
+
const structureRef = useRef(structureKey);
|
|
377
|
+
const pinned = columnWidths.length > 0;
|
|
378
|
+
|
|
379
|
+
// Panel-scroll mode: the band stays fixed to the shell and cannot scroll
|
|
380
|
+
// natively, so it translates by the row scroller's scrollLeft to keep every
|
|
381
|
+
// title over its column. Direct DOM write — no re-render per scroll event.
|
|
382
|
+
const syncBandScroll = useCallback(() => {
|
|
383
|
+
const band = bandContentRef.current;
|
|
384
|
+
const scroller = scrollerRef.current;
|
|
385
|
+
if (band && scroller) band.style.transform = `translateX(${-scroller.scrollLeft}px)`;
|
|
386
|
+
}, []);
|
|
361
387
|
|
|
362
388
|
useLayoutEffect(() => {
|
|
363
389
|
const table = bodyTableRef.current;
|
|
364
390
|
if (!table) return;
|
|
391
|
+
if (structureRef.current !== structureKey) {
|
|
392
|
+
// Column set changed: drop the pinned widths so the body re-renders
|
|
393
|
+
// content-sized (table-auto) and the next pass measures intrinsic
|
|
394
|
+
// widths against the new structure. Without this, the effect would
|
|
395
|
+
// measure the stale colgroup's shifted layout and pin the wrong widths.
|
|
396
|
+
structureRef.current = structureKey;
|
|
397
|
+
setColumnWidths((prev) => (prev.length === 0 ? prev : []));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
365
400
|
const measure = () => {
|
|
366
401
|
const cells = table.tBodies[0]?.rows.item(0)?.cells;
|
|
367
|
-
// The band may be squeezed narrower than its own sort-button content by
|
|
368
|
-
// the body-measured widths; scrollWidth reports the true minimum, so it
|
|
369
|
-
// floors each column and stops titles from overflowing their neighbors.
|
|
370
|
-
const bandCells = bandTableRef.current?.querySelectorAll("thead th");
|
|
371
402
|
if (!cells || cells.length === 0) {
|
|
372
403
|
setColumnWidths((prev) => (prev.length === 0 ? prev : []));
|
|
373
404
|
return;
|
|
374
405
|
}
|
|
406
|
+
// Widths cover every rendered column — including the selection checkbox
|
|
407
|
+
// and row-actions cells — in order, so one <col> per entry maps 1:1 in
|
|
408
|
+
// both colgroups. Never add extra <col> elements alongside these.
|
|
409
|
+
//
|
|
410
|
+
// Header floor: read the th's sizer span, never the th/cell layout
|
|
411
|
+
// itself. Once pinned with w-full, fixed-layout columns render wider
|
|
412
|
+
// than their content (surplus distribution) and reading them back would
|
|
413
|
+
// ratchet totalWidth up to the container size — after any container
|
|
414
|
+
// shrink the stale minWidth would keep a scrollbar alive forever. The
|
|
415
|
+
// sizer span is intrinsic in every state. Widths stay unrounded so the
|
|
416
|
+
// sum is the exact content requirement.
|
|
417
|
+
const bandThs = bandTableRef.current?.querySelectorAll("thead th");
|
|
375
418
|
const widths = Array.from(cells, (cell, i) =>
|
|
376
419
|
Math.max(
|
|
377
|
-
|
|
378
|
-
|
|
420
|
+
cell.getBoundingClientRect().width,
|
|
421
|
+
bandThs?.item(i)?.querySelector("[data-col-sizer]")?.getBoundingClientRect().width ?? 0,
|
|
379
422
|
),
|
|
380
423
|
);
|
|
381
424
|
// Bail out when nothing changed: a new array would re-render, and the
|
|
@@ -385,11 +428,16 @@ export function DataTable<T>({
|
|
|
385
428
|
);
|
|
386
429
|
};
|
|
387
430
|
measure();
|
|
431
|
+
syncBandScroll();
|
|
388
432
|
if (typeof ResizeObserver === "undefined") return;
|
|
389
433
|
const observer = new ResizeObserver(measure);
|
|
390
434
|
observer.observe(table);
|
|
391
435
|
return () => observer.disconnect();
|
|
392
|
-
|
|
436
|
+
// `pinned` re-runs this effect after the structure reset clears the
|
|
437
|
+
// widths (false) and again after re-pinning (true). Safe to measure in
|
|
438
|
+
// the pinned state because the floor reads sizer spans — intrinsic in
|
|
439
|
+
// every state — never the pinned layout itself.
|
|
440
|
+
}, [structureKey, pinned, loading, error, syncBandScroll]);
|
|
393
441
|
|
|
394
442
|
const totalWidth = columnWidths.reduce((sum, width) => sum + width, 0);
|
|
395
443
|
|
|
@@ -654,6 +702,8 @@ export function DataTable<T>({
|
|
|
654
702
|
)}
|
|
655
703
|
|
|
656
704
|
<TableSurface
|
|
705
|
+
scrollMode="panel"
|
|
706
|
+
headerContentRef={bandContentRef}
|
|
657
707
|
action={headerAction}
|
|
658
708
|
tone={tone}
|
|
659
709
|
headerClassName={headerClassName}
|
|
@@ -667,21 +717,24 @@ export function DataTable<T>({
|
|
|
667
717
|
role="presentation"
|
|
668
718
|
className="w-full table-fixed border-collapse"
|
|
669
719
|
style={{
|
|
670
|
-
|
|
720
|
+
// Fill the container when it fits; force totalWidth (and the
|
|
721
|
+
// shared scroller) only when the columns need more room.
|
|
722
|
+
minWidth: totalWidth > 0 ? totalWidth : undefined,
|
|
671
723
|
visibility: totalWidth > 0 ? undefined : "hidden",
|
|
672
724
|
}}
|
|
673
725
|
>
|
|
674
726
|
<colgroup>
|
|
675
|
-
{selectable && <col key="selectable" style={{ width: "2.75rem" }} />}
|
|
676
727
|
{columnWidths.map((width, i) => (
|
|
677
728
|
<col key={i} style={{ width }} />
|
|
678
729
|
))}
|
|
679
|
-
{rowActions && <col key="actions" style={{ width: "11rem" }} />}
|
|
680
730
|
</colgroup>
|
|
681
731
|
<thead>
|
|
682
732
|
<tr>
|
|
733
|
+
{/* text-left: UA stylesheets center th content; without this
|
|
734
|
+
the select-all checkbox centers in its (surplus-widened)
|
|
735
|
+
column and drifts right of the row checkboxes. */}
|
|
683
736
|
{selectable && (
|
|
684
|
-
<th className="h-(--table-header-h) w-11 px-
|
|
737
|
+
<th className="h-(--table-header-h) w-11 px-(--table-pad-x) text-left align-middle">
|
|
685
738
|
<Checkbox
|
|
686
739
|
aria-label="Select all rows"
|
|
687
740
|
checked={allSelected ? true : someSelected ? "indeterminate" : false}
|
|
@@ -694,34 +747,44 @@ export function DataTable<T>({
|
|
|
694
747
|
key={c.id}
|
|
695
748
|
className={cn(HEADER_CELL, c.align === "right" && "text-right", c.className)}
|
|
696
749
|
>
|
|
697
|
-
{
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
750
|
+
{/* Sizer: shrink-wraps the title (sort carets included) so
|
|
751
|
+
its rect is the column's intrinsic header width even
|
|
752
|
+
while the fixed layout squeezes the th. The negative
|
|
753
|
+
margins cancel the cell padding the span adds, so the
|
|
754
|
+
measured width is directly comparable to cell widths. */}
|
|
755
|
+
<span
|
|
756
|
+
data-col-sizer
|
|
757
|
+
className="inline-block whitespace-nowrap px-(--table-pad-x) -mx-(--table-pad-x)"
|
|
758
|
+
>
|
|
759
|
+
{c.sortable ? (
|
|
760
|
+
<Button
|
|
761
|
+
type="button"
|
|
762
|
+
variant="ghost"
|
|
763
|
+
size="xs"
|
|
764
|
+
aria-label={`Sort by ${c.header}`}
|
|
765
|
+
onClick={() => toggleSort(c)}
|
|
766
|
+
className={cn(
|
|
767
|
+
"h-auto gap-1.5 rounded-none px-0 py-0 text-[0.8125rem] text-table-header transition-colors [&_svg]:size-3.5",
|
|
768
|
+
"hover:bg-transparent hover:text-table-header-active hover:shadow-none",
|
|
769
|
+
"active:scale-100 active:bg-transparent active:shadow-none",
|
|
770
|
+
sort?.id === c.id && "text-table-header-active",
|
|
771
|
+
)}
|
|
772
|
+
>
|
|
773
|
+
{c.header}
|
|
774
|
+
{sort?.id === c.id ? (
|
|
775
|
+
sort.dir === "asc" ? (
|
|
776
|
+
<CaretUp className="size-3.5" weight="bold" />
|
|
777
|
+
) : (
|
|
778
|
+
<CaretDown className="size-3.5" weight="bold" />
|
|
779
|
+
)
|
|
715
780
|
) : (
|
|
716
|
-
<
|
|
717
|
-
)
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
c.header
|
|
724
|
-
)}
|
|
781
|
+
<CaretUpDown className="size-3.5 opacity-50" />
|
|
782
|
+
)}
|
|
783
|
+
</Button>
|
|
784
|
+
) : (
|
|
785
|
+
c.header
|
|
786
|
+
)}
|
|
787
|
+
</span>
|
|
725
788
|
</th>
|
|
726
789
|
))}
|
|
727
790
|
{rowActions && (
|
|
@@ -734,6 +797,10 @@ export function DataTable<T>({
|
|
|
734
797
|
</table>
|
|
735
798
|
}
|
|
736
799
|
>
|
|
800
|
+
{/* The scroller lives inside the panel: both shells stay fixed while
|
|
801
|
+
only the row content scrolls, and onScroll keeps the band titles
|
|
802
|
+
translated in lockstep with the rows. */}
|
|
803
|
+
<div ref={scrollerRef} className="overflow-x-auto" onScroll={syncBandScroll}>
|
|
737
804
|
{loading ? (
|
|
738
805
|
<div className="space-y-2 p-4">
|
|
739
806
|
{[0, 1, 2].map((i) => (
|
|
@@ -771,18 +838,18 @@ export function DataTable<T>({
|
|
|
771
838
|
"border-collapse text-left",
|
|
772
839
|
// Unmeasured: content-sized so intrinsic column widths can be
|
|
773
840
|
// measured (w-full would squeeze columns into the container).
|
|
774
|
-
// Measured: pinned to the band's colgroup
|
|
841
|
+
// Measured: fill the container, pinned to the band's colgroup
|
|
842
|
+
// pixel widths; minWidth forces the shared scroller open only
|
|
843
|
+
// when the columns need more room than the container has.
|
|
775
844
|
columnWidths.length > 0 ? "w-full table-fixed" : "table-auto",
|
|
776
845
|
)}
|
|
777
|
-
style={{
|
|
846
|
+
style={{ minWidth: totalWidth > 0 ? totalWidth : undefined }}
|
|
778
847
|
>
|
|
779
848
|
{columnWidths.length > 0 && (
|
|
780
849
|
<colgroup>
|
|
781
|
-
{selectable && <col key="selectable" style={{ width: "2.75rem" }} />}
|
|
782
850
|
{columnWidths.map((width, i) => (
|
|
783
851
|
<col key={i} style={{ width }} />
|
|
784
852
|
))}
|
|
785
|
-
{rowActions && <col key="actions" style={{ width: "11rem" }} />}
|
|
786
853
|
</colgroup>
|
|
787
854
|
)}
|
|
788
855
|
{/* Semantic headers for assistive tech; the visible band is presentational. */}
|
|
@@ -809,7 +876,7 @@ export function DataTable<T>({
|
|
|
809
876
|
className={onRowClick ? "cursor-pointer" : undefined}
|
|
810
877
|
>
|
|
811
878
|
{selectable && (
|
|
812
|
-
<TableCell className="w-11
|
|
879
|
+
<TableCell className="w-11" onClick={(e) => e.stopPropagation()}>
|
|
813
880
|
<Checkbox
|
|
814
881
|
aria-label="Select row"
|
|
815
882
|
checked={isSelected}
|
|
@@ -837,7 +904,8 @@ export function DataTable<T>({
|
|
|
837
904
|
</TableBody>
|
|
838
905
|
</table>
|
|
839
906
|
)}
|
|
840
|
-
|
|
907
|
+
</div>
|
|
908
|
+
</TableSurface>
|
|
841
909
|
|
|
842
910
|
{showPagination && (
|
|
843
911
|
<div className="flex flex-wrap items-center justify-between gap-3 text-sm">
|
package/src/table-surface.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
1
|
+
import type { ReactNode, Ref } from "react";
|
|
2
2
|
|
|
3
3
|
import { Button } from "./button";
|
|
4
4
|
import { TableContainer, TablePanel } from "./table";
|
|
@@ -36,6 +36,15 @@ interface TableSurfaceProps {
|
|
|
36
36
|
panelClassName?: string;
|
|
37
37
|
/** Minimum panel height; keeps empty states from collapsing. */
|
|
38
38
|
minHeight?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Where horizontal scrolling lives. `surface` (default) scrolls band and
|
|
41
|
+
* panel together — right for short content. `panel` keeps both shells fixed
|
|
42
|
+
* while the caller puts the scroller inside the panel; the band content is
|
|
43
|
+
* then expected to translate in lockstep via `headerContentRef`.
|
|
44
|
+
*/
|
|
45
|
+
scrollMode?: "surface" | "panel";
|
|
46
|
+
/** Panel mode only: ref for the band's translating inner wrapper. */
|
|
47
|
+
headerContentRef?: Ref<HTMLDivElement>;
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
const SHELL_TONE: Record<TableSurfaceTone, string> = {
|
|
@@ -79,8 +88,11 @@ export function TableSurface({
|
|
|
79
88
|
headerClassName,
|
|
80
89
|
panelClassName,
|
|
81
90
|
minHeight,
|
|
91
|
+
scrollMode = "surface",
|
|
92
|
+
headerContentRef,
|
|
82
93
|
}: TableSurfaceProps) {
|
|
83
94
|
const hasTitle = Boolean(title || description);
|
|
95
|
+
const panelScroll = scrollMode === "panel";
|
|
84
96
|
const actionNode = isActionConfig(action) ? (
|
|
85
97
|
<Button
|
|
86
98
|
variant={action.variant ?? "primary"}
|
|
@@ -103,37 +115,61 @@ export function TableSurface({
|
|
|
103
115
|
action
|
|
104
116
|
);
|
|
105
117
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
const band =
|
|
119
|
+
header || hasTitle ? (
|
|
120
|
+
// Header rows must not inherit the body row hover surface. Panel mode
|
|
121
|
+
// clips the band instead of scrolling it; the caller translates the
|
|
122
|
+
// inner wrapper so titles track the row content. px-px mirrors the
|
|
123
|
+
// panel's 1px border so band and body share the same width context —
|
|
124
|
+
// identical fixed-layout distribution keeps every column aligned.
|
|
125
|
+
<div
|
|
126
|
+
className={cn(
|
|
127
|
+
"[--table-row-hover:transparent] text-table-header",
|
|
128
|
+
panelScroll && "overflow-hidden px-px",
|
|
129
|
+
headerClassName,
|
|
130
|
+
)}
|
|
131
|
+
>
|
|
132
|
+
{hasTitle ? (
|
|
133
|
+
<div
|
|
134
|
+
className={cn(
|
|
135
|
+
"px-(--table-pad-x) pb-3 pt-4",
|
|
136
|
+
actionNode && "pr-28",
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
{title ? (
|
|
140
|
+
<div className="text-sm font-semibold text-fg">{title}</div>
|
|
141
|
+
) : null}
|
|
142
|
+
{description ? (
|
|
143
|
+
<div className="mt-0.5 text-sm text-muted">{description}</div>
|
|
126
144
|
) : null}
|
|
127
|
-
{header}
|
|
128
145
|
</div>
|
|
129
146
|
) : null}
|
|
130
|
-
<
|
|
131
|
-
className={cn(PANEL_TONE[tone], panelClassName)}
|
|
132
|
-
style={minHeight ? { minHeight } : undefined}
|
|
133
|
-
>
|
|
134
|
-
{children}
|
|
135
|
-
</TablePanel>
|
|
147
|
+
{panelScroll ? <div ref={headerContentRef}>{header}</div> : header}
|
|
136
148
|
</div>
|
|
149
|
+
) : null;
|
|
150
|
+
|
|
151
|
+
const panel = (
|
|
152
|
+
<TablePanel
|
|
153
|
+
className={cn(PANEL_TONE[tone], panelClassName)}
|
|
154
|
+
style={minHeight ? { minHeight } : undefined}
|
|
155
|
+
>
|
|
156
|
+
{children}
|
|
157
|
+
</TablePanel>
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<TableContainer className={cn("relative", SHELL_TONE[tone], className)}>
|
|
162
|
+
{panelScroll ? (
|
|
163
|
+
<>
|
|
164
|
+
{band}
|
|
165
|
+
{panel}
|
|
166
|
+
</>
|
|
167
|
+
) : (
|
|
168
|
+
<div className="overflow-x-auto">
|
|
169
|
+
{band}
|
|
170
|
+
{panel}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
137
173
|
{actionNode ? (
|
|
138
174
|
<div className="pointer-events-none absolute right-4 top-(--table-inset) flex h-(--table-header-h) items-center">
|
|
139
175
|
<div className="pointer-events-auto">{actionNode}</div>
|