From 813fa87a7482b04dddeb43ecfa76a3bb5d4ca118 Mon Sep 17 00:00:00 2001 From: Richard Walker Date: Mon, 29 Aug 2022 14:39:15 +1200 Subject: [PATCH 1/6] feat: add modal dialog --- index.js | 1 + package.json | 4 +- packages/modal/index.js | 136 ++++++++++++++++++++++++++++++++++++ packages/modal/svgs.js | 30 ++++++++ packages/modal/test.js | 43 ++++++++++++ pages/components/modal.html | 68 ++++++++++++++++++ pages/includes/nav.html | 4 ++ vite.config.js | 5 ++ 8 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 packages/modal/index.js create mode 100644 packages/modal/svgs.js create mode 100644 packages/modal/test.js create mode 100644 pages/components/modal.html diff --git a/index.js b/index.js index 69705da8..dfd2fbd3 100644 --- a/index.js +++ b/index.js @@ -6,4 +6,5 @@ export * from './packages/toast/toast'; export * from './packages/toast/toast-container'; export * from './packages/toast/api'; export * from './packages/broadcast'; +export * from './packages/modal'; export * from './packages/utils/expand-transition'; diff --git a/package.json b/package.json index 7457ba6a..392d749b 100644 --- a/package.json +++ b/package.json @@ -79,9 +79,11 @@ "vite-plugin-html": "3.2.0" }, "dependencies": { + "@a11y/focus-trap": "^1.0.5", "@fabric-ds/core": "0.0.15", "@open-wc/testing": "3.1.6", - "html-format": "1.0.2" + "html-format": "1.0.2", + "scroll-doctor": "^1.0.1" }, "publishConfig": { "access": "public" diff --git a/packages/modal/index.js b/packages/modal/index.js new file mode 100644 index 00000000..4432c744 --- /dev/null +++ b/packages/modal/index.js @@ -0,0 +1,136 @@ +import { html } from 'lit'; +import { fclasses, FabricElement } from '../utils'; +import { modal as c } from '@fabric-ds/css/component-classes'; +import { leftButtonSvg, rightButtonSvg } from './svgs'; +import { setup, teardown } from 'scroll-doctor'; +import '@a11y/focus-trap'; + +class FabricModal extends FabricElement { + static properties = { + open: { type: Boolean }, + left: { type: Boolean }, + right: { type: Boolean }, + }; + + get _leftButtonClasses() { + return fclasses({ + [c.transitionTitle]: true, + [c.titleButton]: true, + [c.titleButtonLeft]: true, + 'justify-self-start': true, + }); + } + + get _rightButtonClasses() { + return fclasses({ + [c.transitionTitle]: true, + [c.titleButton]: true, + [c.titleButtonRight]: true, + 'justify-self-end': true, + }); + } + + get _titleClasses() { + return fclasses({ + [c.transitionTitle]: true, + 'justify-self-center': !!this.left, + 'col-span-2': !this.left, + }); + } + + updated() { + super.updated(); + if (this.open) { + if (!this._scrollDoctorEnabled) { + setup(this); + this._scrollDoctorEnabled = true; + // // take note of where the focus is for later + this._activeEl = document.activeElement; + // // set focus inside the modal + this.shadowRoot.querySelector('focus-trap').focusFirstElement(); + } + } else { + if (this._scrollDoctorEnabled) { + teardown(); + this._scrollDoctorEnabled = false; + this._activeEl.focus(); + } + } + } + + _containerKeyDown(event) { + if (event.key === 'Escape') this._dismiss(); + } + + _containerClick(event) { + event.stopPropagation(); + } + + _dismiss(event) { + this.dispatchEvent(new CustomEvent('close')); + } + + get _leftButton() { + return html``; + } + + get _rightButton() { + return html``; + } + + render() { + if (!this.open) return html``; + return html` + ${this._fabricStylesheet} + +
+ +
+
+ `; + } +} + +if (!customElements.get('f-modal')) { + customElements.define('f-modal', FabricModal); +} + +export { FabricModal }; diff --git a/packages/modal/svgs.js b/packages/modal/svgs.js new file mode 100644 index 00000000..eb6223cc --- /dev/null +++ b/packages/modal/svgs.js @@ -0,0 +1,30 @@ +import { fclasses } from '../utils'; +import { html } from 'lit'; +import { modal as c } from '@fabric-ds/css/component-classes'; + +export const leftButtonSvg = html` + +`; + +export const rightButtonSvg = html` + + `; diff --git a/packages/modal/test.js b/packages/modal/test.js new file mode 100644 index 00000000..fb08c634 --- /dev/null +++ b/packages/modal/test.js @@ -0,0 +1,43 @@ +/* eslint-disable no-undef */ +import tap, { test, beforeEach, teardown } from 'tap'; +import { chromium } from 'playwright'; +import { addContentToPage } from '../../tests/utils/index.js'; + +tap.before(async () => { + const browser = await chromium.launch({ headless: true }); + tap.context.browser = browser; +}); + +beforeEach(async (t) => { + const { browser } = t.context; + const context = await browser.newContext(); + t.context.page = await context.newPage(); +}); + +teardown(async () => { + const { browser } = tap.context; + browser.close(); +}); + +test('Box component with no attributes is rendered on the page', async (t) => { + // GIVEN: A box component + const component = ` + +

This is a box

+
+ `; + + // WHEN: the component is added to the page + const page = await addContentToPage({ + page: t.context.page, + content: component, + }); + + // THEN: the component is visible in the DOM + const locator = await page.locator('f-box'); + t.equal((await locator.innerHTML()).trim(), '

This is a box

', 'HTML should be rendered'); + t.equal(await locator.getAttribute('bleed'), null, 'Bleed attribute should be null'); + t.equal(await locator.getAttribute('bordered'), null, 'Bordered attribute should be null'); + t.equal(await locator.getAttribute('info'), null, 'Info attribute should be null'); + t.equal(await locator.getAttribute('neutral'), null, 'Neutral attribute should be null'); +}); diff --git a/pages/components/modal.html b/pages/components/modal.html new file mode 100644 index 00000000..9fa5d49b --- /dev/null +++ b/pages/components/modal.html @@ -0,0 +1,68 @@ + + <%- include('head.html'); -%> + + + <%- include('nav.html'); -%> +
+

Modal

+

Modal...

+ +

Props

+ + + + + + + + + + + + + + + +
proptypedefault
open +
boolean
+
Opens the modal
+
false
+ +

Examples

+ Left back button + + + This is the main content +
+ + +
+
+
+
+ + This is the main content +
+ + +
+
+ + +
+
+ <%- include('footer.html'); -%> +
+ + <%- include('scripts.html'); -%> + + diff --git a/pages/includes/nav.html b/pages/includes/nav.html index 0abc6126..04ebbce4 100644 --- a/pages/includes/nav.html +++ b/pages/includes/nav.html @@ -21,6 +21,10 @@ { "title": "Toast", "href": "/pages/components/toast.html" + }, + { + "title": "Modal", + "href": "/pages/components/modal.html" } ] }, diff --git a/vite.config.js b/vite.config.js index d78ce8a7..aae7d966 100644 --- a/vite.config.js +++ b/vite.config.js @@ -74,6 +74,11 @@ export default ({ mode }) => { template: 'pages/components/toast.html', injectOptions, }, + { + filename: 'modal.html', + template: 'pages/components/modal.html', + injectOptions, + }, { filename: 'index.html', template: 'index.html', From 47dc9a74ac548828beef531da0757a25cd0d8358 Mon Sep 17 00:00:00 2001 From: Richard Walker Date: Mon, 29 Aug 2022 14:42:46 +1200 Subject: [PATCH 2/6] --wip-- [skip ci] --- packages/modal/index.js | 51 ++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/packages/modal/index.js b/packages/modal/index.js index 4432c744..cabf737c 100644 --- a/packages/modal/index.js +++ b/packages/modal/index.js @@ -3,7 +3,6 @@ import { fclasses, FabricElement } from '../utils'; import { modal as c } from '@fabric-ds/css/component-classes'; import { leftButtonSvg, rightButtonSvg } from './svgs'; import { setup, teardown } from 'scroll-doctor'; -import '@a11y/focus-trap'; class FabricModal extends FabricElement { static properties = { @@ -46,8 +45,6 @@ class FabricModal extends FabricElement { this._scrollDoctorEnabled = true; // // take note of where the focus is for later this._activeEl = document.activeElement; - // // set focus inside the modal - this.shadowRoot.querySelector('focus-trap').focusFirstElement(); } } else { if (this._scrollDoctorEnabled) { @@ -96,35 +93,31 @@ class FabricModal extends FabricElement { if (!this.open) return html``; return html` ${this._fabricStylesheet} - -
-