Skip to content

Commit

Permalink
remove nested portals (#2209)
Browse files Browse the repository at this point in the history
  • Loading branch information
mayank99 authored Aug 27, 2024
1 parent 22a6844 commit d0de9f2
Show file tree
Hide file tree
Showing 16 changed files with 263 additions and 121 deletions.
5 changes: 5 additions & 0 deletions .changeset/blue-shirts-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@itwin/itwinui-react': patch
---

Fixed an issue where nested `Popover`s were automatically closing due to faulty "outside click" detection.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 18 additions & 30 deletions packages/itwinui-react/src/core/Popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import {
import type { PolymorphicForwardRefComponent } from '../../utils/index.js';
import { usePortalTo } from '../../utils/components/Portal.js';
import type { PortalProps } from '../../utils/components/Portal.js';
import { ThemeProvider } from '../ThemeProvider/ThemeProvider.js';

type PopoverOptions = {
/**
Expand Down Expand Up @@ -399,13 +398,7 @@ export const Popover = React.forwardRef((props, forwardedRef) => {
middleware,
});

const [popoverElement, setPopoverElement] = React.useState<HTMLElement>();

const popoverRef = useMergedRefs(
popover.refs.setFloating,
forwardedRef,
setPopoverElement,
);
const popoverRef = useMergedRefs(popover.refs.setFloating, forwardedRef);

const triggerId = `${useId()}-trigger`;
const hasAriaLabel = !!props['aria-labelledby'] || !!props['aria-label'];
Expand All @@ -430,28 +423,23 @@ export const Popover = React.forwardRef((props, forwardedRef) => {

{popover.open ? (
<PopoverPortal portal={portal}>
<ThemeProvider
portalContainer={popoverElement} // portal nested popovers into this one
>
<DisplayContents />
<FloatingFocusManager context={popover.context} modal={false}>
<Box
className={cx(
{ 'iui-popover-surface': applyBackground },
className,
)}
aria-labelledby={
!hasAriaLabel
? popover.refs.domReference.current?.id
: undefined
}
{...popover.getFloatingProps(rest)}
ref={popoverRef}
>
{content}
</Box>
</FloatingFocusManager>
</ThemeProvider>
<FloatingFocusManager context={popover.context} modal={false}>
<Box
className={cx(
{ 'iui-popover-surface': applyBackground },
className,
)}
aria-labelledby={
!hasAriaLabel
? popover.refs.domReference.current?.id
: undefined
}
{...popover.getFloatingProps(rest)}
ref={popoverRef}
>
{content}
</Box>
</FloatingFocusManager>
</PopoverPortal>
) : null}
</>
Expand Down
2 changes: 1 addition & 1 deletion packages/itwinui-react/src/core/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -960,7 +960,7 @@ export const Table = <
return (
<ColumnHeader<T>
{...dragAndDropProps}
key={dragAndDropProps.key}
key={dragAndDropProps.key || column.id || index}
columnRefs={columnRefs}
column={column}
index={index}
Expand Down
78 changes: 58 additions & 20 deletions testing/e2e/app/routes/Popover/route.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,69 @@
import { Popover } from '@itwin/itwinui-react';
import * as React from 'react';
import { ComboBox, Popover } from '@itwin/itwinui-react';
import { useSearchParams } from '@remix-run/react';

export default function Page() {
const [searchParams] = useSearchParams();

if (searchParams.get('nestedComboBox')) {
return <NestedComboBoxTest />;
}

return <FocusTest />;
}

// ----------------------------------------------------------------------------

function NestedComboBoxTest() {
return (
<Popover
applyBackground
content={
<div>
<h2>Popover title</h2>
<ComboBox
options={React.useMemo(
() => [
{ label: 'Item 1', value: 1 },
{ label: 'Item 2', value: 2 },
{ label: 'Item 3', value: 3 },
],
[],
)}
/>
</div>
}
>
<button>Open</button>
</Popover>
);
}

// ----------------------------------------------------------------------------

function FocusTest() {
const [searchParams] = useSearchParams();

const shouldImperativeFocus = searchParams.get('imperativeFocus') === 'true';
const shouldFocusInput = searchParams.get('focusInput') === 'true';

return (
<>
<Popover
applyBackground
content={
<div>
<h2
tabIndex={-1}
ref={shouldImperativeFocus ? (el) => el?.focus() : undefined}
>
Popover title
</h2>
<p>Popover content</p>
{shouldFocusInput && <input />}
</div>
}
>
<button>Open</button>
</Popover>
</>
<Popover
applyBackground
content={
<div>
<h2
tabIndex={-1}
ref={shouldImperativeFocus ? (el) => el?.focus() : undefined}
>
Popover title
</h2>
<p>Popover content</p>
{shouldFocusInput && <input />}
</div>
}
>
<button>Open</button>
</Popover>
);
}
20 changes: 20 additions & 0 deletions testing/e2e/app/routes/Popover/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,23 @@ test.describe('Popover (focus)', () => {
await expect(page.getByRole('heading')).toBeFocused();
});
});

test.describe('Nested popovers', () => {
test('should work with ComboBox', async ({ page }) => {
await page.goto('/Popover?nestedComboBox=true');
await page.click('button');

const popover = page.getByRole('dialog');
const comboboxInput = page.getByRole('combobox');
const comboboxListbox = page.getByRole('listbox');

await expect(popover).toBeVisible();
await expect(comboboxInput).toBeVisible();
await expect(comboboxListbox).toBeVisible();

// close the combobox
await comboboxInput.click();
await expect(comboboxListbox).not.toBeVisible();
await expect(popover).toBeVisible();
});
});
Loading

0 comments on commit d0de9f2

Please sign in to comment.