From bf51fd73c1963a1bcf4ab5f3476054f5d512a0df Mon Sep 17 00:00:00 2001 From: FenjuFu <92919259+FenjuFu@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:52:49 +0800 Subject: [PATCH] fix(web): keep select menus within viewport Cap shared Radix select content to the available viewport height and enable vertical scrolling so long option lists remain usable near page and dialog edges. Add a shared component regression test for the viewport and overflow classes.\n\nCloses #714 Signed-off-by: FenjuFu <92919259+FenjuFu@users.noreply.github.com> --- web/src/shared/ui/select.test.ts | 8 ++++++++ web/src/shared/ui/select.tsx | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/web/src/shared/ui/select.test.ts b/web/src/shared/ui/select.test.ts index 0768fd3a..9da2727f 100644 --- a/web/src/shared/ui/select.test.ts +++ b/web/src/shared/ui/select.test.ts @@ -40,6 +40,14 @@ describe('shared select contract', () => { expect(SELECT_ITEM_CLASS_NAME).toContain('rounded-md') }) + it('keeps long option lists inside the available viewport', () => { + expect(SELECT_CONTENT_CLASS_NAME).toContain( + 'max-h-[var(--radix-select-content-available-height)]' + ) + expect(SELECT_CONTENT_CLASS_NAME).toContain('overflow-y-auto') + expect(SELECT_CONTENT_CLASS_NAME).toContain('overflow-x-hidden') + }) + it('uses pointer cursors for expanded select interactions', () => { expect(SELECT_ITEM_CLASS_NAME).toContain('cursor-pointer') expect(SELECT_SCROLL_BUTTON_CLASS_NAME).toContain('cursor-pointer') diff --git a/web/src/shared/ui/select.tsx b/web/src/shared/ui/select.tsx index f45cc4f5..3f0c72ba 100644 --- a/web/src/shared/ui/select.tsx +++ b/web/src/shared/ui/select.tsx @@ -12,7 +12,7 @@ export const SELECT_TRIGGER_CLASS_NAME = cn( ) export const SELECT_CONTENT_CLASS_NAME = cn( - 'z-50 overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-md', + 'z-50 max-h-[var(--radix-select-content-available-height)] overflow-x-hidden overflow-y-auto rounded-lg border border-border bg-popover text-popover-foreground shadow-md', // In-tree (no Portal): avoids React 19 removeChild races on route unmount. // No exit animations: delayed unmount still races commits when Content was portaled. 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',