mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
Apply data grid sizing updates.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
9d3d5865fb
commit
7050e96058
@ -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")}
|
||||
>
|
||||
|
||||
@ -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", () => {
|
||||
|
||||
@ -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]);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user