Skip to content

Commit 88bfc5b

Browse files
authoredJan 30, 2025··
fix(menu): focus indicator styles fixed for S2 (#3530)

File tree

8 files changed

+99
-64
lines changed

8 files changed

+99
-64
lines changed
 

‎.changeset/funny-pants-perform.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@spectrum-css/menu": patch
3+
---
4+
5+
By adding additional system mappings for the menu item, we are able to support box-shadow styling for Spectrum 1 and Express, while updating to outline styles for Spectrum 2 (S2) foundations.
6+
7+
For S2 foundations, a margin is leveraged on the Menu item to leave space for the outline width and outline offset. This is reflected in the padding present for the menu items in the design specifications for S2.

‎components/menu/dist/metadata.json

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
{
22
"sourceFile": "index.css",
33
"selectors": [
4-
".is-selectableMultiple .spectrum-Menu-item",
5-
".is-selectableMultiple .spectrum-Menu-itemCheckbox",
64
".js-focus-within .spectrum-Menu .spectrum-Menu-item--collapsible.is-open[focus-within]",
75
".spectrum-Menu",
86
".spectrum-Menu .spectrum-Menu-backIcon",
@@ -130,9 +128,10 @@
130128
".spectrum-Menu-sectionHeading",
131129
".spectrum-Menu.is-selectable .spectrum-Menu-item",
132130
".spectrum-Menu.is-selectable .spectrum-Menu-item.is-selected",
131+
".spectrum-Menu.is-selectableMultiple .spectrum-Menu-item",
132+
".spectrum-Menu.is-selectableMultiple .spectrum-Menu-itemCheckbox",
133133
".spectrum-Menu.js-focus-within .spectrum-Menu-item--collapsible.is-open[focus-within]",
134134
".spectrum-Menu.spectrum-Menu--sizeL",
135-
".spectrum-Menu.spectrum-Menu--sizeM",
136135
".spectrum-Menu.spectrum-Menu--sizeS",
137136
".spectrum-Menu.spectrum-Menu--sizeXL",
138137
".spectrum-Menu:lang(ja)",
@@ -259,8 +258,13 @@
259258
"--spectrum-menu-item-description-font-size",
260259
"--spectrum-menu-item-description-line-height",
261260
"--spectrum-menu-item-description-line-height-cjk",
261+
"--spectrum-menu-item-focus-indicator-border-width",
262262
"--spectrum-menu-item-focus-indicator-color",
263+
"--spectrum-menu-item-focus-indicator-color-default",
263264
"--spectrum-menu-item-focus-indicator-direction-scalar",
265+
"--spectrum-menu-item-focus-indicator-offset",
266+
"--spectrum-menu-item-focus-indicator-outline-style",
267+
"--spectrum-menu-item-focus-indicator-shadow",
264268
"--spectrum-menu-item-focus-indicator-width",
265269
"--spectrum-menu-item-icon-height",
266270
"--spectrum-menu-item-icon-width",
@@ -289,6 +293,7 @@
289293
"--spectrum-menu-item-selectable-edge-to-text-not-selected-large",
290294
"--spectrum-menu-item-selectable-edge-to-text-not-selected-medium",
291295
"--spectrum-menu-item-selectable-edge-to-text-not-selected-small",
296+
"--spectrum-menu-item-spacing-multiplier",
292297
"--spectrum-menu-item-text-to-control",
293298
"--spectrum-menu-item-top-edge-to-text",
294299
"--spectrum-menu-item-top-to-action",
@@ -379,7 +384,11 @@
379384
"--system-menu-item-background-color-down",
380385
"--system-menu-item-background-color-hover",
381386
"--system-menu-item-background-color-key-focus",
382-
"--system-menu-item-corner-radius"
387+
"--system-menu-item-corner-radius",
388+
"--system-menu-item-focus-indicator-offset",
389+
"--system-menu-item-focus-indicator-outline-style",
390+
"--system-menu-item-focus-indicator-shadow",
391+
"--system-menu-item-spacing-multiplier"
383392
],
384393
"passthroughs": [
385394
"--mod-checkbox-text-to-control",

‎components/menu/index.css

+55-56
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,37 @@
138138
--spectrum-menu-checkmark-display-shown: block;
139139
--spectrum-menu-checkmark-display: var(--spectrum-menu-checkmark-display-shown);
140140

141+
--spectrum-menu-item-min-height: var(--spectrum-component-height-100);
142+
--spectrum-menu-item-icon-height: var(--spectrum-workflow-icon-size-100);
143+
--spectrum-menu-item-icon-width: var(--spectrum-workflow-icon-size-100);
144+
--spectrum-menu-item-label-font-size: var(--spectrum-font-size-100);
145+
--spectrum-menu-item-label-text-to-visual: var(--spectrum-text-to-visual-100);
146+
147+
--spectrum-menu-item-label-inline-edge-to-content: var(--spectrum-component-edge-to-text-100);
148+
--spectrum-menu-item-top-edge-to-text: var(--spectrum-component-top-to-text-100);
149+
--spectrum-menu-item-bottom-edge-to-text: var(--spectrum-component-bottom-to-text-100);
150+
151+
--spectrum-menu-item-text-to-control: var(--spectrum-text-to-control-100);
152+
153+
--spectrum-menu-item-description-font-size: var(--spectrum-font-size-75);
154+
155+
--spectrum-menu-section-header-font-size: var(--spectrum-font-size-100);
156+
--spectrum-menu-section-header-min-width: var(--spectrum-component-height-100);
157+
158+
--spectrum-menu-item-selectable-edge-to-text-not-selected: var(--spectrum-menu-item-selectable-edge-to-text-not-selected-medium);
159+
160+
--spectrum-menu-item-checkmark-height: var(--spectrum-menu-item-checkmark-height-medium);
161+
--spectrum-menu-item-checkmark-width: var(--spectrum-menu-item-checkmark-width-medium);
162+
--spectrum-menu-item-top-to-checkmark: var(--spectrum-menu-item-top-to-selected-icon-medium);
163+
164+
--spectrum-menu-back-icon-margin: var(--spectrum-navigational-indicator-top-to-back-icon-medium);
165+
141166
/* "no" icon: just the chevron (we're not counting it because it HAS to be there for a collapsible) */
142167
--spectrum-menu-item-collapsible-no-icon-submenu-item-padding-x-start: calc((var(--spectrum-menu-item-label-inline-edge-to-content) + var(--spectrum-menu-item-checkmark-width) + var(--spectrum-menu-item-label-text-to-visual) + var(--spectrum-menu-item-focus-indicator-width)));
143168

169+
--spectrum-menu-item-focus-indicator-color-default: var(--highcontrast-menu-item-focus-indicator-color, var(--mod-menu-item-focus-indicator-color, var(--spectrum-menu-item-focus-indicator-color)));
170+
--spectrum-menu-item-focus-indicator-border-width: calc(var(--mod-menu-item-focus-indicator-width, var(--spectrum-menu-item-focus-indicator-width)) * var(--spectrum-menu-item-focus-indicator-direction-scalar, 1));
171+
144172
&.spectrum-Menu--sizeS {
145173
--spectrum-menu-item-min-height: var(--spectrum-component-height-75);
146174
--spectrum-menu-item-icon-height: var(--spectrum-workflow-icon-size-75);
@@ -168,34 +196,6 @@
168196
--spectrum-menu-back-icon-margin: var(--spectrum-navigational-indicator-top-to-back-icon-small);
169197
}
170198

171-
&,
172-
&.spectrum-Menu--sizeM {
173-
--spectrum-menu-item-min-height: var(--spectrum-component-height-100);
174-
--spectrum-menu-item-icon-height: var(--spectrum-workflow-icon-size-100);
175-
--spectrum-menu-item-icon-width: var(--spectrum-workflow-icon-size-100);
176-
--spectrum-menu-item-label-font-size: var(--spectrum-font-size-100);
177-
--spectrum-menu-item-label-text-to-visual: var(--spectrum-text-to-visual-100);
178-
179-
--spectrum-menu-item-label-inline-edge-to-content: var(--spectrum-component-edge-to-text-100);
180-
--spectrum-menu-item-top-edge-to-text: var(--spectrum-component-top-to-text-100);
181-
--spectrum-menu-item-bottom-edge-to-text: var(--spectrum-component-bottom-to-text-100);
182-
183-
--spectrum-menu-item-text-to-control: var(--spectrum-text-to-control-100);
184-
185-
--spectrum-menu-item-description-font-size: var(--spectrum-font-size-75);
186-
187-
--spectrum-menu-section-header-font-size: var(--spectrum-font-size-100);
188-
--spectrum-menu-section-header-min-width: var(--spectrum-component-height-100);
189-
190-
--spectrum-menu-item-selectable-edge-to-text-not-selected: var(--spectrum-menu-item-selectable-edge-to-text-not-selected-medium);
191-
192-
--spectrum-menu-item-checkmark-height: var(--spectrum-menu-item-checkmark-height-medium);
193-
--spectrum-menu-item-checkmark-width: var(--spectrum-menu-item-checkmark-width-medium);
194-
--spectrum-menu-item-top-to-checkmark: var(--spectrum-menu-item-top-to-selected-icon-medium);
195-
196-
--spectrum-menu-back-icon-margin: var(--spectrum-navigational-indicator-top-to-back-icon-medium);
197-
}
198-
199199
&.spectrum-Menu--sizeL {
200200
--spectrum-menu-item-min-height: var(--spectrum-component-height-200);
201201
--spectrum-menu-item-icon-height: var(--spectrum-workflow-icon-size-200);
@@ -249,7 +249,9 @@
249249

250250
--spectrum-menu-back-icon-margin: var(--spectrum-navigational-indicator-top-to-back-icon-extra-large);
251251
}
252+
}
252253

254+
.spectrum-Menu {
253255
display: inline-block;
254256
inline-size: var(--mod-menu-inline-size, auto);
255257
box-sizing: border-box;
@@ -283,6 +285,16 @@
283285
}
284286
}
285287

288+
&.is-selectableMultiple {
289+
.spectrum-Menu-item {
290+
align-items: start;
291+
}
292+
293+
.spectrum-Menu-itemCheckbox {
294+
grid-area: checkmarkArea;
295+
}
296+
}
297+
286298
.spectrum-Menu-divider {
287299
--spectrum-menu-divider-thickness: var(--spectrum-divider-thickness-medium);
288300

@@ -384,7 +396,7 @@
384396
padding-block-end: var(--mod-menu-item-bottom-edge-to-text, var(--spectrum-menu-item-bottom-edge-to-text));
385397
padding-inline: var(--mod-menu-item-label-inline-edge-to-content, var(--spectrum-menu-item-label-inline-edge-to-content));
386398

387-
margin: 0;
399+
margin: calc((var(--spectrum-menu-item-focus-indicator-offset) + var(--spectrum-menu-item-focus-indicator-border-width)) * var(--spectrum-menu-item-spacing-multiplier));
388400
text-decoration: none;
389401

390402
display: grid;
@@ -590,17 +602,10 @@
590602

591603
.spectrum-Menu-item:focus-visible,
592604
.spectrum-Menu-back:focus-visible {
593-
box-shadow: inset calc(var(--mod-menu-item-focus-indicator-width, var(--spectrum-menu-item-focus-indicator-width)) * var(--spectrum-menu-item-focus-indicator-direction-scalar, 1)) 0 0 0 var(--highcontrast-menu-item-focus-indicator-color, var(--mod-menu-item-focus-indicator-color, var(--spectrum-menu-item-focus-indicator-color)));
594-
}
595-
596-
.is-selectableMultiple {
597-
.spectrum-Menu-item {
598-
align-items: start;
599-
}
600-
601-
.spectrum-Menu-itemCheckbox {
602-
grid-area: checkmarkArea;
603-
}
605+
box-shadow: var(--spectrum-menu-item-focus-indicator-shadow) var(--spectrum-menu-item-focus-indicator-border-width) 0 0 0 var(--spectrum-menu-item-focus-indicator-color-default);
606+
outline: var(--spectrum-menu-item-focus-indicator-border-width) var(--spectrum-menu-item-focus-indicator-outline-style) var(--spectrum-menu-item-focus-indicator-color-default);
607+
outline-offset: var(--spectrum-menu-item-focus-indicator-offset);
608+
border-radius: var(--spectrum-menu-item-corner-radius);
604609
}
605610

606611
.spectrum-Menu-itemSelection {
@@ -765,26 +770,20 @@
765770
}
766771
}
767772

768-
&:hover {
769-
.spectrum-Menu-chevron {
770-
fill: var(--highcontrast-menu-item-color-focus, var(--mod-menu-drillin-icon-color-hover, var(--spectrum-menu-drillin-icon-color-hover)));
771-
color: var(--highcontrast-menu-item-color-focus, var(--mod-menu-drillin-icon-color-hover, var(--spectrum-menu-drillin-icon-color-hover)));
772-
}
773+
&:hover .spectrum-Menu-chevron {
774+
fill: var(--highcontrast-menu-item-color-focus, var(--mod-menu-drillin-icon-color-hover, var(--spectrum-menu-drillin-icon-color-hover)));
775+
color: var(--highcontrast-menu-item-color-focus, var(--mod-menu-drillin-icon-color-hover, var(--spectrum-menu-drillin-icon-color-hover)));
773776
}
774777

775-
&:focus,
776-
&.is-focused {
777-
.spectrum-Menu-chevron {
778-
fill: var(--highcontrast-menu-item-color-focus, var(--mod-menu-drillin-icon-color-focus, var(--spectrum-menu-drillin-icon-color-focus)));
779-
color: var(--highcontrast-menu-item-color-focus, var(--mod-menu-drillin-icon-color-focus, var(--spectrum-menu-drillin-icon-color-focus)));
780-
}
778+
&:focus .spectrum-Menu-chevron,
779+
&.is-focused .spectrum-Menu-chevron {
780+
fill: var(--highcontrast-menu-item-color-focus, var(--mod-menu-drillin-icon-color-focus, var(--spectrum-menu-drillin-icon-color-focus)));
781+
color: var(--highcontrast-menu-item-color-focus, var(--mod-menu-drillin-icon-color-focus, var(--spectrum-menu-drillin-icon-color-focus)));
781782
}
782783

783-
&:active {
784-
.spectrum-Menu-chevron {
785-
fill: var(--highcontrast-menu-item-color-focus, var(--mod-menu-drillin-icon-color-down, var(--spectrum-menu-drillin-icon-color-down)));
786-
color: var(--highcontrast-menu-item-color-focus, var(--mod-menu-drillin-icon-color-down, var(--spectrum-menu-drillin-icon-color-down)));
787-
}
784+
&:active .spectrum-Menu-chevron {
785+
fill: var(--highcontrast-menu-item-color-focus, var(--mod-menu-drillin-icon-color-down, var(--spectrum-menu-drillin-icon-color-down)));
786+
color: var(--highcontrast-menu-item-color-focus, var(--mod-menu-drillin-icon-color-down, var(--spectrum-menu-drillin-icon-color-down)));
788787
}
789788
}
790789

‎components/menu/stories/menu.stories.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ Default.args = {
110110
{
111111
label: "Focused menu item",
112112
iconName: "FolderOpen",
113-
isFocused: true
113+
isFocused: true,
114+
isActive: true,
114115
},
115116
{
116117
label: "A menu item with a longer label that causes the text to wrap to the next line",
@@ -248,7 +249,7 @@ TraySubmenu.parameters = {
248249
docs: {
249250
story: {
250251
inline: false,
251-
height: "400px"
252+
height: "300px",
252253
}
253254
},
254255
viewport: {
@@ -497,6 +498,7 @@ export const PopoverSubmenu = SubmenuInPopover.bind({});
497498
PopoverSubmenu.storyName = "Submenu in popover";
498499
PopoverSubmenu.tags = ["!dev"];
499500
PopoverSubmenu.parameters = {
501+
layout: "padded",
500502
chromatic: { disableSnapshot: true },
501503
};
502504

‎components/menu/stories/menu.test.js

+5
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ export const MenuItemGroup = Variants({
220220
testHeading: "Focused",
221221
isFocused: true,
222222
},
223+
{
224+
testHeading: "Focused and active",
225+
isFocused: true,
226+
isActive: true,
227+
},
223228
{
224229
testHeading: "Disabled",
225230
isDisabled: true,

‎components/menu/stories/template.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -741,8 +741,7 @@ export const SubmenuInPopover = (context) => Popover({
741741
isOpen: true,
742742
position: "end-top",
743743
customStyles: {
744-
"--mod-popover-animation-distance": "-4px",
745-
top: "-105px",
744+
top: "-120px",
746745
"inline-size": "120px",
747746
},
748747
content: [

‎components/menu/themes/spectrum-two.css

+7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@
1616
--spectrum-menu-item-background-color-hover: rgba(var(--spectrum-gray-1000-rgb), var(--spectrum-transparent-black-200-opacity));
1717
--spectrum-menu-item-background-color-down: rgba(var(--spectrum-gray-1000-rgb), var(--spectrum-transparent-black-200-opacity));
1818
--spectrum-menu-item-background-color-key-focus: rgba(var(--spectrum-gray-1000-rgb), var(--spectrum-transparent-black-200-opacity));
19+
1920
--spectrum-menu-item-corner-radius: var(--spectrum-corner-radius-100);
21+
22+
/* Focus state styling */
23+
--spectrum-menu-item-focus-indicator-shadow: none;
24+
--spectrum-menu-item-focus-indicator-offset: var(--spectrum-spacing-50);
25+
--spectrum-menu-item-spacing-multiplier: 1;
26+
--spectrum-menu-item-focus-indicator-outline-style: solid;
2027
}
2128
}

‎components/menu/themes/spectrum.css

+7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@
2020
--spectrum-menu-item-background-color-hover: rgba(var(--spectrum-gray-900-rgb), var(--spectrum-transparent-black-200-opacity));
2121
--spectrum-menu-item-background-color-down: rgba(var(--spectrum-gray-900-rgb), var(--spectrum-transparent-black-200-opacity));
2222
--spectrum-menu-item-background-color-key-focus: rgba(var(--spectrum-gray-900-rgb), var(--spectrum-transparent-black-200-opacity));
23+
2324
--spectrum-menu-item-corner-radius: 0;
25+
26+
/* Focus state styling */
27+
--spectrum-menu-item-focus-indicator-shadow: inset;
28+
--spectrum-menu-item-focus-indicator-offset: 0;
29+
--spectrum-menu-item-spacing-multiplier: 0;
30+
--spectrum-menu-item-focus-indicator-outline-style: none;
2431
}
2532
}

0 commit comments

Comments
 (0)
Please sign in to comment.