Skip to content

Commit 44d5fec

Browse files
authored
fix(ui): keep modal popovers interactive (#7435)
1 parent 7baba0e commit 44d5fec

2 files changed

Lines changed: 44 additions & 1 deletion

File tree

packages/emcn/src/components/combobox/combobox.dom.test.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@
1212
import { act, type ReactNode, useState } from 'react'
1313
import { createRoot, type Root } from 'react-dom/client'
1414
import { afterEach, describe, expect, it, vi } from 'vitest'
15+
import { InsideModalContext } from '../modal/modal'
1516
import { Combobox } from './combobox'
1617

18+
vi.mock('next/navigation', () => ({
19+
usePathname: () => '/workspace/workspace-1/home',
20+
}))
21+
1722
let root: Root | null = null
1823
let container: HTMLDivElement | null = null
1924

@@ -42,6 +47,12 @@ function click(node: HTMLElement) {
4247
})
4348
}
4449

50+
function mouseDown(node: HTMLElement) {
51+
act(() => {
52+
node.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))
53+
})
54+
}
55+
4556
function press(node: HTMLElement, key: string) {
4657
act(() => {
4758
node.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true }))
@@ -60,6 +71,7 @@ afterEach(() => {
6071
container?.remove()
6172
root = null
6273
container = null
74+
document.body.removeAttribute('style')
6375
vi.restoreAllMocks()
6476
})
6577

@@ -72,6 +84,28 @@ describe('Combobox onOpenChange', () => {
7284
expect(container?.querySelector('[role="listbox"]')).not.toBeNull()
7385
})
7486

87+
it('keeps portaled options interactive inside modal content', () => {
88+
const onChange = vi.fn()
89+
render(
90+
<InsideModalContext.Provider value>
91+
<Combobox options={OPTIONS} onChange={onChange} />
92+
</InsideModalContext.Provider>
93+
)
94+
95+
click(trigger())
96+
97+
const option = [...document.querySelectorAll<HTMLElement>('[role="option"]')].find(
98+
({ textContent }) => textContent === 'Alpha'
99+
)
100+
if (!option) throw new Error('Alpha option was not rendered')
101+
expect(document.body.style.pointerEvents).toBe('none')
102+
expect(getComputedStyle(option).pointerEvents).toBe('auto')
103+
104+
mouseDown(option)
105+
106+
expect(onChange).toHaveBeenCalledWith('alpha')
107+
})
108+
75109
it('uses the overlay label for the interactive overflow layer', () => {
76110
render(
77111
<Combobox
@@ -97,6 +131,7 @@ describe('Combobox onOpenChange', () => {
97131
click(trigger())
98132

99133
expect(onOpenChange).toHaveBeenCalledWith(true)
134+
expect(document.body.style.pointerEvents).toBe('')
100135
})
101136

102137
it('reports the close a second trigger click causes', () => {

packages/emcn/src/components/popover/popover.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import { createPortal } from 'react-dom'
5656
import { Check, ChevronLeft, ChevronRight, Search } from '../../icons'
5757
import { cn } from '../../lib/cn'
5858
import { chipActiveSurfaceClass, chipHoverSurfaceClass } from '../chip/chip-chrome'
59+
import { InsideModalContext } from '../modal/modal'
5960
import { TOOLTIP_MAX_WIDTH_PX, TOOLTIP_SURFACE_CLASS } from '../tooltip/tooltip-styles'
6061

6162
type PopoverSize = 'sm' | 'md'
@@ -209,8 +210,10 @@ const Popover: React.FC<PopoverProps> = ({
209210
colorScheme = 'default',
210211
open,
211212
onOpenChange,
213+
modal,
212214
...props
213215
}) => {
216+
const insideModal = React.useContext(InsideModalContext)
214217
const [currentFolder, setCurrentFolder] = React.useState<string | null>(null)
215218
const [folderTitle, setFolderTitle] = React.useState<string | null>(null)
216219
const [onFolderSelect, setOnFolderSelect] = React.useState<(() => void) | null>(null)
@@ -329,7 +332,12 @@ const Popover: React.FC<PopoverProps> = ({
329332

330333
return (
331334
<PopoverContext.Provider value={contextValue}>
332-
<PopoverPrimitive.Root open={open} onOpenChange={handleOpenChange} {...props}>
335+
<PopoverPrimitive.Root
336+
open={open}
337+
onOpenChange={handleOpenChange}
338+
modal={insideModal ? true : modal}
339+
{...props}
340+
>
333341
{children}
334342
</PopoverPrimitive.Root>
335343
</PopoverContext.Provider>

0 commit comments

Comments
 (0)