Skip to content

Commit 1827fcb

Browse files
committed
Bug 1791816 - moz-button component r=reusable-components-reviewers,desktop-theme-reviewers,hjones,jules,accessibility-frontend-reviewers,ayeddi,morgan,dao,emilio
Differential Revision: https://phabricator.services.mozilla.com/D188122
1 parent f4b209b commit 1827fcb

File tree

12 files changed

+662
-35
lines changed

12 files changed

+662
-35
lines changed

browser/components/storybook/stories/button.stories.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export default {
1414
},
1515
},
1616
parameters: {
17+
actions: {
18+
handles: ["click"],
19+
},
1720
status: "stable",
1821
fluent: `
1922
button-regular = Regular

toolkit/content/customElements.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,8 @@
818818
// like the previous Services.scriptloader.loadSubscript() function
819819
function importCustomElementFromESModule(name) {
820820
switch (name) {
821+
case "moz-button":
822+
return import("chrome://global/content/elements/moz-button.mjs");
821823
case "moz-button-group":
822824
return import(
823825
"chrome://global/content/elements/moz-button-group.mjs"

toolkit/content/jar.mn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ toolkit.jar:
9191
content/global/elements/message-bar.js (widgets/message-bar.js)
9292
content/global/elements/menu.js (widgets/menu.js)
9393
content/global/elements/menupopup.js (widgets/menupopup.js)
94+
content/global/elements/moz-button.css (widgets/moz-button/moz-button.css)
95+
content/global/elements/moz-button.mjs (widgets/moz-button/moz-button.mjs)
9496
content/global/elements/moz-button-group.css (widgets/moz-button-group/moz-button-group.css)
9597
content/global/elements/moz-button-group.mjs (widgets/moz-button-group/moz-button-group.mjs)
9698
content/global/elements/moz-card.css (widgets/moz-card/moz-card.css)

toolkit/content/tests/widgets/chrome.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ skip-if = ["os == 'linux'"] # Bug 1116215
2222
["test_menubar.xhtml"]
2323
skip-if = ["os == 'mac'"]
2424

25+
["test_moz_button.html"]
26+
2527
["test_moz_button_group.html"]
2628

2729
["test_moz_card.html"]
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<!DOCTYPE HTML>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>MozButton Tests</title>
6+
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
7+
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
8+
<script type="module" src="chrome://global/content/elements/moz-button.mjs"></script>
9+
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
10+
<link rel="stylesheet" href="chrome://global/skin/design-system/tokens-brand.css">
11+
<link rel="stylesheet" href="chrome://global/skin/design-system/text-and-typography.css">
12+
<style>
13+
.four::part(button),
14+
.five::part(button),
15+
.six::part(button) {
16+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16' fill='context-fill' fill-opacity='context-fill-opacity'%3E%3Cpath d='M3 7 1.5 7l-.5.5L1 9l.5.5 1.5 0 .5-.5 0-1.5z'/%3E%3Cpath d='m8.75 7-1.5 0-.5.5 0 1.5.5.5 1.5 0 .5-.5 0-1.5z'/%3E%3Cpath d='M14.5 7 13 7l-.5.5 0 1.5.5.5 1.5 0L15 9l0-1.5z'/%3E%3C/svg%3E");
17+
}
18+
</style>
19+
<script>
20+
function normalizeColor(val, computedStyles) {
21+
if (val.includes("currentColor")) {
22+
val = val.replaceAll("currentColor", computedStyles.color);
23+
}
24+
if (val.startsWith("light-dark")) {
25+
let [, light, dark] = val.match(/light-dark\(([^,]+),\s*([^)]+)\)/);
26+
if (light && dark) {
27+
val = window.matchMedia("(prefers-color-scheme: dark)").matches ? dark : light;
28+
}
29+
}
30+
try {
31+
let { r, g, b, a } = InspectorUtils.colorToRGBA(val);
32+
return `rgba(${r}, ${g}, ${b}, ${a})`;
33+
} catch (e) {
34+
info(val);
35+
throw e;
36+
}
37+
}
38+
39+
function assertButtonPropertiesMatch(el, propertyToCssVar) {
40+
let elStyles = getComputedStyle(el.buttonEl);
41+
for (let [property, cssVar] of Object.entries(propertyToCssVar)) {
42+
let propertyVal = elStyles[property];
43+
let cssVarVal = cssVar.startsWith("--") ? elStyles.getPropertyValue(cssVar) : cssVar;
44+
if (propertyVal.startsWith("rgb") || propertyVal.startsWith("#") || propertyVal.startsWith("color")) {
45+
propertyVal = normalizeColor(propertyVal, elStyles);
46+
cssVarVal = normalizeColor(cssVarVal, elStyles);
47+
}
48+
info(`${propertyVal} == ${cssVarVal}`);
49+
is(propertyVal, cssVarVal, `${property} should be ${cssVar}`);
50+
}
51+
}
52+
53+
add_task(async function testButtonTypes() {
54+
let [...buttons] = document.querySelectorAll("moz-button");
55+
let [one, two, three, four, five, six] = buttons;
56+
57+
await Promise.all(buttons.map(btn => btn.updateComplete));
58+
59+
is(one.textContent, "Test button", "Text is set");
60+
is(two.buttonEl.textContent.trim(), "Test button", "Text is set");
61+
is(three.textContent, "Test button", "Text is set");
62+
63+
assertButtonPropertiesMatch(one, {
64+
backgroundColor: "--button-background-color",
65+
color: "--button-text-color",
66+
height: "--button-min-height",
67+
});
68+
assertButtonPropertiesMatch(two, {
69+
backgroundColor: "--button-background-color",
70+
color: "--button-text-color",
71+
height: "--button-min-height",
72+
});
73+
assertButtonPropertiesMatch(three, {
74+
backgroundColor: "--button-background-color-primary",
75+
color: "--button-text-color-primary",
76+
height: "--button-min-height",
77+
});
78+
79+
assertButtonPropertiesMatch(four, {
80+
width: "--button-size-icon",
81+
height: "--button-size-icon",
82+
backgroundColor: "--button-background-color",
83+
fill: "--button-text-color",
84+
});
85+
assertButtonPropertiesMatch(five, {
86+
width: "--button-size-icon",
87+
height: "--button-size-icon",
88+
backgroundColor: "transparent",
89+
fill: "--button-text-color",
90+
});
91+
assertButtonPropertiesMatch(six, {
92+
width: "--button-size-icon",
93+
height: "--button-size-icon",
94+
backgroundColor: "transparent",
95+
fill: "--button-text-color",
96+
});
97+
98+
buttons.forEach(btn => (btn.size = "small"));
99+
100+
await Promise.all(buttons.map(btn => btn.updateComplete));
101+
102+
assertButtonPropertiesMatch(one, {
103+
height: "--button-min-height-small",
104+
});
105+
assertButtonPropertiesMatch(two, {
106+
height: "--button-min-height-small",
107+
});
108+
assertButtonPropertiesMatch(three, {
109+
height: "--button-min-height-small",
110+
});
111+
assertButtonPropertiesMatch(four, {
112+
width: "--button-size-icon-small",
113+
height: "--button-size-icon-small",
114+
});
115+
assertButtonPropertiesMatch(five, {
116+
width: "--button-size-icon-small",
117+
height: "--button-size-icon-small",
118+
});
119+
assertButtonPropertiesMatch(six, {
120+
width: "--button-size-icon-small",
121+
height: "--button-size-icon-small",
122+
});
123+
});
124+
125+
add_task(async function testA11yAttributes() {
126+
let button = document.querySelector("moz-button");
127+
128+
async function testProperty(propName, jsPropName = propName) {
129+
let propValue = `${propName} value`;
130+
ok(!button.buttonEl.hasAttribute(propName), `No ${propName} on inner button`);
131+
button.setAttribute(propName, propValue);
132+
133+
await button.updateComplete;
134+
135+
ok(!button.hasAttribute(propName), `moz-button ${propName} cleared`);
136+
is(button.buttonEl.getAttribute(propName), propValue, `${propName} added to inner button`);
137+
138+
button[jsPropName] = null;
139+
await button.updateComplete;
140+
141+
ok(!button.buttonEl.hasAttribute(propName), `${propName} cleared by setting property`);
142+
}
143+
144+
await testProperty("title");
145+
await testProperty("aria-label", "ariaLabel");
146+
});
147+
148+
</script>
149+
</head>
150+
<body>
151+
<moz-button class="one">Test button</moz-button>
152+
<moz-button class="two" label="Test button"></moz-button>
153+
<moz-button class="three" type="primary">Test button</moz-button>
154+
<moz-button class="four" type="icon"></moz-button>
155+
<moz-button class="five" type="icon ghost"></moz-button>
156+
<moz-button class="six" type="ghost icon"></moz-button>
157+
</body>
158+
</html>
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
:host {
6+
display: inline-block;
7+
}
8+
9+
button {
10+
appearance: none;
11+
min-height: var(--button-min-height);
12+
color: var(--button-text-color);
13+
border: var(--button-border);
14+
border-radius: var(--button-border-radius);
15+
background-color: var(--button-background-color);
16+
padding: var(--button-padding);
17+
/* HTML button gets `font: -moz-button` from UA styles,
18+
* but we want it to match the root font styling. */
19+
font: inherit;
20+
font-weight: var(--button-font-weight);
21+
/* Ensure font-size isn't overridden by widget styling (e.g. in forms.css) */
22+
font-size: var(--button-font-size);
23+
width: 100%;
24+
25+
&[size=small] {
26+
min-height: var(--button-min-height-small);
27+
font-size: var(--button-font-size-small);
28+
}
29+
30+
&:hover {
31+
background-color: var(--button-background-color-hover);
32+
border-color: var(--button-border-color-hover);
33+
color: var(--button-text-color-hover);
34+
}
35+
36+
&:hover:active {
37+
background-color: var(--button-background-color-active);
38+
border-color: var(--button-border-color-active);
39+
color: var(--button-text-color-active);
40+
}
41+
42+
&:disabled {
43+
background-color: var(--button-background-color-disabled);
44+
border-color: var(--button-border-color-disabled);
45+
color: var(--button-text-color-disabled);
46+
opacity: var(--button-opacity-disabled);
47+
}
48+
49+
&:focus-visible {
50+
outline: var(--focus-outline);
51+
outline-offset: var(--focus-outline-offset);
52+
}
53+
54+
&[type="primary"] {
55+
background-color: var(--button-background-color-primary);
56+
border-color: var(--button-border-color-primary);
57+
color: var(--button-text-color-primary);
58+
59+
&:hover {
60+
background-color: var(--button-background-color-primary-hover);
61+
border-color: var(--button-border-color-primary-hover);
62+
color: var(--button-text-color-primary-hover);
63+
}
64+
65+
&:hover:active {
66+
background-color: var(--button-background-color-primary-active);
67+
border-color: var(--button-border-color-primary-active);
68+
color: var(--button-text-color-primary-active);
69+
}
70+
71+
&:disabled {
72+
background-color: var(--button-background-color-primary-disabled);
73+
border-color: var(--button-border-color-primary-disabled);
74+
color: var(--button-text-color-primary-disabled);
75+
}
76+
}
77+
78+
&[type="destructive"] {
79+
background-color: var(--button-background-color-destructive);
80+
border-color: var(--button-border-color-destructive);
81+
color: var(--button-text-color-destructive);
82+
83+
&:hover {
84+
background-color: var(--button-background-color-destructive-hover);
85+
border-color: var(--button-border-color-destructive-hover);
86+
color: var(--button-text-color-destructive-hover);
87+
}
88+
89+
&:hover:active {
90+
background-color: var(--button-background-color-destructive-active);
91+
border-color: var(--button-border-color-destructive-active);
92+
color: var(--button-text-color-destructive-active);
93+
}
94+
95+
&:disabled {
96+
background-color: var(--button-background-color-destructive-disabled);
97+
border-color: var(--button-border-color-destructive-disabled);
98+
color: var(--button-text-color-destructive-disabled);
99+
}
100+
}
101+
102+
&[type~=ghost] {
103+
background-color: var(--button-background-color-ghost);
104+
border-color: var(--button-border-color-ghost);
105+
color: var(--button-text-color-ghost);
106+
107+
&:hover {
108+
background-color: var(--button-background-color-ghost-hover);
109+
border-color: var(--button-border-color-ghost-hover);
110+
color: var(--button-text-color-ghost-hover);
111+
}
112+
113+
&:hover:active {
114+
background-color: var(--button-background-color-ghost-active);
115+
border-color: var(--button-border-color-ghost-active);
116+
color: var(--button-text-color-ghost-active);
117+
}
118+
119+
&:disabled {
120+
background-color: var(--button-background-color-ghost-disabled);
121+
border-color: var(--button-border-color-ghost-disabled);
122+
color: var(--button-text-color-ghost-disabled);
123+
}
124+
}
125+
126+
&[type~=icon] {
127+
background-size: var(--icon-size-default);
128+
background-position: center;
129+
background-repeat: no-repeat;
130+
-moz-context-properties: fill, stroke;
131+
fill: currentColor;
132+
stroke: currentColor;
133+
width: var(--button-size-icon);
134+
height: var(--button-size-icon);
135+
padding: var(--button-padding-icon);
136+
137+
&[size=small] {
138+
width: var(--button-size-icon-small);
139+
height: var(--button-size-icon-small);
140+
}
141+
}
142+
}

0 commit comments

Comments
 (0)