Skip to content
Open
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
45 changes: 33 additions & 12 deletions src/Modal/ModalDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import classNames from 'classnames';
import { useMediaQuery } from 'react-responsive';
import { useIntl } from 'react-intl';
import ModalLayer from './ModalLayer';

// @ts-ignore for now - this needs to be converted to TypeScript
import ModalCloseButton from './ModalCloseButton';
import ModalDialogHeader from './ModalDialogHeader';
Expand All @@ -17,7 +18,8 @@ import ModalDialogHero from './ModalDialogHero';

import Icon from '../Icon';
import IconButton from '../IconButton';
import { Close } from '../../icons';
import useToggle from '../hooks/useToggleHook';
import { Close, FullscreenExit, Fullscreen } from '../../icons';
import messages from './messages';

interface Props {
Expand All @@ -39,6 +41,10 @@ interface Props {
closeLabel?: string;
/** Specifies class name to append to the base element */
className?: string;
/** Renders the fullscreen icon button in the top right of the dialog box. It will not be redered (even if set to
* true) if the dialog is fullscreen for another reason (e.g. isFullscreenOnMobile is true and the viewport is
* mobile, or the dialog size is already set to fullscreen) */
hasFullscreenButton?: boolean;
/**
* Determines where a scrollbar should appear if a modal is too large for the
* viewport. When false, the ``ModalDialog``. Body receives a scrollbar, when true
Expand Down Expand Up @@ -69,6 +75,7 @@ function ModalDialog({
variant = 'default',
hasCloseButton = true,
closeLabel,
hasFullscreenButton = false,
isFullscreenScroll = false,
className,
isFullscreenOnMobile = false,
Expand All @@ -79,7 +86,10 @@ function ModalDialog({
const intl = useIntl();
const closeButtonText = closeLabel || intl.formatMessage(messages.closeButtonText);
const isMobile = useMediaQuery({ query: '(max-width: 767.98px)' });
const showFullScreen = (isFullscreenOnMobile && isMobile);
const alwaysFullscreen = (isFullscreenOnMobile && isMobile) || size === 'fullscreen';

const [isFullscreen, , , toggleFullscreen] = useToggle(alwaysFullscreen);

return (
<ModalLayer isOpen={isOpen} onClose={onClose} isBlocking={isBlocking} zIndex={zIndex}>
<div
Expand All @@ -88,23 +98,34 @@ function ModalDialog({
className={classNames(
'pgn__modal',
{
[`pgn__modal-${showFullScreen ? 'fullscreen' : size}`]: size,
[`pgn__modal-${isFullscreen ? 'fullscreen' : size}`]: size,
[`pgn__modal-${variant}`]: variant,
'pgn__modal-scroll-fullscreen': isFullscreenScroll,
'pgn__modal-visible-overflow': isOverflowVisible,
},
className,
)}
>
{hasCloseButton && (
<div className="pgn__modal-close-container">
<ModalCloseButton
as={IconButton}
iconAs={Icon}
invertColors={variant === 'dark'}
src={Close}
alt={closeButtonText}
/>
{(hasCloseButton || hasFullscreenButton) && (
<div className="pgn__modal-title-buttons-container">
{hasFullscreenButton && !alwaysFullscreen && (
<IconButton
src={isFullscreen ? FullscreenExit : Fullscreen}
iconAs={Icon}
invertColors={variant === 'dark'}
onClick={toggleFullscreen}
alt={intl.formatMessage(messages.fullscreenButtonText)}
/>
)}
{hasCloseButton && (
<ModalCloseButton
as={IconButton}
iconAs={Icon}
invertColors={variant === 'dark'}
src={Close}
alt={closeButtonText}
/>
)}
</div>
)}
{children}
Expand Down
2 changes: 1 addition & 1 deletion src/Modal/_ModalDialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@

// Subcomponents

.pgn__modal-close-container {
.pgn__modal-title-buttons-container {
position: absolute;
z-index: 10;
top: var(--pgn-spacing-dropdown-close-container-top);
Expand Down
5 changes: 5 additions & 0 deletions src/Modal/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ const messages = defineMessages({
defaultMessage: 'Close',
description: 'Accessible name for the close button in the modal dialog',
},
fullscreenButtonText: {
id: 'pgn.Modal.fullscreenButton',
defaultMessage: 'Fullscreen',
description: 'Accessible name for the fullscreen button in the modal dialog',
},
});

export default messages;
1 change: 1 addition & 0 deletions src/Modal/modal-dialog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ label for the dialog element.
size={modalSize}
variant={modalVariant}
hasCloseButton
hasFullscreenButton
isFullscreenOnMobile
isOverflowVisible={false}
>
Expand Down
105 changes: 105 additions & 0 deletions src/Modal/tests/ModalDialog.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { IntlProvider } from 'react-intl';
import ModalDialog from '../ModalDialog';
Expand Down Expand Up @@ -58,6 +59,110 @@ describe('ModalDialog', () => {

expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});

it('renders a dialog with hasFullscreenButton', async () => {
const user = userEvent.setup();
const onClose = jest.fn();
render(
<IntlProvider locale="en" messages={{}}>
<ModalDialog
title="My dialog"
isOpen
onClose={onClose}
size="md"
variant="default"
hasCloseButton
hasFullscreenButton
isOverflowVisible={false}
>
<ModalDialog.Body>
<p>The content</p>
</ModalDialog.Body>
</ModalDialog>
</IntlProvider>,
);
const dialogNode = screen.getByRole('dialog');

expect(dialogNode).toBeInTheDocument();

const fullscreenButton = screen.getByRole('button', { name: 'Fullscreen' });
expect(fullscreenButton).toBeInTheDocument();
expect(fullscreenButton).toHaveAttribute('aria-label', 'Fullscreen');

await user.click(fullscreenButton);

expect(dialogNode).toHaveClass('pgn__modal-fullscreen');
expect(dialogNode).not.toHaveClass('pgn__modal-md');

await user.click(fullscreenButton);

expect(dialogNode).toHaveClass('pgn__modal-md');
expect(dialogNode).not.toHaveClass('pgn__modal-fullscreen');
});

it('should not render fullscreen button if isFullscreenOnMobile is true and viewport is mobile', async () => {
const onClose = jest.fn();

// Mock useMediaQuery
// eslint-disable-next-line global-require
const reactResponsiveUseMediaQuery = require('react-responsive');
jest.spyOn(reactResponsiveUseMediaQuery, 'useMediaQuery').mockImplementation(() => true);

render(
<IntlProvider locale="en" messages={{}}>
<ModalDialog
title="My dialog"
isOpen
onClose={onClose}
size="md"
variant="default"
hasCloseButton
hasFullscreenButton
isFullscreenOnMobile
isOverflowVisible={false}
>
<ModalDialog.Body>
<p>The content</p>
</ModalDialog.Body>
</ModalDialog>
</IntlProvider>,
);
const dialogNode = screen.getByRole('dialog');

expect(dialogNode).toBeInTheDocument();

const fullscreenButton = screen.queryByRole('button', { name: 'Fullscreen' });
expect(fullscreenButton).not.toBeInTheDocument();
});

it('should not render fullscreen button if size is fullscreen', async () => {
const onClose = jest.fn();

render(
<IntlProvider locale="en" messages={{}}>
<ModalDialog
title="My dialog"
isOpen
onClose={onClose}
size="fullscreen"
variant="default"
hasCloseButton
hasFullscreenButton
isOverflowVisible={false}
>
<ModalDialog.Body>
<p>The content</p>
</ModalDialog.Body>
</ModalDialog>
</IntlProvider>,
);
const dialogNode = screen.getByRole('dialog');

expect(dialogNode).toBeInTheDocument();

const fullscreenButton = screen.queryByRole('button', { name: 'Fullscreen' });
expect(fullscreenButton).not.toBeInTheDocument();
});
});

describe('ModalDialog with Hero', () => {
Expand Down