From 0483f3ce44941a42201838bf7cdfca318e16117e Mon Sep 17 00:00:00 2001 From: Tofik Hasanov Date: Fri, 12 Jun 2026 12:48:18 -0400 Subject: [PATCH] fix(framework-editor): flip Linked Controls panel up when clipped at bottom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Linked Controls panel always opened downward (absolute top-0), so on rows near the bottom of the framework it extended past the table's overflow-auto container and got clipped — you couldn't see the rest of the panel. Measure the space below the cell when the panel opens and anchor it to the bottom (grow upward) when there's less than ~340px of room, mirroring the existing ComboboxCell behavior. Closes FRAME-8 Co-Authored-By: Claude Opus 4.8 --- .../components/table/RelationalCell.test.tsx | 32 +++++++++++++++++++ .../app/components/table/RelationalCell.tsx | 13 ++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/apps/framework-editor/app/components/table/RelationalCell.test.tsx b/apps/framework-editor/app/components/table/RelationalCell.test.tsx index 7efbf39b59..5d9f8bb4c1 100644 --- a/apps/framework-editor/app/components/table/RelationalCell.test.tsx +++ b/apps/framework-editor/app/components/table/RelationalCell.test.tsx @@ -86,3 +86,35 @@ describe('RelationalCell on uncommitted rows', () => { ); }); }); + +describe('RelationalCell panel placement (FRAME-8)', () => { + beforeEach(() => vi.clearAllMocks()); + + it('opens downward when there is room below', () => { + renderCell(); + fireEvent.click(screen.getByText('None')); + const panel = screen.getByText('Linked Controls').closest('.bg-popover'); + expect(panel?.className).toContain('top-0'); + expect(panel?.className).not.toContain('bottom-0'); + }); + + it('flips upward when the cell is near the viewport bottom', () => { + renderCell(); + const trigger = screen.getByText('None').closest('div') as HTMLDivElement; + // window.innerHeight is 768 in jsdom; a cell ending at 760 leaves 8px below. + vi.spyOn(trigger, 'getBoundingClientRect').mockReturnValue({ + bottom: 760, + top: 740, + left: 0, + right: 100, + width: 100, + height: 20, + x: 0, + y: 740, + toJSON: () => ({}), + } as DOMRect); + fireEvent.click(trigger); + const panel = screen.getByText('Linked Controls').closest('.bg-popover'); + expect(panel?.className).toContain('bottom-0'); + }); +}); diff --git a/apps/framework-editor/app/components/table/RelationalCell.tsx b/apps/framework-editor/app/components/table/RelationalCell.tsx index b29d5fc33d..638c561ca8 100644 --- a/apps/framework-editor/app/components/table/RelationalCell.tsx +++ b/apps/framework-editor/app/components/table/RelationalCell.tsx @@ -42,6 +42,7 @@ export function RelationalCell({ const [search, setSearch] = useState(''); const [allItems, setAllItems] = useState([]); const [isLoading, setIsLoading] = useState(false); + const [dropUp, setDropUp] = useState(false); const containerRef = useRef(null); // Close when clicking outside @@ -118,7 +119,13 @@ export function RelationalCell({ return (
setIsExpanded(true)} + onClick={(e) => { + // Flip the panel upward when there isn't room below the cell, so it + // isn't clipped by the table's overflow-auto scroll container. + const rect = e.currentTarget.getBoundingClientRect(); + setDropUp(window.innerHeight - rect.bottom < 340); + setIsExpanded(true); + }} > {items.length === 0 ? ( None @@ -134,7 +141,9 @@ export function RelationalCell({ // Expanded view - show all items with controls return (
{/* Header */}