Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { FluentProvider } from '@fluentui/react-provider';
import { webLightTheme } from '@fluentui/react-theme';

import { testDrawerBaseScenarios } from '../../e2e/DrawerShared';
import { MotionBehaviourProvider } from '@fluentui/react-motion';
import { Dialog, DialogActions, DialogBody, DialogSurface, DialogTrigger } from '@fluentui/react-dialog';
import { OverlayDrawer } from './OverlayDrawer';
import type { OverlayDrawerProps } from './OverlayDrawer.types';
import { overlayDrawerClassNames } from './useOverlayDrawerStyles.styles';
Expand Down Expand Up @@ -83,6 +85,65 @@ describe('OverlayDrawer', () => {
});
});

// https://github.com/microsoft/fluentui/issues/35389
describe('mountNode with modalType="non-modal"', () => {
const ExampleDrawerInContainer = () => {
const [container, setContainer] = React.useState<HTMLDivElement | null>(null);

return (
<MotionBehaviourProvider value="skip">
<input id="outside-input" />
<Dialog>
<DialogTrigger disableButtonEnhancement>
<button id="open-dialog">Open Dialog</button>
</DialogTrigger>
<DialogSurface>
<DialogBody>
<DialogActions>
<DialogTrigger disableButtonEnhancement>
<button id="close-dialog">Close Dialog</button>
</DialogTrigger>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
<div ref={setContainer} style={{ position: 'relative', width: 500, height: 300, overflow: 'hidden' }}>
{container && (
<OverlayDrawer open mountNode={container} modalType="non-modal">
<input id="drawer-input" />
</OverlayDrawer>
)}
</div>
</MotionBehaviourProvider>
);
};

it('should keep focus on elements outside the container', () => {
mountFluent(<ExampleDrawerInContainer />);

cy.get('#drawer-input').realClick();
cy.get('#drawer-input').should('be.focused');

cy.get('#outside-input').realClick();
// Tabster pulls focus back into a trapped modalizer after a delay, so wait past it
cy.wait(300);
cy.get('#outside-input').should('be.focused');
});

it('should let focus return to the Drawer after a modal Dialog outside the container closes', () => {
mountFluent(<ExampleDrawerInContainer />);

cy.get('#open-dialog').realClick();
cy.get('#close-dialog').realClick();
cy.get('#open-dialog').should('be.focused');

cy.get('#drawer-input').realClick();
cy.wait(300);
cy.get('#drawer-input').should('be.focused');
cy.get('.fui-OverlayDrawer').should('not.have.attr', 'aria-hidden');
});
});

describe('body scroll', () => {
it('should hide scroll when opened', () => {
const ExampleDrawer = (props: OverlayDrawerProps) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export const OverlayInsideContainer = (): JSXElement => {
as="aside"
{...restoreFocusSourceAttributes}
mountNode={ref.current}
// The rest of the page stays visible and interactive, so the Drawer is not modal.
modalType="non-modal"
open={isOpen}
onOpenChange={(_, { open }) => setIsOpen(open)}
>
Expand Down Expand Up @@ -87,6 +89,10 @@ OverlayInsideContainer.parameters = {
story: [
'The overlay Drawer can be rendered inside a specific container by setting the `mountNode` prop to the desired container element.',
'This approach is useful when you need the Drawer to appear within a particular section of the DOM, rather than being attached to the root element.',
'',
'A Drawer inside a container only covers that container, so the rest of the page stays visible and interactive.',
'In that case, set `modalType="non-modal"`: a modal Drawer traps focus and hides the rest of the page from assistive technology,',
'which conflicts with users clicking or tabbing to the controls outside the container (for example, a header that opens other dialogs).',
].join('\n'),
},
},
Expand Down