Skip to content

Commit

Permalink
Added isAlert prop so blocking modal/dialog don't need to have the 'a…
Browse files Browse the repository at this point in the history
…lertdialog' role (#18298)
  • Loading branch information
tringakrasniqi authored Jun 7, 2021
1 parent 5410ece commit e816200
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Blocking Dialog/Modal always gets assigned role \"alertdialog\" when IsBlocking is true",
"packageName": "@fluentui/react",
"email": "[email protected]",
"dependentChangeType": "patch"
}
1 change: 1 addition & 0 deletions packages/react/etc/react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5979,6 +5979,7 @@ export interface IModalProps extends React_2.RefAttributes<HTMLDivElement>, IAcc
containerClassName?: string;
dragOptions?: IDragOptions;
enableAriaHiddenSiblings?: boolean;
isAlert?: boolean;
isBlocking?: boolean;
isDarkOverlay?: boolean;
isModeless?: boolean;
Expand Down
4 changes: 3 additions & 1 deletion packages/react/src/components/Modal/Modal.base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export const ModalBase: React.FunctionComponent<IModalProps> = React.forwardRef<
forceFocusInsideTrap,
ignoreExternalFocusing,
isBlocking,
isAlert,
isClickableOutsideFocusTrap,
isDarkOverlay,
onDismiss,
Expand Down Expand Up @@ -150,6 +151,7 @@ export const ModalBase: React.FunctionComponent<IModalProps> = React.forwardRef<
}));

const { keepInBounds } = dragOptions || ({} as IDragOptions);
const isAlertRole = isAlert ?? (isBlocking && !isModeless);

const layerClassName = layerProps === undefined ? '' : layerProps.className;
const classNames = getClassNames(styles, {
Expand Down Expand Up @@ -446,7 +448,7 @@ export const ModalBase: React.FunctionComponent<IModalProps> = React.forwardRef<
(isModalOpen && modalResponsiveMode! >= (responsiveMode || ResponsiveMode.small) && (
<Layer ref={mergedRef} {...mergedLayerProps}>
<Popup
role={isModeless || !isBlocking ? 'dialog' : 'alertdialog'}
role={isAlertRole ? 'alertdialog' : 'dialog'}
aria-modal={!isModeless}
ariaLabelledBy={titleAriaId}
ariaDescribedBy={subtitleAriaId}
Expand Down
95 changes: 95 additions & 0 deletions packages/react/src/components/Modal/Modal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as path from 'path';
import { isConformant } from '../../common/isConformant';
import { safeCreate } from '@fluentui/test-utilities';
import { resetIds } from '../../Utilities';
import { Popup } from '../Popup/Popup';

describe('Modal', () => {
beforeEach(() => {
Expand Down Expand Up @@ -89,4 +90,98 @@ describe('Modal', () => {
},
);
});

it('renders a Modal with ARIA role alertDialog when isAlert is true ', () => {
// Mock createPortal to capture its component hierarchy in snapshot output.
const ReactDOM = require('react-dom');
ReactDOM.createPortal = jest.fn(element => {
return element;
});

safeCreate(
<Modal isOpen={true} isAlert={true} className={'test-className'} containerClassName={'test-containerClassName'}>
Test Content
</Modal>,
component => {
const componentInstance = component.root;
expect(componentInstance.findByType(Popup).props.role).toBe('alertdialog');
ReactDOM.createPortal.mockClear();
},
);
});

it('renders Modal with ARIA role dialog when isModeless and isBlocking are set to true', () => {
// Mock createPortal to capture its component hierarchy in snapshot output.
const ReactDOM = require('react-dom');
ReactDOM.createPortal = jest.fn(element => {
return element;
});

safeCreate(
<Modal
isOpen={true}
isAlert={false}
isModeless={true}
isBlocking={true}
className={'test-className'}
containerClassName={'test-containerClassName'}
>
Test Content
</Modal>,
component => {
const componentInstance = component.root;
expect(componentInstance.findByType(Popup).props.role).toBe('dialog');
ReactDOM.createPortal.mockClear();
},
);
});

it('renders Modal with ARIA role dialog when isAlert is false', () => {
// Mock createPortal to capture its component hierarchy in snapshot output.
const ReactDOM = require('react-dom');
ReactDOM.createPortal = jest.fn(element => {
return element;
});

safeCreate(
<Modal
isOpen={true}
isAlert={false}
isBlocking={true}
className={'test-className'}
containerClassName={'test-containerClassName'}
>
Test Content
</Modal>,
component => {
const componentInstance = component.root;
expect(componentInstance.findByType(Popup).props.role).toBe('dialog');
ReactDOM.createPortal.mockClear();
},
);
});

it('renders Modal with ARIA role alertdialog when isBlocking is true', () => {
// Mock createPortal to capture its component hierarchy in snapshot output.
const ReactDOM = require('react-dom');
ReactDOM.createPortal = jest.fn(element => {
return element;
});

safeCreate(
<Modal
isOpen={true}
isBlocking={true}
className={'test-className'}
containerClassName={'test-containerClassName'}
>
Test Content
</Modal>,
component => {
const componentInstance = component.root;
expect(componentInstance.findByType(Popup).props.role).toBe('alertdialog');
ReactDOM.createPortal.mockClear();
},
);
});
});
8 changes: 8 additions & 0 deletions packages/react/src/components/Modal/Modal.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ export interface IModalProps extends React.RefAttributes<HTMLDivElement>, IAcces
*/
isModeless?: boolean;

/**
* Determines the ARIA role of the dialog (alertdialog/dialog)
* If this is set, it will override the ARIA role determined by isBlocking and isModeless
*
* For more information regarding dialogs please see https://w3c.github.io/aria-practices/#alertdialog
*/
isAlert?: boolean;

/**
* Optional class name to be added to the root class
*/
Expand Down

0 comments on commit e816200

Please sign in to comment.