From a5f2110773b0263a42915f683166597cb8165306 Mon Sep 17 00:00:00 2001 From: nazarli-shabnam Date: Tue, 5 May 2026 15:21:23 +0400 Subject: [PATCH 1/4] fix(ui): increase dropdown z-index to appear above modals --- ui/src/components/work-item/Dropdown.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/components/work-item/Dropdown.tsx b/ui/src/components/work-item/Dropdown.tsx index 58474088..ddbcacfd 100644 --- a/ui/src/components/work-item/Dropdown.tsx +++ b/ui/src/components/work-item/Dropdown.tsx @@ -1,7 +1,8 @@ import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -const DROPDOWN_Z_INDEX = 9999; +// Must be above modal root (`z-10050`) so dropdowns work inside modals. +const DROPDOWN_Z_INDEX = 10100; export interface DropdownProps { id: string; From ea8bdf98be2f29887e47350abf5edd3853be72d3 Mon Sep 17 00:00:00 2001 From: nazarli-shabnam Date: Tue, 5 May 2026 15:32:16 +0400 Subject: [PATCH 2/4] fix(ui): improve dropdown positioning and modal panel sizing --- ui/src/components/work-item/Dropdown.tsx | 38 ++++++++++++++++--- .../workspace-views/CreateViewModal.tsx | 7 +++- .../WorkspaceViewsDisplayPanel.tsx | 2 +- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/ui/src/components/work-item/Dropdown.tsx b/ui/src/components/work-item/Dropdown.tsx index ddbcacfd..781133b1 100644 --- a/ui/src/components/work-item/Dropdown.tsx +++ b/ui/src/components/work-item/Dropdown.tsx @@ -3,6 +3,8 @@ import { createPortal } from 'react-dom'; // Must be above modal root (`z-10050`) so dropdowns work inside modals. const DROPDOWN_Z_INDEX = 10100; +const VIEWPORT_PADDING = 8; +const PANEL_GAP = 4; export interface DropdownProps { id: string; @@ -50,6 +52,7 @@ export function Dropdown({ top: number; left?: number; right?: number; + maxHeight?: number; } | null>(null); const open = openId === id; @@ -64,14 +67,36 @@ export function Dropdown({ setPosition(null); return; } - const rect = triggerRef.current.getBoundingClientRect(); + const triggerRect = triggerRef.current.getBoundingClientRect(); + const panelRect = panelRef.current?.getBoundingClientRect(); + const panelHeight = panelRect?.height ?? 0; + const panelWidth = panelRect?.width ?? 0; + + const availableBelow = window.innerHeight - triggerRect.bottom - VIEWPORT_PADDING - PANEL_GAP; + const availableAbove = triggerRect.top - VIEWPORT_PADDING - PANEL_GAP; + + // Prefer opening below, but flip above when below space is tighter. + let top = triggerRect.bottom + PANEL_GAP; + if (panelHeight > 0 && availableBelow < panelHeight && availableAbove > availableBelow) { + top = Math.max(VIEWPORT_PADDING, triggerRect.top - panelHeight - PANEL_GAP); + } + + // Always constrain panel within viewport and allow internal scrolling. + const maxHeight = Math.max(120, Math.max(availableBelow, availableAbove)); + if (align === 'right') { - setPosition({ - top: rect.bottom + 4, - right: window.innerWidth - rect.right, - }); + const unclampedRight = window.innerWidth - triggerRect.right; + const maxRight = Math.max( + VIEWPORT_PADDING, + window.innerWidth - panelWidth - VIEWPORT_PADDING, + ); + const right = Math.min(Math.max(unclampedRight, VIEWPORT_PADDING), maxRight); + setPosition({ top, right, maxHeight }); } else { - setPosition({ top: rect.bottom + 4, left: rect.left }); + const unclampedLeft = triggerRect.left; + const maxLeft = Math.max(VIEWPORT_PADDING, window.innerWidth - panelWidth - VIEWPORT_PADDING); + const left = Math.min(Math.max(unclampedLeft, VIEWPORT_PADDING), maxLeft); + setPosition({ top, left, maxHeight }); } }, [open, align]); @@ -123,6 +148,7 @@ export function Dropdown({ top: position.top, ...(position.left !== undefined && { left: position.left }), ...(position.right !== undefined && { right: position.right }), + ...(position.maxHeight !== undefined && { maxHeight: position.maxHeight }), zIndex: DROPDOWN_Z_INDEX, }} > diff --git a/ui/src/components/workspace-views/CreateViewModal.tsx b/ui/src/components/workspace-views/CreateViewModal.tsx index 51811568..f1f3ef61 100644 --- a/ui/src/components/workspace-views/CreateViewModal.tsx +++ b/ui/src/components/workspace-views/CreateViewModal.tsx @@ -34,8 +34,11 @@ export function CreateViewModal({ open, onClose, onCreated }: CreateViewModalPro useEffect(() => { if (open) { + setOpenPanel(null); setLocalFilters(contextFilters); setLocalDisplay(contextDisplay); + } else { + setOpenPanel(null); } }, [open, contextFilters, contextDisplay]); @@ -125,7 +128,7 @@ export function CreateViewModal({ open, onClose, onCreated }: CreateViewModalPro displayValue="" triggerContent={Filters} triggerClassName="inline-flex items-center justify-center rounded-md border border-(--border-subtle) bg-(--bg-surface-1) px-3 py-2 text-sm font-medium text-(--txt-primary) hover:bg-(--bg-layer-1-hover)" - panelClassName="flex w-[280px] max-h-[min(70vh,28rem)] flex-col rounded-md border border-(--border-subtle) bg-(--bg-surface-1) shadow-(--shadow-raised) overflow-hidden" + panelClassName="flex w-[min(280px,calc(100vw-2rem))] max-h-[min(52vh,22rem)] flex-col overflow-hidden rounded-md border border-(--border-subtle) bg-(--bg-surface-1) shadow-(--shadow-raised)" align="left" > {workspaceSlug && ( @@ -149,7 +152,7 @@ export function CreateViewModal({ open, onClose, onCreated }: CreateViewModalPro displayValue="" triggerContent={Display} triggerClassName="inline-flex items-center justify-center rounded-md border border-(--border-subtle) bg-(--bg-surface-1) px-3 py-2 text-sm font-medium text-(--txt-primary) hover:bg-(--bg-layer-1-hover)" - panelClassName="flex min-w-[280px] max-w-[320px] flex-col rounded-md border border-(--border-subtle) bg-(--bg-surface-1) shadow-(--shadow-raised) overflow-hidden" + panelClassName="flex w-[min(320px,calc(100vw-2rem))] max-h-[min(52vh,22rem)] flex-col overflow-hidden rounded-md border border-(--border-subtle) bg-(--bg-surface-1) shadow-(--shadow-raised)" align="left" > diff --git a/ui/src/components/workspace-views/WorkspaceViewsDisplayPanel.tsx b/ui/src/components/workspace-views/WorkspaceViewsDisplayPanel.tsx index 2c5949e1..2fb02b52 100644 --- a/ui/src/components/workspace-views/WorkspaceViewsDisplayPanel.tsx +++ b/ui/src/components/workspace-views/WorkspaceViewsDisplayPanel.tsx @@ -28,7 +28,7 @@ export function WorkspaceViewsDisplayPanel({

Display Properties

-
+
{DISPLAY_PROPERTY_KEYS.map((key) => { const selected = display.properties.includes(key); return ( From bab2e309d08ca99e6862ea34ef82e1dc129c9760 Mon Sep 17 00:00:00 2001 From: nazarli-shabnam Date: Tue, 5 May 2026 15:37:18 +0400 Subject: [PATCH 3/4] refactor(workspace-views): remove unused max-height style from filters panel content --- .../workspace-views/WorkspaceViewsFiltersPanel.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ui/src/components/workspace-views/WorkspaceViewsFiltersPanel.tsx b/ui/src/components/workspace-views/WorkspaceViewsFiltersPanel.tsx index 0a12085b..a68c7566 100644 --- a/ui/src/components/workspace-views/WorkspaceViewsFiltersPanel.tsx +++ b/ui/src/components/workspace-views/WorkspaceViewsFiltersPanel.tsx @@ -29,8 +29,6 @@ import type { LabelApiResponse, } from '../../api/types'; -const LONG_LIST_PANEL_STYLE = { maxHeight: 'min(70vh, 28rem)' }; - export interface WorkspaceViewsFiltersPanelProps { filters: WorkspaceViewFilters; onFiltersChange: (updater: (prev: WorkspaceViewFilters) => WorkspaceViewFilters) => void; @@ -130,10 +128,7 @@ export function WorkspaceViewsFiltersPanel({ !search.trim() || label.toLowerCase().includes(search.trim().toLowerCase()); const content = ( -
+
Date: Tue, 5 May 2026 15:39:58 +0400 Subject: [PATCH 4/4] refactor(workspace-views): consolidate dropdown state and improve positioning --- ui/src/components/layout/PageHeader.tsx | 16 +++++++++------- .../WorkspaceViewsDisplayDropdown.tsx | 2 +- .../WorkspaceViewsEllipsisMenu.tsx | 18 ++++++++++++++++-- .../WorkspaceViewsFiltersDropdown.tsx | 2 +- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/ui/src/components/layout/PageHeader.tsx b/ui/src/components/layout/PageHeader.tsx index 28edc0d3..56002204 100644 --- a/ui/src/components/layout/PageHeader.tsx +++ b/ui/src/components/layout/PageHeader.tsx @@ -2912,8 +2912,7 @@ function WorkspaceViewsHeader() { }>(); const navigate = useNavigate(); const [viewDropdownOpen, setViewDropdownOpen] = useState(null); - const [filtersDropdownOpen, setFiltersDropdownOpen] = useState(null); - const [displayDropdownOpen, setDisplayDropdownOpen] = useState(null); + const [toolbarDropdownOpen, setToolbarDropdownOpen] = useState(null); const [createViewModalOpen, setCreateViewModalOpen] = useState(false); const [viewSearch, setViewSearch] = useState(''); const [customViews, setCustomViews] = useState([]); @@ -3017,17 +3016,20 @@ function WorkspaceViewsHeader() {
diff --git a/ui/src/components/workspace-views/WorkspaceViewsDisplayDropdown.tsx b/ui/src/components/workspace-views/WorkspaceViewsDisplayDropdown.tsx index ceb0f890..de441137 100644 --- a/ui/src/components/workspace-views/WorkspaceViewsDisplayDropdown.tsx +++ b/ui/src/components/workspace-views/WorkspaceViewsDisplayDropdown.tsx @@ -45,7 +45,7 @@ export function WorkspaceViewsDisplayDropdown({ label="Display" icon={} displayValue="Display" - panelClassName="flex min-w-[280px] max-w-[320px] flex-col rounded-md border border-(--border-subtle) bg-(--bg-surface-1) shadow-(--shadow-raised) overflow-hidden" + panelClassName="flex w-[min(320px,calc(100vw-2rem))] max-h-[min(52vh,22rem)] flex-col overflow-hidden rounded-md border border-(--border-subtle) bg-(--bg-surface-1) shadow-(--shadow-raised)" align="right" > diff --git a/ui/src/components/workspace-views/WorkspaceViewsEllipsisMenu.tsx b/ui/src/components/workspace-views/WorkspaceViewsEllipsisMenu.tsx index 7fc49ee7..cd0729a1 100644 --- a/ui/src/components/workspace-views/WorkspaceViewsEllipsisMenu.tsx +++ b/ui/src/components/workspace-views/WorkspaceViewsEllipsisMenu.tsx @@ -47,7 +47,9 @@ const IconMoreVertical = () => ( ); -const DROPDOWN_Z_INDEX = 9999; +const DROPDOWN_Z_INDEX = 10100; +const VIEWPORT_PADDING = 8; +const PANEL_GAP = 4; export function WorkspaceViewsEllipsisMenu() { const location = useLocation(); @@ -57,6 +59,7 @@ export function WorkspaceViewsEllipsisMenu() { const [position, setPosition] = useState<{ top: number; right: number; + maxHeight?: number; } | null>(null); const fullUrl = @@ -68,10 +71,20 @@ export function WorkspaceViewsEllipsisMenu() { return; } const rect = triggerRef.current.getBoundingClientRect(); + const panelRect = panelRef.current?.getBoundingClientRect(); + const panelHeight = panelRect?.height ?? 0; + const availableBelow = window.innerHeight - rect.bottom - VIEWPORT_PADDING - PANEL_GAP; + const availableAbove = rect.top - VIEWPORT_PADDING - PANEL_GAP; + let top = rect.bottom + PANEL_GAP; + if (panelHeight > 0 && availableBelow < panelHeight && availableAbove > availableBelow) { + top = Math.max(VIEWPORT_PADDING, rect.top - panelHeight - PANEL_GAP); + } + const maxHeight = Math.max(120, Math.max(availableBelow, availableAbove)); queueMicrotask(() => setPosition({ - top: rect.bottom + 4, + top, right: window.innerWidth - rect.right, + maxHeight, }), ); }, [open]); @@ -123,6 +136,7 @@ export function WorkspaceViewsEllipsisMenu() { position: 'fixed', top: position.top, right: position.right, + ...(position.maxHeight !== undefined && { maxHeight: position.maxHeight }), zIndex: DROPDOWN_Z_INDEX, }} > diff --git a/ui/src/components/workspace-views/WorkspaceViewsFiltersDropdown.tsx b/ui/src/components/workspace-views/WorkspaceViewsFiltersDropdown.tsx index 3fb5bde6..d0833ae9 100644 --- a/ui/src/components/workspace-views/WorkspaceViewsFiltersDropdown.tsx +++ b/ui/src/components/workspace-views/WorkspaceViewsFiltersDropdown.tsx @@ -26,7 +26,7 @@ export function WorkspaceViewsFiltersDropdown({ label="Filters" icon={} displayValue="Filters" - panelClassName="flex w-[280px] max-h-[min(70vh,28rem)] flex-col rounded-md border border-(--border-subtle) bg-(--bg-surface-1) shadow-(--shadow-raised) overflow-hidden" + panelClassName="flex w-[min(280px,calc(100vw-2rem))] max-h-[min(52vh,22rem)] flex-col overflow-hidden rounded-md border border-(--border-subtle) bg-(--bg-surface-1) shadow-(--shadow-raised)" align="right" >