diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
commit | 40a355a42d4a9444dc753c04c6608dade2f06a23 (patch) | |
tree | 871fc667d2de662f171103ce5ec067014ef85e61 /toolkit/content/widgets/moz-page-nav | |
parent | Adding upstream version 124.0.1. (diff) | |
download | firefox-40a355a42d4a9444dc753c04c6608dade2f06a23.tar.xz firefox-40a355a42d4a9444dc753c04c6608dade2f06a23.zip |
Adding upstream version 125.0.1.upstream/125.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/content/widgets/moz-page-nav')
4 files changed, 446 insertions, 0 deletions
diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css b/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css new file mode 100644 index 0000000000..2975bb1a7c --- /dev/null +++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css @@ -0,0 +1,123 @@ +/* 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 { + border-radius: var(--border-radius-small); + &:focus-visible { + outline-offset: var(--page-nav-focus-outline-inset); + } +} + +button { + background-color: var(--page-nav-button-background-color); + border: 1px solid var(--page-nav-border-color); + -moz-context-properties: fill, fill-opacity; + fill: currentColor; + display: grid; + grid-template-columns: min-content 1fr; + gap: 12px; + align-items: center; + font-size: inherit; + width: 100%; + font-weight: normal; + border-radius: var(--page-nav-button-border-radius); + color: var(--page-nav-button-text-color); + text-align: start; + transition: background-color 150ms; + padding: var(--page-nav-button-padding); +} + +button:hover { + cursor: pointer; +} + +@media not (prefers-contrast) { + button { + border-inline-start: 2px solid transparent; + border-inline-end: none; + border-block: none; + } + + button:hover, + button[selected]:hover { + background-color: var(--page-nav-button-background-color-hover); + } + + button[selected]:hover { + border-inline-start-color: inherit; + } + + button[selected], + button[selected]:hover { + border-inline-start: 2px solid; + } + + button[selected]:not(:focus-visible) { + border-start-start-radius: 0; + border-end-start-radius: 0; + } + + button[selected]:not(:hover) { + color: var(--color-accent-primary); + background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 5%, transparent); + border-inline-start-color: var(--color-accent-primary); + } +} + +@media (prefers-color-scheme: dark) { + button[selected] { + background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 12%, transparent); + } +} + +button:focus-visible, +button[selected]:focus-visible { + outline: var(--focus-outline); + outline-offset: var(--focus-outline-offset); + border-radius: var(--border-radius-small); +} + +.page-nav-icon { + height: 20px; + width: 20px; + -moz-context-properties: fill; + fill: currentColor; +} + +@media (prefers-contrast) { + button { + transition: none; + border-color: ButtonText; + background-color: var(--button-background-color); + } + + button:hover { + color: SelectedItem; + } + + button[selected] { + color: SelectedItemText; + background-color: SelectedItem; + border-color: SelectedItem; + } +} + +slot { + font-size: var(--font-size-large); + margin: 0; + padding-inline-start: 0; + user-select: none; +} + +@media (max-width: 52rem) { + button { + grid-template-columns: min-content; + justify-content: center; + margin-inline: 0; + } + + slot { + display: none; + } +} diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.css b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css new file mode 100644 index 0000000000..49000f622d --- /dev/null +++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css @@ -0,0 +1,76 @@ +/* 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 { + --page-nav-button-border-radius: var(--button-border-radius); + --page-nav-button-text-color: var(--button-text-color); + --page-nav-button-background-color: transparent; + --page-nav-button-background-color-hover: var(--button-background-color-hover); + --page-nav-button-background-color-selected: var(--color-accent-primary); + --page-nav-button-padding: var(--space-small); + --page-nav-margin-top: 72px; + --page-nav-margin-bottom: 36px; + --page-nav-gap: 25px; + --page-nav-button-gap: var(--space-xsmall); + --page-nav-border-color: var(--border-color); + --page-nav-focus-outline-inset: var(--focus-outline-inset); + --page-nav-width: 240px; + margin-inline-start: 42px; + position: sticky; + top: 0; + height: 100vh; + width: var(--page-nav-width); + + @media (prefers-reduced-motion) { + /* (See Bug 1610081) Setting border-inline-end to add clear differentiation between side navigation and main content area */ + border-inline-end: 1px solid var(--page-nav-border-color); + } + + @media (max-width: 52rem) { + grid-template-rows: 1fr auto; + --page-nav-width: 118px; + } +} + +nav { + display: grid; + grid-template-rows: min-content 1fr auto; + gap: var(--page-nav-gap); + margin-block: var(--page-nav-margin-top) var(--page-nav-margin-bottom); + height: calc(100vh - var(--page-nav-margin-top) - var(--page-nav-margin-bottom)); + @media (max-width: 52rem) { + grid-template-rows: 1fr auto; + } +} + +.page-nav-header { + /* Align the header text/icon with the page nav button icons */ + margin-inline-start: var(--page-nav-button-padding); + font-size: var(--font-size-xlarge); + font-weight: var(--font-weight-bold); + margin-block: 0; + + @media (max-width: 52rem) { + display: none; + } +} + +.primary-nav-group, +#secondary-nav-group { + display: grid; + grid-template-columns: 1fr; + grid-auto-rows: min-content; + gap: var(--page-nav-button-gap); + + @media (max-width: 52rem) { + justify-content: center; + grid-template-columns: min-content; + } +} + +@media (prefers-contrast) { + .primary-nav-group { + gap: var(--space-small); + } +} diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs b/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs new file mode 100644 index 0000000000..f998ee735f --- /dev/null +++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs @@ -0,0 +1,170 @@ +/* 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 } from "chrome://global/content/vendor/lit.all.mjs"; +import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; + +/** + * A grouping of navigation buttons that is displayed at the page level, + * intended to change the selected view, provide a heading, and have links + * to external resources. + * + * @tagname moz-page-nav + * @property {string} currentView - The currently selected view. + * @property {string} heading - A heading to be displayed at the top of the navigation. + * @slot [default] - Used to append moz-page-nav-button elements to the navigation. + */ +export default class MozPageNav extends MozLitElement { + static properties = { + currentView: { type: String }, + heading: { type: String }, + }; + + static queries = { + headingEl: "#page-nav-header", + primaryNavGroupSlot: ".primary-nav-group slot", + secondaryNavGroupSlot: "#secondary-nav-group slot", + }; + + get pageNavButtons() { + return this.primaryNavGroupSlot + .assignedNodes() + .filter( + node => node?.localName === "moz-page-nav-button" && !node.hidden + ); + } + + onChangeView(e) { + this.currentView = e.target.view; + } + + handleFocus(e) { + if (e.key == "ArrowDown" || e.key == "ArrowRight") { + e.preventDefault(); + this.focusNextView(); + } else if (e.key == "ArrowUp" || e.key == "ArrowLeft") { + e.preventDefault(); + this.focusPreviousView(); + } + } + + focusPreviousView() { + let pageNavButtons = this.pageNavButtons; + let currentIndex = pageNavButtons.findIndex(b => b.selected); + let prev = pageNavButtons[currentIndex - 1]; + if (prev) { + prev.activate(); + prev.buttonEl.focus(); + } + } + + focusNextView() { + let pageNavButtons = this.pageNavButtons; + let currentIndex = pageNavButtons.findIndex(b => b.selected); + let next = pageNavButtons[currentIndex + 1]; + if (next) { + next.activate(); + next.buttonEl.focus(); + } + } + + render() { + return html` + <link + rel="stylesheet" + href="chrome://global/content/elements/moz-page-nav.css" + /> + <nav> + <h1 class="page-nav-header" id="page-nav-header">${this.heading}</h1> + <div + class="primary-nav-group" + role="tablist" + aria-orientation="vertical" + aria-labelledby="page-nav-header" + > + <slot + @change-view=${this.onChangeView} + @keydown=${this.handleFocus} + ></slot> + </div> + <div id="secondary-nav-group" role="group"> + <slot name="secondary-nav" @keydown=${this.handleFocus}></slot> + </div> + </nav> + `; + } + + updated() { + let isViewSelected = false; + let assignedPageNavButtons = this.pageNavButtons; + for (let button of assignedPageNavButtons) { + button.selected = button.view == this.currentView; + isViewSelected = isViewSelected || button.selected; + } + if (!isViewSelected && assignedPageNavButtons.length) { + // Current page nav has no matching view, reset to the first view. + assignedPageNavButtons[0].activate(); + } + } +} +customElements.define("moz-page-nav", MozPageNav); + +/** + * A navigation button intended to change the selected view within a page. + * + * @tagname moz-page-nav-button + * @property {string} iconSrc - The chrome:// url for the icon used for the button. + * @property {string} l10nId - The fluent ID for the button's text + * @property {boolean} selected - Whether or not the button is currently selected. + * @slot [default] - Used to append the l10n string to the button. + */ +export class MozPageNavButton extends MozLitElement { + static properties = { + iconSrc: { type: String }, + l10nId: { type: String }, + selected: { type: Boolean }, + }; + + connectedCallback() { + super.connectedCallback(); + this.setAttribute("role", "none"); + } + + static queries = { + buttonEl: "button", + }; + + get view() { + return this.getAttribute("view"); + } + + activate() { + this.dispatchEvent( + new CustomEvent("change-view", { + bubbles: true, + composed: true, + }) + ); + } + + render() { + return html` + <link + rel="stylesheet" + href="chrome://global/content/elements/moz-page-nav-button.css" + /> + <button + aria-selected=${this.selected} + tabindex=${this.selected ? 0 : -1} + role="tab" + ?selected=${this.selected} + @click=${this.activate} + > + <img class="page-nav-icon" src=${this.iconSrc} /> + <slot></slot> + </button> + `; + } +} +customElements.define("moz-page-nav-button", MozPageNavButton); diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs b/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs new file mode 100644 index 0000000000..4ac7b455cf --- /dev/null +++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs @@ -0,0 +1,77 @@ +/* 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 } from "../vendor/lit.all.mjs"; +// eslint-disable-next-line import/no-unassigned-import +import "./moz-page-nav.mjs"; + +export default { + title: "UI Widgets/Page Nav", + component: "moz-page-nav", + parameters: { + status: "in-development", + actions: { + handles: ["change-view"], + }, + fluent: ` +moz-page-nav-button-one = View 1 + .title = View 1 +moz-page-nav-button-two = View 2 + .title = View 2 +moz-page-nav-button-three = View 3 + .title = View 3 +moz-page-link-one = Support Page + .title = Support Page +moz-page-nav-heading = + .heading = Heading + `, + }, +}; + +const Template = () => html` + <style> + #page { + height: 100%; + display: flex; + + @media (max-width: 52rem) { + grid-template-columns: 82px 1fr; + } + } + moz-page-nav { + margin-inline-start: 10px; + --page-nav-margin-top: 10px; + + @media (max-width: 52rem) { + margin-inline-start: 0; + } + } + </style> + <div id="page"> + <moz-page-nav data-l10n-id="moz-page-nav-heading" data-l10n-attrs="heading"> + <moz-page-nav-button + view="view-one" + data-l10n-id="moz-page-nav-button-one" + iconSrc="chrome://browser/skin/preferences/category-general.svg" + > + </moz-page-nav-button> + <moz-page-nav-button + view="view-two" + data-l10n-id="moz-page-nav-button-two" + iconSrc="chrome://browser/skin/preferences/category-general.svg" + > + </moz-page-nav-button> + <moz-page-nav-button + view="view-three" + data-l10n-id="moz-page-nav-button-three" + iconSrc="chrome://browser/skin/preferences/category-general.svg" + > + </moz-page-nav-button> + </moz-page-nav> + <main></main> + </div> +`; + +export const Default = Template.bind({}); +Default.args = {}; |