mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
Add data grid sizing helpers.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
15f4573bfb
commit
9d3d5865fb
@ -0,0 +1,41 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { fitColumnsToContainer } from "./data-grid-sizing";
|
||||
import type { DataGridColumnDef } from "./types";
|
||||
|
||||
type Row = { id: string };
|
||||
|
||||
describe("fitColumnsToContainer", () => {
|
||||
const columns: DataGridColumnDef<Row>[] = [
|
||||
{ id: "name", header: "Name", width: 320, minWidth: 80, type: "string", accessor: () => "" },
|
||||
{ id: "email", header: "Email", width: 420, minWidth: 80, type: "string", accessor: () => "" },
|
||||
];
|
||||
|
||||
it("shrinks fixed columns to fit a narrow container", () => {
|
||||
const sizes = { name: 320, email: 420 };
|
||||
fitColumnsToContainer(sizes, columns, 320, 0);
|
||||
expect(sizes.name + sizes.email).toBe(320);
|
||||
expect(sizes.name).toBeGreaterThanOrEqual(80);
|
||||
expect(sizes.email).toBeGreaterThanOrEqual(80);
|
||||
});
|
||||
|
||||
it("does not treat total column width as chrome width", () => {
|
||||
const sizes = { name: 200, email: 200 };
|
||||
const buggySizes = { name: 200, email: 200 };
|
||||
fitColumnsToContainer(sizes, columns, 500, 0);
|
||||
// Buggy call passed pre-summed column total as chrome, double-counting widths.
|
||||
fitColumnsToContainer(buggySizes, columns, 500, 400);
|
||||
expect(sizes.name + sizes.email).toBe(400);
|
||||
expect(buggySizes.name + buggySizes.email).toBeLessThan(400);
|
||||
});
|
||||
|
||||
it("grows flex columns when the container is wider than the base total", () => {
|
||||
const flexColumns: DataGridColumnDef<Row>[] = [
|
||||
{ id: "name", header: "Name", width: 100, minWidth: 80, flex: 1, type: "string", accessor: () => "" },
|
||||
{ id: "email", header: "Email", width: 100, minWidth: 80, type: "string", accessor: () => "" },
|
||||
];
|
||||
const sizes = { name: 100, email: 100 };
|
||||
fitColumnsToContainer(sizes, flexColumns, 400, 0);
|
||||
expect(sizes.name + sizes.email).toBe(400);
|
||||
expect(sizes.name).toBeGreaterThan(100);
|
||||
});
|
||||
});
|
||||
@ -49,3 +49,79 @@ export function getEffectiveMaxWidth<TRow>(col: DataGridColumnDef<TRow>): number
|
||||
export function clampColumnWidth<TRow>(col: DataGridColumnDef<TRow>, width: number): number {
|
||||
return Math.max(getEffectiveMinWidth(col), Math.min(getEffectiveMaxWidth(col), width));
|
||||
}
|
||||
|
||||
function distributeFlexWidths<TRow>(
|
||||
sizes: Record<string, number>,
|
||||
visibleColumns: readonly DataGridColumnDef<TRow>[],
|
||||
available: number,
|
||||
): void {
|
||||
const flexCols = visibleColumns.filter((c) => c.flex != null && c.flex > 0);
|
||||
if (flexCols.length === 0 || available <= 0) return;
|
||||
const totalFlex = flexCols.reduce((acc, c) => acc + (c.flex ?? 0), 0);
|
||||
let remaining = available;
|
||||
flexCols.forEach((col, i) => {
|
||||
const isLast = i === flexCols.length - 1;
|
||||
const share = isLast
|
||||
? remaining
|
||||
: Math.floor(available * ((col.flex ?? 0) / totalFlex));
|
||||
const max = getEffectiveMaxWidth(col);
|
||||
const add = Math.max(0, Math.min(share, max - sizes[col.id]));
|
||||
sizes[col.id] += add;
|
||||
remaining -= add;
|
||||
});
|
||||
}
|
||||
|
||||
/** Grow flex columns when there is extra space; shrink flex (then fixed) columns when overflowing. */
|
||||
export function fitColumnsToContainer<TRow>(
|
||||
sizes: Record<string, number>,
|
||||
visibleColumns: readonly DataGridColumnDef<TRow>[],
|
||||
containerWidth: number,
|
||||
chromeWidth: number,
|
||||
): void {
|
||||
const getTotal = () =>
|
||||
chromeWidth + visibleColumns.reduce((sum, col) => sum + sizes[col.id], 0);
|
||||
|
||||
if (containerWidth <= 0) return;
|
||||
|
||||
const total = getTotal();
|
||||
if (total <= containerWidth) {
|
||||
distributeFlexWidths(sizes, visibleColumns, containerWidth - total);
|
||||
return;
|
||||
}
|
||||
|
||||
let overflow = total - containerWidth;
|
||||
const flexCols = visibleColumns.filter((c) => (c.flex ?? 0) > 0);
|
||||
|
||||
for (const col of flexCols) {
|
||||
if (overflow <= 0) break;
|
||||
const min = getEffectiveMinWidth(col);
|
||||
const reducible = sizes[col.id] - min;
|
||||
if (reducible <= 0) continue;
|
||||
const delta = Math.min(overflow, reducible);
|
||||
sizes[col.id] -= delta;
|
||||
overflow -= delta;
|
||||
}
|
||||
|
||||
if (overflow <= 0) return;
|
||||
|
||||
const shrinkable = visibleColumns
|
||||
.map((col) => ({
|
||||
col,
|
||||
reducible: sizes[col.id] - getEffectiveMinWidth(col),
|
||||
}))
|
||||
.filter((entry) => entry.reducible > 0);
|
||||
|
||||
const totalReducible = shrinkable.reduce((sum, entry) => sum + entry.reducible, 0);
|
||||
if (totalReducible <= 0) return;
|
||||
|
||||
let remaining = overflow;
|
||||
shrinkable.forEach((entry, index) => {
|
||||
const isLast = index === shrinkable.length - 1;
|
||||
const share = isLast
|
||||
? remaining
|
||||
: Math.floor(overflow * (entry.reducible / totalReducible));
|
||||
const delta = Math.min(remaining, share, entry.reducible);
|
||||
sizes[entry.col.id] -= delta;
|
||||
remaining -= delta;
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user