tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 1b69823731aed5e7ff2d4d192653a1fbbe964723
parent 9c8a99ad31717c852f89bc6d4597ccaa33a5f8cf
Author: hannajones <hjones@mozilla.com>
Date:   Tue, 18 Nov 2025 20:03:02 +0000

Bug 1998446 - fix visual issues for scrollable moz-box-group with wrapped items r=desktop-theme-reviewers,jules

Differential Revision: https://phabricator.services.mozilla.com/D271508

Diffstat:
Mbrowser/components/storybook/component-status/components.json | 2+-
Mtoolkit/content/widgets/moz-box-common.css | 8+++++---
Mtoolkit/content/widgets/moz-box-group/moz-box-group.css | 19++++++++++++++-----
Mtoolkit/content/widgets/moz-box-group/moz-box-group.mjs | 5++++-
Mtoolkit/content/widgets/moz-box-group/moz-box-group.stories.mjs | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
5 files changed, 209 insertions(+), 42 deletions(-)

diff --git a/browser/components/storybook/component-status/components.json b/browser/components/storybook/component-status/components.json @@ -1,5 +1,5 @@ { - "generatedAt": "2025-11-12T19:18:35.085Z", + "generatedAt": "2025-11-18T18:36:31.823Z", "count": 29, "items": [ { diff --git a/toolkit/content/widgets/moz-box-common.css b/toolkit/content/widgets/moz-box-common.css @@ -13,8 +13,10 @@ --box-icon-size: var(--icon-size); --box-icon-fill: var(--icon-color); - border: var(--box-border); - border-block-end: var(--box-border-width-end, var(--box-border-width)) solid var(--box-border-color); + border-inline-start: var(--box-border-inline-start, var(--box-border)); + border-inline-end: var(--box-border-inline-end, var(--box-border)); + border-block-start: var(--box-border-block-start, var(--box-border)); + border-block-end: var(--box-border-block-end, var(--box-border)); border-start-start-radius: var(--box-border-radius-start, var(--box-border-radius)); border-start-end-radius: var(--box-border-radius-start, var(--box-border-radius)); border-end-start-radius: var(--box-border-radius-end, var(--box-border-radius)); @@ -90,7 +92,7 @@ &:focus-visible { outline: var(--focus-outline); - outline-offset: var(--focus-outline-offset); + outline-offset: var(--focus-outline-inset); } &:hover { diff --git a/toolkit/content/widgets/moz-box-group/moz-box-group.css b/toolkit/content/widgets/moz-box-group/moz-box-group.css @@ -7,35 +7,44 @@ --box-group-border-radius-inner: calc(var(--border-radius-medium) - var(--border-width)); display: block; - border: var(--box-group-border); + outline: var(--box-group-border); border-radius: var(--border-radius-medium); overflow: hidden; } ::slotted(*) { - border: none; + --box-border-inline-end: none; + --box-border-inline-start: none; } ::slotted(*:not(.last)) { --box-border-radius-end: 0; - --box-border-width-end: 0; + --box-border-block-end: 0; } ::slotted(*:not(.first, [position="0"])) { --box-border-radius-start: 0; - border-block-start: var(--box-group-border); +} + +/* targets the first element when we have a header, since the element at +position 0 won't have the .first class */ +::slotted([position="0"]:not(.first)) { + --box-border-radius-start: 0; + --box-border-block-start: none; } ::slotted(.first) { --box-border-radius-start: var(--box-group-border-radius-inner); + --box-border-block-start: none; } ::slotted(.last) { --box-border-radius-end: var(--box-group-border-radius-inner); + --box-border-block-end: none; } slot[name="header"]::slotted(:first-child) { - border-block-end: var(--box-group-border); + --box-border-block-end: var(--box-group-border); } .list { diff --git a/toolkit/content/widgets/moz-box-group/moz-box-group.mjs b/toolkit/content/widgets/moz-box-group/moz-box-group.mjs @@ -73,6 +73,7 @@ export default class MozBoxGroup extends MozLitElement { let listTag = this.type == GROUP_TYPES.reorderable ? literal`ol` : literal`ul`; return staticHtml`<${listTag} + tabindex="-1" class="list scroll-container" aria-orientation="vertical" @keydown=${this.handleKeydown} @@ -87,7 +88,9 @@ export default class MozBoxGroup extends MozLitElement { </${listTag}> <slot hidden></slot>`; } - return html`<div class="scroll-container"><slot></slot></div>`; + return html`<div class="scroll-container"> + <slot></slot> + </div>`; } handleReorder(event) { diff --git a/toolkit/content/widgets/moz-box-group/moz-box-group.stories.mjs b/toolkit/content/widgets/moz-box-group/moz-box-group.stories.mjs @@ -4,6 +4,8 @@ import { html, ifDefined } from "../vendor/lit.all.mjs"; import { GROUP_TYPES } from "./moz-box-group.mjs"; +// eslint-disable-next-line mozilla/no-browser-refs-in-toolkit +import "chrome://browser/content/preferences/widgets/setting-control.mjs"; export default { title: "UI Widgets/Box Group", @@ -53,10 +55,34 @@ moz-box-button-footer = }, }; +function basicTemplate({ type, hasHeader, hasFooter, wrapped }) { + return html`<moz-box-group type=${ifDefined(type)}> + ${hasHeader + ? html`<moz-box-item + slot="header" + data-l10n-id="moz-box-item-header" + ></moz-box-item>` + : ""} + ${getInnerElements(type, wrapped)} + ${hasFooter + ? html`<moz-box-button + slot="footer" + data-l10n-id="moz-box-button-footer" + ></moz-box-button>` + : ""} + </moz-box-group> + ${type == "list" + ? html`<moz-button class="delete" @click=${appendItem}> + Add an item + </moz-button>` + : ""}`; +} + function getInnerElements(type) { if (type == GROUP_TYPES.reorderable) { return reorderableElements(); } + return basicElements(); } @@ -116,38 +142,6 @@ function basicElements() { <moz-box-button data-l10n-id="moz-box-button-2"></moz-box-button>`; } -const Template = ({ type, hasHeader, hasFooter, scrollable }) => html` - <style> - moz-box-group { - --box-group-max-height: ${scrollable ? "200px" : "unset"}; - } - - .delete { - margin-top: var(--space-medium); - } - </style> - <moz-box-group type=${ifDefined(type)}> - ${hasHeader - ? html`<moz-box-item - slot="header" - data-l10n-id="moz-box-item-header" - ></moz-box-item>` - : ""} - ${getInnerElements(type)} - ${hasFooter - ? html`<moz-box-button - slot="footer" - data-l10n-id="moz-box-button-footer" - ></moz-box-button>` - : ""} - </moz-box-group> - ${type == "list" - ? html`<moz-button class="delete" @click=${appendItem}> - Add an item - </moz-button>` - : ""} -`; - const appendItem = event => { let group = event.target.getRootNode().querySelector("moz-box-group"); @@ -165,12 +159,165 @@ const appendItem = event => { group.prepend(boxItem); }; +// Example with all child elements wrapped in setting-control/setting-group, +// which is the most common use case in Firefox preferences. +function wrappedTemplate({ type, hasHeader, hasFooter }) { + return html`<setting-control + .config=${getConfig({ type, hasHeader, hasFooter })} + .setting=${DEFAULT_SETTING} + .getSetting=${getSetting} + ></setting-control>`; +} + +const getConfig = ({ type, hasHeader, hasFooter }) => ({ + id: "exampleWrapped", + control: "moz-box-group", + controlAttrs: { + type, + }, + items: [ + ...(hasHeader + ? [ + { + id: "header", + control: "moz-box-item", + l10nId: "moz-box-item-header", + controlAttrs: { slot: "header " }, + }, + ] + : []), + { + id: "item1", + control: "moz-box-item", + l10nId: "moz-box-item", + options: [ + { + id: "slotted-button", + control: "moz-button", + l10nId: "moz-box-edit-action", + iconSrc: "chrome://global/skin/icons/edit-outline.svg", + controlAttrs: { + type: "ghost", + slot: "actions", + }, + }, + { + id: "slotted-toggle", + control: "moz-toggle", + l10nId: "moz-box-toggle-action", + controlAttrs: { + slot: "actions", + }, + }, + { + id: "slotted-icon-button", + control: "moz-button", + l10nId: "moz-box-more-action", + iconSrc: "chrome://global/skin/icons/more.svg", + controlAttrs: { + slot: "actions", + }, + }, + ], + }, + { + id: "link1", + control: "moz-box-link", + l10nId: "moz-box-link", + }, + { + id: "button1", + control: "moz-box-button", + l10nId: "moz-box-button-1", + }, + { + id: "item2", + control: "moz-box-item", + l10nId: "moz-box-item", + options: [ + { + id: "slotted-button-start", + control: "moz-button", + l10nId: "moz-box-edit-action", + iconSrc: "chrome://global/skin/icons/edit-outline.svg", + controlAttrs: { + type: "ghost", + slot: "actions-start", + }, + }, + { + id: "slotted-icon-button-start", + control: "moz-button", + l10nId: "moz-box-more-action", + iconSrc: "chrome://global/skin/icons/more.svg", + controlAttrs: { + slot: "actions-start", + }, + }, + ], + }, + { + id: "button2", + control: "moz-box-button", + l10nId: "moz-box-button-2", + }, + ...(hasFooter + ? [ + { + id: "footer", + control: "moz-box-button", + l10nId: "moz-box-button-footer", + controlAttrs: { slot: "footer " }, + }, + ] + : []), + ], +}); + +const DEFAULT_SETTING = { + value: 1, + on() {}, + off() {}, + userChange() {}, + getControlConfig: c => c, + controllingExtensionInfo: {}, + visible: true, +}; + +function getSetting() { + return { + value: true, + on() {}, + off() {}, + userChange() {}, + visible: () => true, + getControlConfig: c => c, + controllingExtensionInfo: {}, + }; +} + +const Template = ({ type, hasHeader, hasFooter, scrollable, wrapped }) => html` + <style> + moz-box-group { + --box-group-max-height: ${scrollable ? "250px" : "unset"}; + } + + .delete { + margin-top: var(--space-medium); + } + </style> + ${wrapped + ? wrappedTemplate({ type, hasHeader, hasFooter }) + : basicTemplate({ type, hasHeader, hasFooter, wrapped })} +`; + export const Default = Template.bind({}); Default.args = { type: "default", hasHeader: false, hasFooter: false, scrollable: false, + wrapped: false, }; export const List = Template.bind({}); @@ -197,3 +344,9 @@ Scrollable.args = { ...ListWithHeaderAndFooter.args, scrollable: true, }; + +export const Wrapped = Template.bind({}); +Wrapped.args = { + ...ListWithHeaderAndFooter.args, + wrapped: true, +};