Apply data grid sizing updates.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Developing-Gamer 2026-05-27 12:31:14 -07:00
parent 9d3d5865fb
commit 7050e96058
3 changed files with 80 additions and 48 deletions

View File

@ -84,16 +84,16 @@ function QuickSearch({
}) {
return (
<div className="relative flex min-w-0 flex-1 items-center sm:flex-initial">
<MagnifyingGlass className="absolute left-2.5 h-3.5 w-3.5 text-muted-foreground/50 pointer-events-none" />
<MagnifyingGlass className="absolute left-2.5 h-3.5 w-3.5 text-muted-foreground/60 pointer-events-none" />
<input
type="text"
className={cn(
"h-8 w-full sm:w-52 pl-8 pr-7 rounded-xl text-xs",
"bg-background",
"border border-black/[0.08] dark:border-white/[0.08]",
"placeholder:text-muted-foreground/40",
"border border-black/[0.08] dark:border-white/[0.06]",
"bg-white dark:bg-background shadow-sm ring-1 ring-black/[0.08] dark:ring-white/[0.06]",
"placeholder:text-muted-foreground/50",
"focus:outline-none focus:ring-1 focus:ring-foreground/[0.1]",
"transition-all duration-150",
"transition-all duration-150 hover:transition-none hover:ring-black/[0.12] dark:hover:ring-white/[0.1]",
)}
placeholder={placeholder}
value={value}
@ -234,13 +234,13 @@ function ColumnManager<TRow>({
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
{strings.dateFormat}
</span>
<div className="inline-flex items-center gap-0.5 rounded-lg bg-foreground/[0.04] p-0.5">
<div className="inline-flex items-center gap-0.5 rounded-lg bg-zinc-100/90 p-0.5 ring-1 ring-black/[0.06] dark:bg-foreground/[0.04] dark:ring-white/[0.06]">
<button
className={cn(
"px-2 py-0.5 rounded-md text-[11px] font-medium transition-colors duration-75",
"px-2 py-0.5 rounded-md text-[11px] font-medium transition-colors duration-150 hover:transition-none",
dateDisplay === "relative"
? "bg-background text-foreground shadow-sm ring-1 ring-foreground/[0.06]"
: "text-muted-foreground hover:text-foreground",
? "bg-white text-foreground shadow-sm ring-1 ring-black/[0.12] dark:bg-background dark:ring-white/[0.06]"
: "text-muted-foreground hover:text-foreground hover:bg-white/50 dark:hover:bg-white/[0.06]",
)}
onClick={() => onDateDisplayChange("relative")}
>
@ -248,10 +248,10 @@ function ColumnManager<TRow>({
</button>
<button
className={cn(
"px-2 py-0.5 rounded-md text-[11px] font-medium transition-colors duration-75",
"px-2 py-0.5 rounded-md text-[11px] font-medium transition-colors duration-150 hover:transition-none",
dateDisplay === "absolute"
? "bg-background text-foreground shadow-sm ring-1 ring-foreground/[0.06]"
: "text-muted-foreground hover:text-foreground",
? "bg-white text-foreground shadow-sm ring-1 ring-black/[0.12] dark:bg-background dark:ring-white/[0.06]"
: "text-muted-foreground hover:text-foreground hover:bg-white/50 dark:hover:bg-white/[0.06]",
)}
onClick={() => onDateDisplayChange("absolute")}
>

View File

@ -101,8 +101,43 @@ class MockIntersectionObserver implements IntersectionObserver {
}
class MockResizeObserver implements ResizeObserver {
private readonly callback: ResizeObserverCallback;
constructor(callback: ResizeObserverCallback) {
this.callback = callback;
}
disconnect() {}
observe() {}
observe(target: Element) {
const el = target instanceof HTMLElement ? target : null;
const parentWidth = el?.parentElement instanceof HTMLElement ? el.parentElement.clientWidth : 0;
const width = (el?.clientWidth ?? 0) > 0 ? (el?.clientWidth ?? 320) : parentWidth > 0 ? parentWidth : 320;
const height = (el?.clientHeight ?? 0) > 0 ? (el?.clientHeight ?? 400) : 400;
this.callback(
[
{
target,
contentRect: {
x: 0,
y: 0,
width,
height,
top: 0,
left: 0,
right: width,
bottom: height,
toJSON() {
return this;
},
} as DOMRectReadOnly,
borderBoxSize: [],
contentBoxSize: [],
devicePixelContentBoxSize: [],
},
],
this,
);
}
unobserve() {}
}
@ -307,6 +342,12 @@ describe("DataGrid controlled callbacks", () => {
describe("DataGrid horizontal scrolling", () => {
beforeEach(() => {
vi.stubGlobal("ResizeObserver", MockResizeObserver);
Object.defineProperty(HTMLElement.prototype, "clientWidth", {
configurable: true,
get() {
return 320;
},
});
vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(
function getBoundingClientRect() {
return {
@ -344,13 +385,17 @@ describe("DataGrid horizontal scrolling", () => {
vi.unstubAllGlobals();
});
it("sizes the sticky clipping layer to the full row width", () => {
it("does not keep the full unscaled column width in a narrow container", async () => {
const { container } = render(<WideDataGridHarness />);
const rowsClip = container.querySelector("[data-data-grid-rows-clip]");
expect(rowsClip).toBeInstanceOf(HTMLElement);
expect((rowsClip as HTMLElement).style.minWidth).toBe("740px");
await waitFor(() => {
const widthPx = Number.parseInt((rowsClip as HTMLElement).style.minWidth, 10);
expect(widthPx).toBeLessThan(740);
expect(widthPx).toBeLessThanOrEqual(320);
});
});
it("lets the columns popover escape the sticky toolbar bounds", () => {

View File

@ -36,7 +36,7 @@ import React, {
} from "react";
import { DesignSkeleton } from "../skeleton";
import { DEFAULT_COL_WIDTH, clampColumnWidth, getEffectiveMaxWidth, getEffectiveMinWidth } from "./data-grid-sizing";
import { DEFAULT_COL_WIDTH, clampColumnWidth, fitColumnsToContainer, getEffectiveMaxWidth, getEffectiveMinWidth } from "./data-grid-sizing";
import { DataGridToolbar } from "./data-grid-toolbar";
import { exportToCsv, formatGridDate, resolveColumnValue } from "./state";
import { resolveDataGridStrings } from "./strings";
@ -104,29 +104,6 @@ function resolveUpdater<T>(updater: Updater<T>, current: T): T {
return typeof updater === "function" ? (updater as (old: T) => T)(current) : updater;
}
// ─── Flex column width distribution ──────────────────────────────────
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 = col.maxWidth ?? Infinity;
const add = Math.max(0, Math.min(share, max - sizes[col.id]));
sizes[col.id] += add;
remaining -= add;
});
}
// ─── Selection logic (with shift-range anchor) ───────────────────────
type SelectionInput = {
@ -512,9 +489,10 @@ function DefaultFooter<TRow>({
<span>{strings.rowsPerPage}</span>
<select
className={cn(
"h-7 rounded-lg border border-black/[0.08] dark:border-white/[0.08] bg-background px-1.5",
"h-7 rounded-lg border border-black/[0.08] dark:border-white/[0.06] px-1.5",
"bg-white dark:bg-background shadow-sm ring-1 ring-black/[0.08] dark:ring-white/[0.06]",
"text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-foreground/[0.1]",
"cursor-pointer",
"cursor-pointer transition-all duration-150 hover:transition-none hover:ring-black/[0.12] dark:hover:ring-white/[0.1]",
)}
value={state.pagination.pageSize}
onChange={(e) => setPageSize(Number(e.target.value))}
@ -798,12 +776,22 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
const grid = gridRef.current;
const scroller = scrollContainerRef.current;
if (!grid) return;
const update = () => {
const w = scroller?.clientWidth ?? grid.clientWidth;
if (w > 0) setContainerWidth(w);
const update = (entries?: ResizeObserverEntry[]) => {
let measured = 0;
if (entries != null) {
for (const entry of entries) {
measured = Math.max(measured, entry.contentRect.width);
}
}
if (measured <= 0) {
const scrollerWidth = scroller?.clientWidth ?? 0;
const gridWidth = grid.clientWidth;
measured = scrollerWidth > 0 ? scrollerWidth : gridWidth;
}
if (measured > 0) setContainerWidth(measured);
};
update();
const observer = new ResizeObserver(update);
const observer = new ResizeObserver((entries) => update(entries));
observer.observe(grid);
if (scroller) observer.observe(scroller);
return () => observer.disconnect();
@ -814,7 +802,7 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
const columnSizingInfo = table.getState().columnSizingInfo;
const columnSizes = useMemo<Record<string, number>>(() => {
const sizes: Record<string, number> = {};
let baseTotal = selectionMode !== "none" ? 44 : 0;
const selectionChromeWidth = selectionMode !== "none" ? 44 : 0;
const resizingId = columnSizingInfo.isResizingColumn || null;
const deltaOffset = columnSizingInfo.deltaOffset ?? 0;
for (const col of visibleColumns) {
@ -824,9 +812,8 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
? clampColumnWidth(col, baseSize + deltaOffset)
: baseSize;
sizes[col.id] = liveSize;
baseTotal += liveSize;
}
distributeFlexWidths(sizes, visibleColumns, containerWidth - baseTotal);
fitColumnsToContainer(sizes, visibleColumns, containerWidth, selectionChromeWidth);
return sizes;
}, [visibleColumns, table, columnSizingInfo, state.columnWidths, containerWidth, selectionMode]);