summaryrefslogtreecommitdiffstats
path: root/toolkit/content/widgets/moz-toggle
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/content/widgets/moz-toggle')
-rw-r--r--toolkit/content/widgets/moz-toggle/moz-toggle.css119
-rw-r--r--toolkit/content/widgets/moz-toggle/moz-toggle.mjs89
-rw-r--r--toolkit/content/widgets/moz-toggle/moz-toggle.stories.mjs54
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",
+};