Skip to content

Commit

Permalink
add webui-modal component
Browse files Browse the repository at this point in the history
  • Loading branch information
basher committed Apr 3, 2024
1 parent 9494c87 commit 96931b6
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 166 deletions.
4 changes: 2 additions & 2 deletions ui/npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 0 additions & 85 deletions ui/src/javascript/modules/modal.ts

This file was deleted.

5 changes: 3 additions & 2 deletions ui/src/javascript/ui-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// 1. Modules.
import FormValidate from './modules/form-validate';
import Message from './modules/message';
import Modal from './modules/modal';
import Prose from './modules/prose';
import RangeSlider from './modules/range-slider';
import Search from './modules/search';
Expand All @@ -17,12 +16,12 @@ import demoAjaxFetchHTML from './modules/demo-ajax-fetch-html';
// 3. Web components.
import WebUIDisclosure from './web-components/webui-disclosure';
import WebUIMakeClickable from './web-components/webui-make-clickable';
import WebUIModal from './web-components/webui-modal';
import WebUIShare from './web-components/webui-share';

export const uiInit = (): void => {
FormValidate.start();
Message.start();
Modal.start();
RangeSlider.start();
Search.start();
Slider.start();
Expand All @@ -39,6 +38,8 @@ export const uiInit = (): void => {
customElements.define('webui-disclosure', WebUIDisclosure);
!customElements.get('webui-make-clickable') &&
customElements.define('webui-make-clickable', WebUIMakeClickable);
!customElements.get('webui-modal') &&
customElements.define('webui-modal', WebUIModal);
!customElements.get('webui-share') &&
customElements.define('webui-share', WebUIShare);
};
87 changes: 87 additions & 0 deletions ui/src/javascript/web-components/webui-modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
export default class WebUIModal extends HTMLElement {
private dialog: HTMLDialogElement | null;
private modalContent: HTMLElement | null;
private btnModalOpen: HTMLButtonElement | HTMLAnchorElement | null;
private btnsModalClose: NodeListOf<HTMLElement>;
private classNameModalOpen: string;

constructor() {
super();

this.dialog = this.querySelector('dialog');
this.modalContent = this.querySelector('[data-content]');
this.btnModalOpen = this.querySelector('[data-open]');
this.btnsModalClose = this.querySelectorAll('[data-close]');
this.classNameModalOpen = 'has-modal-open';

this.init();

this.btnModalOpen?.addEventListener('click', this);
this.btnsModalClose.forEach((btnModalClose) => {
btnModalClose?.addEventListener('click', this);
});
}

private init(): void {
if (!this.btnModalOpen || !this.dialog) return;
}

private handleOpen(): void {
if (!this.dialog?.open) {
this.dialog?.showModal();
// Set focus on content rather than the 'close' button.
this.modalContent?.setAttribute('tabIndex', '-1');
this.modalContent?.focus();

document.body.classList.add(this.classNameModalOpen);
}
}

private handleClose(): void {
this.dialog?.close();
// Set focus back on button that opened modal.
this.btnModalOpen?.focus();

document.body.classList.remove(this.classNameModalOpen);
}

private handleGlobalClick(e: MouseEvent): void {
const target = e.target as HTMLElement;
if (
this.dialog?.open &&
!this.dialog.contains(target) &&
!this.btnModalOpen?.contains(target)
) {
this.handleClose();
}
}

// Handle web component events from constructor().
handleEvent(e: MouseEvent) {
const target = e.currentTarget as HTMLButtonElement;

// Click 'open' button.
if (target?.dataset.open === '') {
// Prevent default behaviour on any links that open a modal.
e.preventDefault();

this.handleOpen();
}

// Click 'close' button.
if (target?.dataset.close === '') {
this.handleClose();
}
}

// Add/remove other (global) event listeners which are not part of this web component.
connectedCallback() {
window.addEventListener('click', (e: MouseEvent) =>
this.handleGlobalClick(e),
);
}

disconnectedCallback() {
window.removeEventListener('click', this.handleGlobalClick);
}
}
1 change: 0 additions & 1 deletion ui/src/stylesheets/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
@use 'image';
@use 'image-gallery';
@use 'message';
@use 'modal';
@use 'nav';
@use 'pagination';
@use 'prose';
Expand Down
1 change: 1 addition & 0 deletions ui/src/stylesheets/web-components/_index.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@use 'webui-disclosure';
@use 'webui-make-clickable';
@use 'webui-modal';
@use 'webui-share';
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ Dependencies.
--color-backdrop: hsl(0 0% 0% / 70%);
}

.modal {
webui-modal {
&:not(:defined) {
[data-open] {
display: none;
}
}

// Browser already applies "position: fixed" and "width: fit-content".
&__dialog {
.modal__dialog {
background: $color-neutral-100;
margin: auto;
padding: 0 $gutter-m $gutter-m;
Expand All @@ -24,14 +30,14 @@ Dependencies.
}
}

&__header {
.modal__header {
background: $color-neutral-100;
display: flex;
justify-content: flex-end;
padding-block-start: $gutter-m;
}

&__content {
.modal__content {
@include focus($inset: true);
margin-block-start: $gutter-m;

Expand Down
25 changes: 3 additions & 22 deletions ui/stories/5. Components/Modal/Modal.mdx
Original file line number Diff line number Diff line change
@@ -1,25 +1,6 @@
import { Meta, Canvas } from '@storybook/blocks';
import * as Modal from './Modal.stories';
import { Meta } from '@storybook/blocks';

<Meta of={Modal} />
<Meta title="Components/Modal" />

# Modal
- Uses the native HTML `<dialog>` element. See [dialog](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog) docs on MDN.

## Accessibility considerations
- There are ongoing discussions about the [initial focus behaviour](https://github.com/whatwg/html/wiki/dialog--initial-focus,-a-proposal) for `<dialog>`.
- Keyboard `focus` is currently being set on the `data-modal-content` using `tabindex="-1"` to ensure that content gets announced first, before the `close` button.
- When modal is closed, focus is set back to the button that opened the modal.
- Modal can be closed with the `ESC` key (default browser behaviour), and by clicking outside the modal (JavaScript enhancement).

### Useful articles
- [Is dialog ready to be used?](https://www.scottohara.me/blog/2019/03/05/open-dialog.html)
- [Defending dialog](https://whistlr.info/2021/in-defence-of-dialog/) by the official polyfill maintainer.

<Canvas of={Modal.Modal} />

## Modal with overflowing content
<Canvas of={Modal.ModalOverflow} />

## Modal with multiple close buttons
<Canvas of={Modal.ModalMultipleCloseButtons} />
- See the [`<webui-modal>`](/docs/web-components-or-custom-elements-webui-modal--docs) custom element.
28 changes: 0 additions & 28 deletions ui/stories/5. Components/Modal/Modal.stories.js

This file was deleted.

Loading

0 comments on commit 96931b6

Please sign in to comment.