From 014ce2ef3cd552f12b479ea06021524bf1979c8f Mon Sep 17 00:00:00 2001 From: Sanne de Vries Date: Tue, 7 Jul 2026 11:54:28 +0200 Subject: [PATCH 1/2] Fix dropdown clipping in filter and segment modals - Add `grow` mode to `Modal` that lets the panel grow with content and defers scroll to the outer overlay - `ModalLayout` opts in, so filter and segment modals stop clipping combobox popovers - Breakdown modals keep the bounded behavior as inner content scrolls - Remove dead `viewport`/`handleResize`/`DEFAULT_WIDTH`/`getStyle`/`allowScroll` plumbing --- .../js/dashboard/components/modal-layout.tsx | 10 +--- .../stats/modals/breakdown-table.tsx | 4 +- assets/js/dashboard/stats/modals/modal.js | 56 ++++++------------- 3 files changed, 22 insertions(+), 48 deletions(-) diff --git a/assets/js/dashboard/components/modal-layout.tsx b/assets/js/dashboard/components/modal-layout.tsx index 8a321b2265f6..46f37b791f9b 100644 --- a/assets/js/dashboard/components/modal-layout.tsx +++ b/assets/js/dashboard/components/modal-layout.tsx @@ -7,21 +7,15 @@ export function ModalLayout({ title, onClose, children, - maxWidth = '460px', - allowScroll = true + maxWidth = '460px' }: { title: ReactNode onClose: () => void children: ReactNode maxWidth?: string - allowScroll?: boolean }) { return ( - +

diff --git a/assets/js/dashboard/stats/modals/breakdown-table.tsx b/assets/js/dashboard/stats/modals/breakdown-table.tsx index ad57e28f9185..deeaec1d1192 100644 --- a/assets/js/dashboard/stats/modals/breakdown-table.tsx +++ b/assets/js/dashboard/stats/modals/breakdown-table.tsx @@ -47,7 +47,7 @@ export function BreakdownTable({ onClose ?? (() => navigate({ path: rootRoute.path, search: (s) => s })) return ( -
+

@@ -76,7 +76,7 @@ export function BreakdownTable({

-
+
{displayError && status === 'error' && } {isPending && } {columns && data && ( diff --git a/assets/js/dashboard/stats/modals/modal.js b/assets/js/dashboard/stats/modals/modal.js index 261ec624a0f5..30e6a68be1ff 100644 --- a/assets/js/dashboard/stats/modals/modal.js +++ b/assets/js/dashboard/stats/modals/modal.js @@ -1,33 +1,26 @@ import React, { useEffect } from 'react' import { createPortal } from 'react-dom' +import classNames from 'classnames' import { isModifierPressed, isTyping, Keybind } from '../../keybinding' import { rootRoute } from '../../router' import { useAppNavigate } from '../../navigation/use-app-navigate' -// We assume that the dashboard is by default opened on a desktop. This is also a fall-back for when, for any reason, the width is not ascertained. -const DEFAULT_WIDTH = 1080 + class Modal extends React.Component { constructor(props) { super(props) - this.state = { - viewport: DEFAULT_WIDTH - } this.node = React.createRef() this.handleClickOutside = this.handleClickOutside.bind(this) - this.handleResize = this.handleResize.bind(this) } componentDidMount() { document.body.style.overflow = 'hidden' document.body.style.height = '100vh' document.addEventListener('mousedown', this.handleClickOutside) - window.addEventListener('resize', this.handleResize, false) - this.handleResize() } componentWillUnmount() { document.body.style.overflow = null document.body.style.height = null document.removeEventListener('mousedown', this.handleClickOutside) - window.removeEventListener('resize', this.handleResize, false) } handleClickOutside(e) { if (this.node.current.contains(e.target)) { @@ -35,34 +28,21 @@ class Modal extends React.Component { } this.props.onClose() } - handleResize() { - this.setState({ viewport: window.innerWidth }) - } - /** - * @description - * Decide whether to set max-width, and if so, to what. - * If no max-width is available, set width instead to min-content such that we can rely on widths set on th. - * On >md, we use the same behaviour as before: set width to 800 pixels. - * Note that When a max-width comes from the parent component, we rely on that *always*. - */ - getStyle() { - const { maxWidth } = this.props - const styleObject = {} - if (maxWidth) { - styleObject.maxWidth = maxWidth - } else { - styleObject.maxWidth = '880px' - } - return styleObject - } - - getOverflowClass() { - return this.props.allowScroll === true - ? 'overflow-y-auto' - : 'overflow-hidden' - } render() { + // `grow` lets the panel grow with its content (the overlay handles + // scroll). Leave it off when a child uses `flex-1 overflow-auto` for + // its own inner scroll region (e.g. `BreakdownTable`). + const grow = this.props.grow === true + + const panelClass = classNames( + 'w-full flex flex-col bg-white p-3 md:px-6 md:py-4 box-border shadow-2xl rounded-lg dark:bg-gray-900 focus:outline-hidden', + { + 'max-h-[calc(100dvh_-_var(--gap)*2)] transition-[height] duration-200 ease-in': + !grow + } + ) + return createPortal( <>
-
+
From 4c968e7d8cb06400192135740e4cf3776b566278 Mon Sep 17 00:00:00 2001 From: Sanne de Vries Date: Wed, 8 Jul 2026 16:24:00 +0200 Subject: [PATCH 2/2] Fix breakdown modal clipping issue on screens with narrow height --- assets/js/dashboard/stats/modals/breakdown-table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/dashboard/stats/modals/breakdown-table.tsx b/assets/js/dashboard/stats/modals/breakdown-table.tsx index deeaec1d1192..3ee02351ea69 100644 --- a/assets/js/dashboard/stats/modals/breakdown-table.tsx +++ b/assets/js/dashboard/stats/modals/breakdown-table.tsx @@ -47,7 +47,7 @@ export function BreakdownTable({ onClose ?? (() => navigate({ path: rootRoute.path, search: (s) => s })) return ( -
+