diff options
Diffstat (limited to 'toolkit/content/widgets/moz-toggle')
-rw-r--r-- | toolkit/content/widgets/moz-toggle/moz-toggle.css | 119 | ||||
-rw-r--r-- | toolkit/content/widgets/moz-toggle/moz-toggle.mjs | 89 | ||||
-rw-r--r-- | toolkit/content/widgets/moz-toggle/moz-toggle.stories.mjs | 54 |
3 files changed, 262 insertions, 0 deletions
diff --git a/toolkit/content/widgets/moz-toggle/moz-toggle.css b/toolkit/content/widgets/moz-toggle/moz-toggle.css new file mode 100644 index 0000000000..af5d9085ae --- /dev/null +++ b/toolkit/content/widgets/moz-toggle/moz-toggle.css @@ -0,0 +1,119 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +:host { + display: grid; + gap: 8px; + justify-content: space-between; + align-items: center; +} + +:host([disabled]) { + opacity: 0.4 +} + +button, +label { + grid-row: 1; +} + +p { + grid-row: 2; + font-size: .85em; + line-height: 1.25; + color: var(--in-content-deemphasized-text); + margin: 0; + grid-column: 1 / -1; +} + +.toggle-button { + --button-height: 16px; + --button-half-height: 8px; + --button-width: 32px; + --button-border-width: 1px; + /* dot-size = button-height - 2*dot-margin - 2*button-border-width */ + --dot-size: 12px; + --dot-margin: 1px; + /* --dot-transform-x = button-width - 2*dot-margin - dot-size - 2*button-border-width */ + --dot-transform-x: 16px; + --border-color: #8F8F9D; +} + +.toggle-button { + appearance: none; + padding: 0; + margin: 0; + border: var(--button-border-width) solid var(--border-color); + height: var(--button-height); + width: var(--button-width); + border-radius: var(--button-half-height); + background-color: var(--in-content-button-background); + box-sizing: border-box; +} + +.toggle-button:focus-visible { + outline: var(--in-content-focus-outline-width) solid var(--in-content-focus-outline-color); + outline-offset: var(--in-content-focus-outline-offset); +} + +.toggle-button:enabled:hover { + background-color: var(--in-content-button-background-hover); + border-color: var(--border-color); +} + +.toggle-button:enabled:active { + background-color: var(--in-content-button-background-active); + border-color: var(--border-color); +} + +.toggle-button[aria-pressed="true"] { + background-color: var(--in-content-primary-button-background); + border-color: transparent; +} + +.toggle-button[aria-pressed="true"]:enabled:hover { + background-color: var(--in-content-primary-button-background-hover); + border-color: transparent; +} + +.toggle-button[aria-pressed="true"]:enabled:active, +.toggle-button[aria-pressed="true"].toggle-button:checked:enabled:hover:active { + background-color: var(--in-content-primary-button-background-active); + border-color: transparent; +} + +.toggle-button::before { + display: block; + content: ""; + background-color: var(--border-color); + height: var(--dot-size); + width: var(--dot-size); + margin: var(--dot-margin); + border-radius: 50%; + outline: 1px solid transparent; + outline-offset: -1px; + translate: 0; +} + +.toggle-button[aria-pressed="true"]::before { + translate: var(--dot-transform-x); + /* TODO: Bug 1798404 - This color doesn't match the spec in dark mode. This should + be re-visited when we're defining tokens. */ + background-color: var(--in-content-box-background); +} + +.toggle-button[aria-pressed="true"]:-moz-locale-dir(rtl)::before, +.toggle-button[aria-pressed="true"]:dir(rtl)::before { + translate: calc(-1 * var(--dot-transform-x)); +} + +.toggle-button[aria-pressed="true"]:not(:active, :hover:active)::before { + outline-color: var(--in-content-box-background); +} + +@media (prefers-reduced-motion: no-preference) { + .toggle-button::before { + transition: translate 100ms; + } +} diff --git a/toolkit/content/widgets/moz-toggle/moz-toggle.mjs b/toolkit/content/widgets/moz-toggle/moz-toggle.mjs new file mode 100644 index 0000000000..5aee2a7d42 --- /dev/null +++ b/toolkit/content/widgets/moz-toggle/moz-toggle.mjs @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at htp://mozilla.org/MPL/2.0/. */ + +import { html, ifDefined } from "../vendor/lit.all.mjs"; +import { MozLitElement } from "../lit-utils.mjs"; + +export default class MozToggle extends MozLitElement { + static properties = { + pressed: { type: Boolean, reflect: true }, + disabled: { type: Boolean, reflect: true }, + label: { type: String }, + description: { type: String }, + ariaLabel: { type: String, attribute: "aria-label" }, + }; + + static get queries() { + return { + buttonEl: "#moz-toggle-button", + labelEl: "#moz-toggle-label", + descriptionEl: "#moz-toggle-description", + }; + } + + // Use a relative URL in storybook to get faster reloads on style changes. + static stylesheetUrl = window.IS_STORYBOOK + ? "./moz-toggle/moz-toggle.css" + : "chrome://global/content/elements/moz-toggle.css"; + + constructor() { + super(); + this.pressed = false; + this.disabled = false; + } + + handleClick() { + this.pressed = !this.pressed; + this.dispatchOnUpdateComplete( + new CustomEvent("toggle", { + bubbles: true, + composed: true, + }) + ); + } + + labelTemplate() { + if (this.label) { + return html` + <label id="moz-toggle-label" part="label" for="moz-toggle-button"> + ${this.label} + </label> + `; + } + return ""; + } + + descriptionTemplate() { + if (this.description) { + return html` + <p id="moz-toggle-description" part="description"> + ${this.description} + </p> + `; + } + return ""; + } + + render() { + const { pressed, disabled, description, ariaLabel, handleClick } = this; + return html` + <link rel="stylesheet" href=${this.constructor.stylesheetUrl} /> + ${this.labelTemplate()} ${this.descriptionTemplate()} + <button + id="moz-toggle-button" + part="button" + type="button" + class="toggle-button" + ?disabled=${disabled} + aria-pressed=${pressed} + aria-label=${ifDefined(ariaLabel ?? undefined)} + aria-describedby=${ifDefined( + description ? "moz-toggle-description" : undefined + )} + @click=${handleClick} + ></button> + `; + } +} +customElements.define("moz-toggle", MozToggle); diff --git a/toolkit/content/widgets/moz-toggle/moz-toggle.stories.mjs b/toolkit/content/widgets/moz-toggle/moz-toggle.stories.mjs new file mode 100644 index 0000000000..7441cad91b --- /dev/null +++ b/toolkit/content/widgets/moz-toggle/moz-toggle.stories.mjs @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { html, ifDefined } from "../vendor/lit.all.mjs"; +// eslint-disable-next-line import/no-unassigned-import +import "./moz-toggle.mjs"; + +export default { + title: "Design System/Experiments/MozToggle", + parameters: { + actions: { + handles: ["toggle"], + }, + }, +}; + +const Template = ({ pressed, disabled, label, description, ariaLabel }) => html` + <div style="max-width: 400px"> + <moz-toggle + ?pressed=${pressed} + ?disabled=${disabled} + label=${ifDefined(label)} + description=${ifDefined(description)} + aria-label=${ifDefined(ariaLabel)} + ></moz-toggle> + </div> +`; + +export const Default = Template.bind({}); +Default.args = { + pressed: true, + disabled: false, + ariaLabel: "This is the aria-label", +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + ...Default.args, + disabled: true, +}; + +export const WithLabel = Template.bind({}); +WithLabel.args = { + pressed: true, + disabled: false, + label: "This is the label", +}; + +export const WithDescription = Template.bind({}); +WithDescription.args = { + ...WithLabel.args, + description: "This is the description", +}; |