Skip to content

Commit

Permalink
Add modal prop to Dialog and document it in pattern library
Browse files Browse the repository at this point in the history
  • Loading branch information
lyzadanger committed Mar 9, 2023
1 parent 45d8822 commit 3cbf797
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 15 deletions.
45 changes: 30 additions & 15 deletions src/components/feedback/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import classnames from 'classnames';
import type { RefObject } from 'preact';
import { Fragment } from 'preact';
import { useEffect, useLayoutEffect } from 'preact/hooks';

import { useElementShouldClose } from '../../hooks/use-element-should-close';
Expand All @@ -14,16 +15,23 @@ import type { PanelProps } from '../layout/Panel';
type ComponentProps = {
/**
* Dialog _should_ be provided with a close handler. We have a few edge use
* cases, however, in which we need to render a "non-closeable" modal.
* cases, however, in which we need to render a "non-closeable" modal dialog.
*/
onClose?: () => void;
width?: 'sm' | 'md' | 'lg' | 'custom';

/**
* Element that should take focus when the Dialog is first rendered. When not
* provided ("auto"), the dialog's outer element will take focus. Setting this
* prop to "manual" will opt out of focus routing.
*/
initialFocus?: RefObject<HTMLOrSVGElement | null> | 'auto' | 'manual';

/**
* Make this Dialog modal. Modal dialogs are:
* - Rendered with a full-screen overlay backdrop
*/
modal?: boolean;
};

// This component forwards a number of props on to `Panel` but always sets the
Expand All @@ -44,6 +52,7 @@ const DialogNext = function Dialog({
classes,
elementRef,
initialFocus = 'auto',
modal = false,

// Forwarded to Panel
buttons,
Expand Down Expand Up @@ -108,31 +117,37 @@ const DialogNext = function Dialog({
[dialogDescriptionId, modalRef]
);

// Wrap modal dialogs in a full-screen overlay
const Wrapper = modal ? Overlay : Fragment;

return (
<Overlay>
<Wrapper>
<div
aria-modal={modal}
data-component="Dialog"
tabIndex={-1}
// NB: Role can be overridden with an HTML attribute; this is purposeful
role="dialog"
{...htmlAttributes}
className={classnames(
// Maximum width and height should not exceed 90vw/h
'max-w-[90vw] max-h-[90vh]',
// Overlay sets up a flex layout centered on both axes. For taller
// viewports, remove this modal container from the flex flow with
// fixed positioning and position it 10vh from the top of the
// viewport. This feels more balanced on taller screens. Ensure an
// equal 10vh gap at the bottom of the screen by adjusting max-height
// to `80vh`.
'tall:fixed tall:max-h-[80vh] tall:top-[10vh]',
// Column-flex layout to constrain content to max-height
'flex flex-col',
{
// Maximum width and height should not exceed 90vw/h
'max-w-[90vw] max-h-[90vh]': modal,
// Overlay sets up a flex layout centered on both axes. For taller
// viewports, remove this modal container from the flex flow with
// fixed positioning and position it 10vh from the top of the
// viewport. This feels more balanced on taller screens. Ensure an
// equal 10vh gap at the bottom of the screen by adjusting max-height
// to `80vh`.
'tall:fixed tall:max-h-[80vh] tall:top-[10vh]': modal,
},
{
// Max-width rules will ensure actual width never exceeds 90vw
'w-[30rem]': width === 'sm',
'w-[36rem]': width === 'md', // default
'w-[42rem]': width === 'lg',
'w-[30rem]': modal && width === 'sm',
'w-[36rem]': modal && width === 'md', // default
'w-[42rem]': modal && width === 'lg',
// No width classes are added if width is 'custom'
},
classes
Expand All @@ -151,7 +166,7 @@ const DialogNext = function Dialog({
{children}
</Panel>
</div>
</Overlay>
</Wrapper>
);
};

Expand Down
34 changes: 34 additions & 0 deletions src/components/feedback/test/Dialog-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,38 @@ describe('Dialog', () => {
assert.isNull(content.getAttribute('aria-describedby'));
});
});

describe('modal Dialogs', () => {
let nonModalWrapper;
let modalWrapper;

beforeEach(() => {
nonModalWrapper = mount(
<Dialog title="My dialog">
<p>Dialog content</p>
</Dialog>
);

modalWrapper = mount(
<Dialog title="My dialog" modal>
<p>Modal Dialog content</p>
</Dialog>
);
});

it('should render a backdrop for modal dialogs', () => {
assert.isFalse(nonModalWrapper.find('Overlay').exists());
assert.isTrue(modalWrapper.find('Overlay').exists());
});

it('should set aria-modal for modal dialogs', () => {
const nonModalContainer = nonModalWrapper
.find('[role="dialog"]')
.getDOMNode();
const modalContainer = modalWrapper.find('[role="dialog"]').getDOMNode();

assert.equal(nonModalContainer.getAttribute('aria-modal'), 'false');
assert.equal(modalContainer.getAttribute('aria-modal'), 'true');
});
});
});
45 changes: 45 additions & 0 deletions src/pattern-library/components/patterns/feedback/DialogPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,51 @@ export default function DialogPage() {
</InputGroup>
</Dialog_>
</Library.Demo>

<Library.Demo title="Basic modal Dialog" withSource>
<Dialog_
buttons={<DialogButtons />}
icon={EditIcon}
initialFocus={inputRef}
onClose={() => {}}
title="Basic dialog"
modal
>
<p>
This is a basic modal Dialog that routes focus initially to a
text input.
</p>
<InputGroup>
<Input name="my-input" elementRef={inputRef} />
<IconButton icon={ArrowRightIcon} variant="dark" title="go" />
</InputGroup>
</Dialog_>
</Library.Demo>
</Library.Pattern>

<Library.Pattern title="Props">
<p>
<em>Note</em>: At present these props only include those that are
additional to or differ from props accepted by the Modal component.
All component props will be documented before Dialog is released.
</p>
<Library.Example title="modal">
<p>
Setting the <code>modal</code> <code>boolean</code> prop (default{' '}
<code>false</code>) indicates that the Dialog should have modal
behavior.
</p>
<p>
<code>modal</code> Dialogs:
</p>
<ul>
<li>Have a full-screen backdrop</li>
<li>
Position themselves on the screen and constrain the Dialog
dimensions based on the viewport
</li>
</ul>
</Library.Example>
</Library.Pattern>
</Library.Section>
</Library.Page>
Expand Down

0 comments on commit 3cbf797

Please sign in to comment.