diff --git a/README.md b/README.md index c660fd16b2e..7ec039e7bb8 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ npm install @brightspace-ui/core * [Alert](components/alert/): alert components for displaying important information * [Breadcrumbs](components/breadcrumbs/): component to help users understand where they are within an application * [Backdrop](components/backdrop/): component for displaying backdrop behind a target element - * [Buttons](components/button/): normal, primary, icon and subtle buttons + * [Buttons](components/button/): normal, primary, icon, subtle, and toggle buttons * [Calendar](components/calendar/): calendar component * [Card](components/card/): card components * [Colors](components/colors/): color palette diff --git a/components/button/README.md b/components/button/README.md index 656900a5375..5cf0ddf5b33 100644 --- a/components/button/README.md +++ b/components/button/README.md @@ -143,6 +143,34 @@ The `d2l-button-icon` element can be used just like the native `button`, for ins ``` +## Toggle Button [d2l-button-toggle] + +The `d2l-button-toggle` element is a container for buttons that toggle a `pressed` state. The component will automatically show or hide the buttons and manage focus based on the `pressed` state. Simply place a `d2l-button-icon` or `d2l-button-subtle` element in each of the `not-pressed` and `pressed` slots. Each button should describe the state and action the user can take. + + +```html + + + + + +``` + + +### Properties + +| Property | Type | Description | +|--|--|--| +| `pressed` | Boolean | Pressed state | + +### Events + +- `d2l-button-toggle-change`: dispatched when the `pressed` state changes + + ## Add Button [d2l-button-add] The `d2l-button-add` is for quickly adding new items at a specific location, such as when adding items to a curated list. Since the Add button is meant to be subtle, it should always be used in combination with more obvious methods to add items (like a menu or primary button). @@ -220,6 +248,9 @@ Daylight buttons rely on standard button semantics to ensure a smooth experience * For [Icon Buttons](#d2l-button-icon) where there is no visible label, `text` will be displayed in a tooltip * If both `text` and `aria-label` are used, then `aria-label` will be used as the primary label while `text` will be used in a [tooltip](../../components/tooltip) +* [Toggle buttons](#d2l-button-toggle) should describe the current state and the action the user can perform. As such, `aria-pressed` should not be used on the buttons as per [W3C's Button Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/button/#:~:text=Alternatively%2C%20if%20the%20design%20were%20to%20call%20for%20the%20button%20label%20to%20change%20from%20%22Mute%22%20to%20%22Unmute%2C%22%20the%20aria%2Dpressed%20attribute%20would%20not%20be%20needed.). + * Example: "Unpinned, click to pin" and "Pinned, click to unpin" + * [Floating Buttons](#d2l-floating-buttons) maintain their position in the document's structure, despite sticking to the bottom of the viewport, so the tab order is unaffected and the effect is imperceptible to screen reader users * Be cautious when using `always-float`, since screen magnifier users may find it difficult to locate the buttons at the bottom of a large viewport diff --git a/components/button/button-toggle.js b/components/button/button-toggle.js new file mode 100644 index 00000000000..ae81c4f9a63 --- /dev/null +++ b/components/button/button-toggle.js @@ -0,0 +1,97 @@ +import { css, html, LitElement } from 'lit'; + +/** + * A button container component for button toggles. + */ +class ButtonToggle extends LitElement { + + static get properties() { + return { + /** + * Pressed state + * @type {boolean} + */ + pressed: { type: Boolean, reflect: true } + }; + } + + static get styles() { + return css` + :host { + display: inline-block; + } + :host([hidden]) { + display: none; + } + ::slotted(:not(d2l-button-icon, d2l-button-subtle)), + :host slot[name="pressed"], + :host([pressed]) slot[name="not-pressed"] { + display: none; + } + :host slot[name="not-pressed"], + :host([pressed]) slot[name="pressed"] { + display: contents; + } + `; + } + + constructor() { + super(); + this.pressed = false; + } + + firstUpdated(changedProperties) { + super.firstUpdated(changedProperties); + if (this._focusOnFirstRender) { + this._focusOnFirstRender = false; + this.focus(); + } + } + + render() { + return html` + + + `; + } + + updated(changedProperties) { + super.updated(changedProperties); + + if (changedProperties.get('pressed') === undefined) return; + + /** Dispatched when the pressed state changes */ + this.dispatchEvent(new CustomEvent('d2l-button-toggle-change')); + } + + focus() { + if (!this.hasUpdated) { + this._focusOnFirstRender = true; + return; + } + + const elem = this.shadowRoot.querySelector(this.pressed ? 'slot[name="pressed"]' : 'slot[name="not-pressed"]').assignedNodes()[0]; + if (!elem) { + throw new Error('d2l-button-toggle: no button to focus'); + } + + elem.focus(); + } + + async _handleClick(pressed) { + this.pressed = pressed; + await this.updateComplete; + this.focus(); + } + + _handleNotPressedClick() { + this._handleClick(true); + } + + _handlePressedClick() { + this._handleClick(false); + } + +} + +customElements.define('d2l-button-toggle', ButtonToggle); diff --git a/components/button/demo/button-toggle.html b/components/button/demo/button-toggle.html new file mode 100644 index 00000000000..cef6079c494 --- /dev/null +++ b/components/button/demo/button-toggle.html @@ -0,0 +1,60 @@ + + + + + + + + + + + + +

Toggle Button (using d2l-button-icon)

+ + + + + +

Toggle Button (using d2l-button-subtle)

+ + + + + +

Toggle Button (disabled)

+ + + + + +
+ + + diff --git a/components/button/test/button-toggle.axe.js b/components/button/test/button-toggle.axe.js new file mode 100644 index 00000000000..52469e53d94 --- /dev/null +++ b/components/button/test/button-toggle.axe.js @@ -0,0 +1,26 @@ +import '../button-icon.js'; +import '../button-toggle.js'; +import { expect, fixture, html } from '@brightspace-ui/testing'; + +describe('d2l-button-toggle', () => { + + const normalFixture = html` + + + + + `; + + it('not pressed', async() => { + const el = await fixture(normalFixture); + await expect(el).to.be.accessible(); + }); + + it('pressed', async() => { + const el = await fixture(normalFixture); + el.pressed = true; + await el.updateComplete; + await expect(el).to.be.accessible(); + }); + +}); diff --git a/components/button/test/button-toggle.test.js b/components/button/test/button-toggle.test.js new file mode 100644 index 00000000000..9b3a12e6fbe --- /dev/null +++ b/components/button/test/button-toggle.test.js @@ -0,0 +1,66 @@ +import '../button-icon.js'; +import '../button-toggle.js'; +import { clickElem, expect, fixture, html, oneEvent, runConstructor } from '@brightspace-ui/testing'; + +describe('d2l-button-toggle', () => { + + describe('constructor', () => { + + it('should construct', () => { + runConstructor('d2l-button-toggle'); + }); + + }); + + describe('events', () => { + + it('dispatches d2l-button-toggle-change event not-pressed is clicked', async() => { + const el = await fixture(html` + + + + + `); + clickElem(el.querySelector('[slot="not-pressed"]')); + const e = await oneEvent(el, 'd2l-button-toggle-change'); + expect(e.target.pressed).to.equal(true); + }); + + it('dispatches d2l-button-toggle-change event pressed is clicked', async() => { + const el = await fixture(html` + + + + + `); + clickElem(el.querySelector('[slot="pressed"]')); + const e = await oneEvent(el, 'd2l-button-toggle-change'); + expect(e.target.pressed).to.equal(false); + }); + + it('does not dispatch d2l-button-toggle-change event initially', async() => { + let dispatched = false; + const el = document.createElement('d2l-button-toggle'); + el.addEventListener('d2l-button-toggle-change', () => dispatched = true); + document.body.appendChild(el); + await el.updateComplete; + expect(dispatched).to.equal(false); + }); + + it('does not dispatch d2l-button-toggle-change event if disabled buttons are clicked', async() => { + const el = await fixture(html` + + + + + `); + let dispatched = false; + el.addEventListener('d2l-button-toggle-change', () => dispatched = true); + await clickElem(el.querySelector('[slot="not-pressed"]')); + expect(el.pressed).to.equal(false); + expect(dispatched).to.be.false; + }); + + }); + +}); diff --git a/components/button/test/button-toggle.vdiff.js b/components/button/test/button-toggle.vdiff.js new file mode 100644 index 00000000000..77440e84cfd --- /dev/null +++ b/components/button/test/button-toggle.vdiff.js @@ -0,0 +1,39 @@ +import '../button-icon.js'; +import '../button-subtle.js'; +import '../button-toggle.js'; +import { clickElem, expect, fixture, focusElem, hoverElem, html, sendKeysElem } from '@brightspace-ui/testing'; + +describe('button-toggle', () => { + + [ + { category: 'button-icon', template: html`` }, + { category: 'button-icon-pressed', template: html`` }, + { category: 'button-subtle', template: html`` }, + { category: 'button-subtle-pressed', template: html`` }, + { category: 'button-subtle-disabled', template: html`` } + ].forEach(({ category, template }) => { + + const getActiveButton = elem => { + if (elem.pressed) return elem.querySelector('[slot="pressed"]'); + else return elem.querySelector('[slot="not-pressed"]'); + }; + + describe(category, () => { + [ + { name: 'normal' }, + { name: 'hover', action: hoverElem }, + { name: 'focus', action: focusElem }, + { name: 'click', action: elem => clickElem(getActiveButton(elem)) }, + { name: 'enter', action: elem => sendKeysElem(getActiveButton(elem), 'press', 'Enter') } + ].forEach(({ action, name }) => { + it(name, async() => { + const elem = await fixture(template); + if (action) await action(elem); + await expect(elem).to.be.golden(); + }); + }); + }); + + }); + +}); diff --git a/components/button/test/golden/button-toggle/chromium/button-icon-click.png b/components/button/test/golden/button-toggle/chromium/button-icon-click.png new file mode 100644 index 00000000000..8b30318e4af Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-icon-click.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-icon-enter.png b/components/button/test/golden/button-toggle/chromium/button-icon-enter.png new file mode 100644 index 00000000000..900624a3a5f Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-icon-enter.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-icon-focus.png b/components/button/test/golden/button-toggle/chromium/button-icon-focus.png new file mode 100644 index 00000000000..55935ec0ee5 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-icon-focus.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-icon-hover.png b/components/button/test/golden/button-toggle/chromium/button-icon-hover.png new file mode 100644 index 00000000000..0fd7e0e6e80 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-icon-hover.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-icon-normal.png b/components/button/test/golden/button-toggle/chromium/button-icon-normal.png new file mode 100644 index 00000000000..8dadaf06c63 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-icon-normal.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-icon-pressed-click.png b/components/button/test/golden/button-toggle/chromium/button-icon-pressed-click.png new file mode 100644 index 00000000000..0fd7e0e6e80 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-icon-pressed-click.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-icon-pressed-enter.png b/components/button/test/golden/button-toggle/chromium/button-icon-pressed-enter.png new file mode 100644 index 00000000000..55935ec0ee5 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-icon-pressed-enter.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-icon-pressed-focus.png b/components/button/test/golden/button-toggle/chromium/button-icon-pressed-focus.png new file mode 100644 index 00000000000..900624a3a5f Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-icon-pressed-focus.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-icon-pressed-hover.png b/components/button/test/golden/button-toggle/chromium/button-icon-pressed-hover.png new file mode 100644 index 00000000000..8b30318e4af Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-icon-pressed-hover.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-icon-pressed-normal.png b/components/button/test/golden/button-toggle/chromium/button-icon-pressed-normal.png new file mode 100644 index 00000000000..a3b17e3ccce Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-icon-pressed-normal.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-click.png b/components/button/test/golden/button-toggle/chromium/button-subtle-click.png new file mode 100644 index 00000000000..9ed9d43a5b4 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-click.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-click.png b/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-click.png new file mode 100644 index 00000000000..ad9db963b36 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-click.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-enter.png b/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-enter.png new file mode 100644 index 00000000000..ad9db963b36 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-enter.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-focus.png b/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-focus.png new file mode 100644 index 00000000000..ad9db963b36 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-focus.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-hover.png b/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-hover.png new file mode 100644 index 00000000000..ad9db963b36 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-hover.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-normal.png b/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-normal.png new file mode 100644 index 00000000000..ad9db963b36 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-disabled-normal.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-enter.png b/components/button/test/golden/button-toggle/chromium/button-subtle-enter.png new file mode 100644 index 00000000000..8031d04e32e Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-enter.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-focus.png b/components/button/test/golden/button-toggle/chromium/button-subtle-focus.png new file mode 100644 index 00000000000..51b80df3742 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-focus.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-hover.png b/components/button/test/golden/button-toggle/chromium/button-subtle-hover.png new file mode 100644 index 00000000000..71b5d82a107 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-hover.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-normal.png b/components/button/test/golden/button-toggle/chromium/button-subtle-normal.png new file mode 100644 index 00000000000..5de3937fc5c Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-normal.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-click.png b/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-click.png new file mode 100644 index 00000000000..71b5d82a107 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-click.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-enter.png b/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-enter.png new file mode 100644 index 00000000000..51b80df3742 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-enter.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-focus.png b/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-focus.png new file mode 100644 index 00000000000..8031d04e32e Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-focus.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-hover.png b/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-hover.png new file mode 100644 index 00000000000..9ed9d43a5b4 Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-hover.png differ diff --git a/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-normal.png b/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-normal.png new file mode 100644 index 00000000000..2c3dc25230b Binary files /dev/null and b/components/button/test/golden/button-toggle/chromium/button-subtle-pressed-normal.png differ diff --git a/index.html b/index.html index 5209d4f9f35..52b407f0453 100644 --- a/index.html +++ b/index.html @@ -39,6 +39,7 @@

Components

  • d2l-button-add
  • d2l-button-icon
  • d2l-button-subtle
  • +
  • d2l-button-toggle
  • d2l-floating-buttons
  • d2l-floating-buttons in tabs
  • d2l-floating-buttons in frame