From 6c672a3427b329e7c6df11954a765401fe4c30fd Mon Sep 17 00:00:00 2001 From: Developing-Gamer Date: Mon, 13 Jul 2026 16:00:24 -0700 Subject: [PATCH] Improve analytics tables layout and data grid scrolling - Make the analytics tables page fill the shell height and scroll internally so rows never paint under the sticky translucent header - Add horizontalScrollbarPosition option to DataGrid (top puts the horizontal scrollbar under the column headers) with header/body scroll syncing and wheel forwarding, plus tests - Add an opaque dark-mode underlay behind the floating header to fix the bright seam when content scrolls behind it - Scope the CmdK trigger glow to the button itself instead of a document-level mousemove listener that recomposited the header on every cursor move --- .../analytics/tables/page-client.tsx | 146 ++++++++++-------- .../analytics/tables/query-data-grid.tsx | 4 + .../projects/[projectId]/sidebar-layout.tsx | 7 + apps/dashboard/src/components/cmdk-search.tsx | 30 ++-- .../components/data-grid/data-grid.test.tsx | 34 +++- .../src/components/data-grid/data-grid.tsx | 67 +++++++- .../src/components/data-grid/types.ts | 7 + 7 files changed, 205 insertions(+), 90 deletions(-) diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/page-client.tsx index a360dfb28..9bd9490c0 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/page-client.tsx @@ -27,8 +27,6 @@ type TableConfig = { }; type TableId = string; -const ANALYTICS_TABLES_STICKY_TOP = "var(--analytics-tables-sticky-top)"; -const ANALYTICS_TABLES_SIDEBAR_HEIGHT = "var(--analytics-tables-sidebar-height)"; const AVAILABLE_TABLES = new Map([ [ @@ -200,7 +198,9 @@ function TableContent({ tableId }: { tableId: TableId }) { ); return ( -
+ // fillHeight grid owns its own scrollport below the page header, so rows never + // paint under the sticky translucent header (the dark-mode seam bug). +
- - -
- {/* Left sidebar — hidden on mobile */} -
-
- - Tables - -
- {[...AVAILABLE_TABLES.entries()].map(([id, config]) => ( - - ))} -
- - - - Queries - -
+ {/* containedHeight: page fills the shell under the header and scrolls internally, + so table rows cannot travel behind the floating dark-mode header. */} + +
+
+
- {/* Right content — flush to card edge; companion gap is on
in sidebar-layout */} -
div:first-child>div]:pt-3", - "[&_[role=grid]_.sticky>div:first-child>div]:pb-2.5", - "[&_[role=grid]_.sticky>div:first-child>div]:pr-0", - "[&_[role=grid]_.sticky>div:first-child>div]:pl-2.5", - )} - > - {selectedTable ? ( - - ) : ( -
- - Select a table to view its contents - + {/* Match the primary nav's dark:rounded-2xl so the gap junction mirrors + the same radius on both sides (nav top-right ↔ tables top-left). */} +
+ {/* Use the same surface treatment as the primary sidebar so equal radii render + identically. Omit the right border to keep the sidebar/grid junction divider-free. */} +
+
+
+ + Tables + +
+ {[...AVAILABLE_TABLES.entries()].map(([id, config]) => ( + + ))} +
+ + + + Queries + +
- )} +
+ + {/* Right content — grid fills remaining height and scrolls internally */} +
div:first-child>div]:pt-3", + "[&_[role=grid]_.sticky>div:first-child>div]:pb-2.5", + "[&_[role=grid]_.sticky>div:first-child>div]:pr-0", + "[&_[role=grid]_.sticky>div:first-child>div]:pl-2.5", + )} + style={{ + // Outer top-right corner of the block. Same clip-path trick as the + // sidebar: the grid's sticky chrome is its own composited layer and + // paints square over the parent's rounded overflow clip without this. + borderTopRightRadius: "1rem", + clipPath: "inset(0 round 0 1rem 0 0)", + }} + > + {selectedTable ? ( + + ) : ( +
+ + Select a table to view its contents + +
+ )} +
diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/query-data-grid.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/query-data-grid.tsx index 6b1141158..72d60146b 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/query-data-grid.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/query-data-grid.tsx @@ -133,6 +133,8 @@ export type QueryDataGridProps = { fillHeight?: boolean, /** Sticky top offset forwarded to the underlying DataGrid. */ stickyTop?: number | string, + /** Where the horizontal scrollbar is shown when columns overflow. */ + horizontalScrollbarPosition?: "top" | "bottom", }; export type QueryDataGridHandle = { @@ -329,6 +331,7 @@ export const QueryDataGrid = forwardRef footer = false, fillHeight = true, stickyTop, + horizontalScrollbarPosition, }, ref, ) { @@ -654,6 +657,7 @@ export const QueryDataGrid = forwardRef selectionMode="none" fillHeight={fillHeight} stickyTop={stickyTop} + horizontalScrollbarPosition={horizontalScrollbarPosition} toolbar={resolvedToolbar} toolbarExtra={resolvedToolbarExtra} footer={footer ? undefined : false} diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx index 9e2cf81ce..f9de99020 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx @@ -731,6 +731,13 @@ export default function SidebarLayout(props: { children?: React.ReactNode }) { WebkitMaskImage: 'linear-gradient(to bottom, black 50%, transparent 100%)', }} /> + {/* Opaque underlay for dark mode. Table/page content scrolls under this sticky + header; without a solid base, the translucent fill + backdrop-filter composite + a bright seam along the bottom edge whenever rows pass behind it. */} +
{/* Left section: Logo + Menu + Project Switcher */}
diff --git a/apps/dashboard/src/components/cmdk-search.tsx b/apps/dashboard/src/components/cmdk-search.tsx index 300098eb3..22b98ccbd 100644 --- a/apps/dashboard/src/components/cmdk-search.tsx +++ b/apps/dashboard/src/components/cmdk-search.tsx @@ -1005,20 +1005,21 @@ export function CmdKSearch({ // Trigger button component that can be placed in the header export function CmdKTrigger() { const mouseCursorRef = useRef(null); - const mouseCursorParentRef = useRef(null); const modifierKeyLabel = getShortcutModifierKeyLabel(); - useEffect(() => { - const handleMouseMove = (e: MouseEvent) => { - if (mouseCursorRef.current && mouseCursorParentRef.current) { - const rect = mouseCursorParentRef.current.getBoundingClientRect(); - mouseCursorRef.current.style.left = `${e.clientX - rect.left}px`; - mouseCursorRef.current.style.top = `${e.clientY - rect.top}px`; - mouseCursorRef.current.style.display = "block"; - } - }; - document.addEventListener("mousemove", handleMouseMove); - return () => document.removeEventListener("mousemove", handleMouseMove); + const handleMouseMove = useCallback((event: React.MouseEvent) => { + const cursor = mouseCursorRef.current; + if (cursor == null) { + return; + } + + // Keep this glow local to the trigger. A document-level mousemove listener used to + // reposition a blurred layer under the translucent header on every cursor move across + // the page, continuously recompositing the sticky header while table rows scrolled + // behind it and making the header's bottom edge flicker. + const rect = event.currentTarget.getBoundingClientRect(); + cursor.style.left = `${event.clientX - rect.left}px`; + cursor.style.top = `${event.clientY - rect.top}px`; }, []); return ( @@ -1026,6 +1027,7 @@ export function CmdKTrigger() {