Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuration of close, cancel buttons on Dialog #203

Merged
merged 2 commits into from
Sep 30, 2021
Merged
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
40 changes: 29 additions & 11 deletions src/components/Dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ function useUniqueId(prefix) {
* "Cancel" button will be displayed.
* @prop {'dialog'|'alertdialog'} [role] - The aria role for the dialog (defaults to" dialog")
* @prop {string} title
* @prop {boolean} [withCancelButton=true] - If `onCancel` is provided, render
* a Cancel button as one of the Dialog's buttons (along with any other
* `buttons`)
* @prop {boolean} [withCloseButton=true] - If `onCancel` is provided, render
* a close button (X icon) in the Dialog's header
*/

/**
Expand All @@ -73,6 +78,8 @@ export function Dialog({
onCancel,
role = 'dialog',
title,
withCancelButton = true,
withCloseButton = true,
}) {
const dialogDescriptionId = useUniqueId('dialog-description');
const dialogTitleId = useUniqueId('dialog-title');
Expand Down Expand Up @@ -112,12 +119,16 @@ export function Dialog({
}
}, [dialogDescriptionId]);

const hasCancelButton = onCancel && withCancelButton;
const hasCloseButton = onCancel && withCloseButton;
const hasButtons = buttons || hasCancelButton;

return (
<div
aria-labelledby={dialogTitleId}
className={classnames(
'Hyp-Dialog',
{ 'Hyp-Dialog--closeable': !!onCancel },
{ 'Hyp-Dialog--closeable': hasCloseButton },
contentClass
)}
ref={rootEl}
Expand All @@ -133,21 +144,28 @@ export function Dialog({
<h2 className="Hyp-Dialog__title" id={dialogTitleId}>
{title}
</h2>
{onCancel && (
{onCancel && withCloseButton && (
<div className="Hyp-Dialog__close">
<IconButton icon="hyp-cancel" title="Close" onClick={onCancel} />
<IconButton
data-testid="close-button"
icon="hyp-cancel"
title="Close"
onClick={onCancel}
/>
</div>
)}
</header>
{children}
<div className="Hyp-Dialog__actions">
{onCancel && (
<LabeledButton data-testid="cancel-button" onClick={onCancel}>
{cancelLabel}
</LabeledButton>
)}
{buttons}
</div>
{hasButtons && (
<div className="Hyp-Dialog__actions">
{hasCancelButton && (
<LabeledButton data-testid="cancel-button" onClick={onCancel}>
{cancelLabel}
</LabeledButton>
)}
{buttons}
</div>
)}
</div>
);
}
41 changes: 40 additions & 1 deletion src/components/test/Dialog-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,19 @@ describe('Dialog', () => {
assert.isTrue(icon.exists());
});

it('closes when close button is clicked', () => {
it('renders cancel and close buttons by default if `onCancel` provided', () => {
const onCancel = sinon.stub();
const wrapper = mount(<Dialog title="Test dialog" onCancel={onCancel} />);

assert.isTrue(
wrapper.find('LabeledButton[data-testid="cancel-button"]').exists()
);
assert.isTrue(
wrapper.find('IconButton[data-testid="close-button"]').exists()
);
});

it('invokes `onCancel` callback when cancel button is clicked', () => {
const onCancel = sinon.stub();
const wrapper = mount(<Dialog title="Test dialog" onCancel={onCancel} />);

Expand All @@ -61,6 +73,15 @@ describe('Dialog', () => {
assert.called(onCancel);
});

it('invokes `onCancel` callback when close button is clicked', () => {
const onCancel = sinon.stub();
const wrapper = mount(<Dialog title="Test dialog" onCancel={onCancel} />);

wrapper.find('IconButton[data-testid="close-button"]').props().onClick();

assert.called(onCancel);
});

it(`defaults cancel button's label to "Cancel"`, () => {
const wrapper = mount(<Dialog onCancel={sinon.stub()} />);
assert.equal(
Expand All @@ -69,6 +90,24 @@ describe('Dialog', () => {
);
});

it('does not render a cancel button if `withCancelButton` is disabled', () => {
const wrapper = mount(
<Dialog onCancel={sinon.stub()} withCancelButton={false} />
);
assert.isFalse(
wrapper.find('LabeledButton[data-testid="cancel-button"]').exists()
);
});

it('does not render a close button if `withCloseButton` is disabled', () => {
const wrapper = mount(
<Dialog onCancel={sinon.stub()} withCloseButton={false} />
);
assert.isFalse(
wrapper.find('IconButton[data-testid="close-button"]').exists()
);
});

it('adds a custom label to the cancel button', () => {
const wrapper = mount(
<Dialog onCancel={sinon.stub()} cancelLabel="hello" />
Expand Down
69 changes: 69 additions & 0 deletions src/pattern-library/components/patterns/DialogComponents.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,58 @@ function DialogExample() {
}
}

function DialogWithoutCancelButtonExample() {
const [dialogOpen, setDialogOpen] = useState(false);

if (!dialogOpen) {
return (
<LabeledButton
onClick={() => setDialogOpen(!dialogOpen)}
variant="primary"
>
Show Dialog Example
</LabeledButton>
);
} else {
return (
<Dialog
icon="edit"
onCancel={() => setDialogOpen(false)}
title="Dialog: No cancel button"
withCancelButton={false}
>
<p>This is a Dialog that has a close button but no Cancel button.</p>
</Dialog>
);
}
}

function DialogWithoutCloseButtonExample() {
const [dialogOpen, setDialogOpen] = useState(false);

if (!dialogOpen) {
return (
<LabeledButton
onClick={() => setDialogOpen(!dialogOpen)}
variant="primary"
>
Show Dialog Example
</LabeledButton>
);
} else {
return (
<Dialog
icon="edit"
onCancel={() => setDialogOpen(false)}
title="Dialog: no close button"
withCloseButton={false}
>
<p>This is a Dialog that has a Cancel button but no close button.</p>
</Dialog>
);
}
}

function ModalExample() {
const [dialogOpen, setDialogOpen] = useState(false);
const focusRef = createRef();
Expand Down Expand Up @@ -206,6 +258,23 @@ export default function DialogComponents() {
<DialogExample />
</Library.Demo>
</Library.Example>

<Library.Example title="Dialog with no cancel or close button">
<p>
By default, a <code>Dialog</code> will render both a close button
(x) and a Cancel button if an <code>onCancel</code> callback is
provided, but this can be overridden with the{' '}
<code>withCancelButton</code> and <code>withCloseButton</code>{' '}
boolean props for the cancel button and close button, respectively.
</p>
<Library.Demo title="Dialog with no Cancel button">
<DialogWithoutCancelButtonExample />
</Library.Demo>

<Library.Demo title="Dialog with no Close button">
<DialogWithoutCloseButtonExample />
</Library.Demo>
</Library.Example>
</Library.Pattern>

<Library.Pattern title="Modal">
Expand Down