@payglocal_ui/flux-ui 0.3.1 → 0.3.2
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 +27 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +27 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/data-table-column-width.test.tsx +77 -0
- package/src/data-table.tsx +65 -9
package/package.json
CHANGED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, expect, it, beforeAll } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { DataTable, type Column } from "../data-table";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A column's `width` is documented to accept `minmax(min, max)`, but the
|
|
7
|
+
* `<col>` it lands on is a real table column, not a CSS Grid track —
|
|
8
|
+
* `width: minmax(...)` is invalid CSS and the whole declaration is dropped
|
|
9
|
+
* silently, leaving the column with no floor at all. On a narrow viewport
|
|
10
|
+
* that squeezed the column down to nothing, and the header's deliberately
|
|
11
|
+
* un-truncated text spilled into the next column's header — "Batch" and
|
|
12
|
+
* "Currency" rendering as "Batchency".
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
if (!(globalThis as any).ResizeObserver) {
|
|
17
|
+
(globalThis as any).ResizeObserver = class {
|
|
18
|
+
observe() {}
|
|
19
|
+
unobserve() {}
|
|
20
|
+
disconnect() {}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
interface Row {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DATA: Row[] = [{ id: "1", name: "Row one" }];
|
|
31
|
+
|
|
32
|
+
function columns(width: string): Column<Row>[] {
|
|
33
|
+
return [
|
|
34
|
+
{ key: "id", header: "Batch", width, render: (r) => r.id },
|
|
35
|
+
{ key: "name", header: "Currency", width: "84px", render: (r) => r.name },
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The `<col>` for a given column key, inside the fixed-layout `<colgroup>`. */
|
|
40
|
+
function colFor(key: string, container: HTMLElement) {
|
|
41
|
+
const th = screen.getAllByRole("columnheader").find((h) => h.textContent === key);
|
|
42
|
+
expect(th).toBeTruthy();
|
|
43
|
+
const index = Array.from(th!.parentElement!.children).indexOf(th!);
|
|
44
|
+
const cols = container.querySelectorAll("colgroup col");
|
|
45
|
+
return cols[index] as HTMLTableColElement;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("DataTable minmax() column widths", () => {
|
|
49
|
+
it("gives a minmax(min, 1fr) column a real min-width instead of dropping it", () => {
|
|
50
|
+
const { container } = render(<DataTable columns={columns("minmax(250px, 1fr)")} data={DATA} rowKey={(r) => r.id} />);
|
|
51
|
+
|
|
52
|
+
const col = colFor("Batch", container);
|
|
53
|
+
// The floor survives as `min-width` — a legal CSS property — rather than
|
|
54
|
+
// being lost inside an illegal `width: minmax(...)` declaration.
|
|
55
|
+
expect(col.style.minWidth).toBe("250px");
|
|
56
|
+
// `1fr` is a Grid unit and means nothing as a table `width`; leaving it
|
|
57
|
+
// unset is what lets the column take whatever `table-layout: fixed`
|
|
58
|
+
// leaves over, the same as a grid track's `1fr` would.
|
|
59
|
+
expect(col.style.width).toBe("");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("gives a minmax(min, max) column both bounds as real CSS", () => {
|
|
63
|
+
const { container } = render(<DataTable columns={columns("minmax(230px, 260px)")} data={DATA} rowKey={(r) => r.id} />);
|
|
64
|
+
|
|
65
|
+
const col = colFor("Batch", container);
|
|
66
|
+
expect(col.style.minWidth).toBe("230px");
|
|
67
|
+
expect(col.style.width).toBe("260px");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("leaves a plain width untouched", () => {
|
|
71
|
+
const { container } = render(<DataTable columns={columns("180px")} data={DATA} rowKey={(r) => r.id} />);
|
|
72
|
+
|
|
73
|
+
const col = colFor("Batch", container);
|
|
74
|
+
expect(col.style.width).toBe("180px");
|
|
75
|
+
expect(col.style.minWidth).toBe("");
|
|
76
|
+
});
|
|
77
|
+
});
|
package/src/data-table.tsx
CHANGED
|
@@ -177,7 +177,25 @@ export type DataTableExpandable<T> = {
|
|
|
177
177
|
export type Column<T> = {
|
|
178
178
|
key: string;
|
|
179
179
|
header: ReactNode;
|
|
180
|
-
/**
|
|
180
|
+
/**
|
|
181
|
+
* Table column width: `48px`, `18%`, or `minmax(12rem, 1fr)` for a floor
|
|
182
|
+
* that can still grow into whatever the other columns leave over.
|
|
183
|
+
*
|
|
184
|
+
* `minmax(min, max)` is translated rather than passed straight through: this
|
|
185
|
+
* is a real `<table>`/`<colgroup>`, and `minmax()` is a CSS Grid function
|
|
186
|
+
* that is not a legal `width` value outside a grid — the browser drops the
|
|
187
|
+
* whole declaration and the column gets no floor at all. `min` becomes the
|
|
188
|
+
* `<col>`'s `min-width`, and `max` becomes its `width` unless `max` is `1fr`
|
|
189
|
+
* (or any other flex unit), in which case no `width` is set and the column
|
|
190
|
+
* takes its share of whatever `table-layout: fixed` has left over, the same
|
|
191
|
+
* way a grid track's `1fr` would.
|
|
192
|
+
*
|
|
193
|
+
* `overflow-x-auto` on the table's own scroll container is what makes the
|
|
194
|
+
* floor mean something: once every column's minimum no longer fits, the
|
|
195
|
+
* table grows past its container and scrolls instead of every column
|
|
196
|
+
* shrinking under its `min-width` and the header text — deliberately not
|
|
197
|
+
* truncated, see the `<th>` render below — overlapping the column beside it.
|
|
198
|
+
*/
|
|
181
199
|
width?: string;
|
|
182
200
|
minWidth?: number;
|
|
183
201
|
maxWidth?: number;
|
|
@@ -425,6 +443,51 @@ function SortIndicator({ order }: { order: SortOrder | null }) {
|
|
|
425
443
|
);
|
|
426
444
|
}
|
|
427
445
|
|
|
446
|
+
/**
|
|
447
|
+
* `minmax(A, B)` → its two halves, tolerant of whichever whitespace someone
|
|
448
|
+
* wrote it with. `null` for anything else, including a plain length like
|
|
449
|
+
* `"180px"` — that already IS a valid `width` and passes through untouched.
|
|
450
|
+
*/
|
|
451
|
+
function parseMinMaxWidth(width: string | undefined): { min: string; max: string } | null {
|
|
452
|
+
if (!width) return null;
|
|
453
|
+
const match = /^minmax\(\s*([^,]+?)\s*,\s*([^)]+?)\s*\)$/.exec(width.trim());
|
|
454
|
+
return match ? { min: match[1], max: match[2] } : null;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** `1fr`, `2fr`, … — a Grid flex unit, meaningless as a table `width`. */
|
|
458
|
+
function isFlexUnit(value: string): boolean {
|
|
459
|
+
return /^[\d.]*fr$/.test(value.trim());
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* A column's `width` as the three real CSS properties a `<col>` understands.
|
|
464
|
+
*
|
|
465
|
+
* Anything that is not a `minmax(...)` string passes through exactly as it did
|
|
466
|
+
* before this existed, so a plain `"48px"` or `"18%"` column is untouched.
|
|
467
|
+
*/
|
|
468
|
+
function colWidthStyle(col: Pick<Column<unknown>, "width" | "minWidth" | "maxWidth">): {
|
|
469
|
+
width: string | number | undefined;
|
|
470
|
+
minWidth: string | number | undefined;
|
|
471
|
+
maxWidth: string | number | undefined;
|
|
472
|
+
} {
|
|
473
|
+
const parsed = parseMinMaxWidth(col.width);
|
|
474
|
+
if (!parsed) {
|
|
475
|
+
return {
|
|
476
|
+
width: col.width ?? (col.minWidth != null ? `${col.minWidth}px` : undefined),
|
|
477
|
+
minWidth: col.minWidth,
|
|
478
|
+
maxWidth: col.maxWidth,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
width: isFlexUnit(parsed.max) ? undefined : parsed.max,
|
|
483
|
+
// An explicit numeric `minWidth`/`maxWidth` alongside a `minmax()` string
|
|
484
|
+
// is not a combination any caller uses today; the string wins because it
|
|
485
|
+
// is the more specific of the two.
|
|
486
|
+
minWidth: parsed.min,
|
|
487
|
+
maxWidth: isFlexUnit(parsed.max) ? col.maxWidth : undefined,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
428
491
|
export function DataTable<T>({
|
|
429
492
|
columns,
|
|
430
493
|
data,
|
|
@@ -706,14 +769,7 @@ export function DataTable<T>({
|
|
|
706
769
|
<colgroup>
|
|
707
770
|
{hasExpand ? <col style={{ width: 40 }} /> : null}
|
|
708
771
|
{columns.map((col) => (
|
|
709
|
-
<col
|
|
710
|
-
key={col.key}
|
|
711
|
-
style={{
|
|
712
|
-
width: col.width ?? (col.minWidth != null ? `${col.minWidth}px` : undefined),
|
|
713
|
-
minWidth: col.minWidth,
|
|
714
|
-
maxWidth: col.maxWidth,
|
|
715
|
-
}}
|
|
716
|
-
/>
|
|
772
|
+
<col key={col.key} style={colWidthStyle(col)} />
|
|
717
773
|
))}
|
|
718
774
|
{/* Zero-width column: the action floats out of it as an overlay,
|
|
719
775
|
so the last data column stays flush and nothing trails it. */}
|