-
Notifications
You must be signed in to change notification settings - Fork 207
feat(action-menu): S2 migration [CSS-1160] #4085
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
--- | ||
"@spectrum-css/actionmenu": major | ||
"@spectrum-css/actionbutton": minor | ||
"@spectrum-css/menu": patch | ||
"@spectrum-css/actiongroup": patch | ||
--- | ||
|
||
### Action menu component (now with custom styles!) | ||
|
||
Introduces `@spectrum-css/actionmenu`, a composition of `ActionButton`, `Popover`, and `Menu` to present action lists from a trigger. Now with custom styles! | ||
|
||
- Adds wrapper classes: `spectrum-ActionMenu`, `spectrum-ActionMenu-trigger`, `spectrum-ActionMenu-popover`, and `spectrum-ActionMenu-menu`. | ||
- Supports long press triggers and four placements (start/end, top/bottom) via the underlying popover API. | ||
- Design reference: [Figma S2 token specs](https://www.figma.com/design/eoZHKJH9a3LJkHYCGt60Vb/S2-token-specs?node-id=20959-21513&node-type=frame&t=jbePQKK1yLdrHG2M-11). | ||
|
||
#### Migration notes | ||
|
||
- If you previously composed an action menu manually (action button + popover + menu), you can adopt the new wrapper classes without changing the underlying markup semantics. Ensure the trigger has `aria-haspopup="menu"` and manages `aria-expanded` according to your application logic. | ||
- For spacing customizations previously done with ad‑hoc margins, switch to the new `--spectrum-actionmenu-button-to-menu-gap` custom property. | ||
|
||
Example markup: | ||
|
||
```html | ||
<div class="spectrum-ActionMenu"> | ||
<button | ||
class="spectrum-ActionMenu-trigger spectrum-ActionButton" | ||
aria-haspopup="menu" | ||
aria-expanded="false" | ||
> | ||
<!-- icon/label --> | ||
</button> | ||
<div class="spectrum-ActionMenu-popover spectrum-Popover"> | ||
<ul class="spectrum-ActionMenu-menu spectrum-Menu"> | ||
<!-- menu items --> | ||
</ul> | ||
</div> | ||
<!-- popover positioning/visibility is owned by your implementation --> | ||
<!-- use long-press behavior when appropriate to your UX --> | ||
<!-- use Popover placement options: bottom-start, bottom-end, start-top, end-top --> | ||
</div> | ||
``` | ||
|
||
### Menu refinements | ||
|
||
Updates `@spectrum-css/menu` styles to align with latest Spectrum 2 design specifications and improve accessibility. | ||
|
||
- Updated `.is-selectableMultiple .spectrum-Menu-itemCheckbox` to `.is-selectableMultiple:not(:has(.is-selectable)) .spectrum-Menu-itemCheckbox` to prevent clash with the `.is-selectable` placement. | ||
- Non-breaking; no class or DOM changes required. | ||
|
||
### Action button refinements | ||
|
||
- Selection styling now applies when components use ARIA pressed/expanded semantics, not just `.is-selected`. | ||
- Implemented with `:where()` to keep selector specificity low and prevent downstream specificity battles. | ||
- Non-breaking; no class changes required. | ||
|
||
### Action group refinements | ||
|
||
Aligns selection behavior of grouped items with action button updates. | ||
|
||
- Adds `:where([aria-pressed="true"], [aria-expanded="true"])` alongside `.is-selected` on items to cover more accessibility use-cases while keeping specificity low. | ||
- Non-breaking; no class changes required. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,7 +63,7 @@ governing permissions and limitations under the License. | |
--spectrum-actionbutton-background-color-focus: var(--spectrum-gray-200); | ||
--spectrum-actionbutton-background-color-disabled: transparent; | ||
|
||
&.is-selected { | ||
&:is(.is-selected, [aria-pressed="true"], [aria-expanded="true"]) { | ||
--spectrum-actionbutton-background-color-disabled: var(--spectrum-disabled-background-color); | ||
} | ||
} | ||
|
@@ -116,7 +116,8 @@ governing permissions and limitations under the License. | |
} | ||
} | ||
|
||
&.is-selected { | ||
/* expanded is specific to action menu when the menu is open */ | ||
&:is(.is-selected, [aria-pressed="true"], [aria-expanded="true"]) { | ||
--mod-actionbutton-background-color-default: var(--mod-actionbutton-background-color-default-selected, var(--spectrum-neutral-background-color-selected-default)); | ||
castastrophe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
--mod-actionbutton-background-color-hover: var(--mod-actionbutton-background-color-hover-selected, var(--spectrum-neutral-background-color-selected-hover)); | ||
--mod-actionbutton-background-color-down: var(--mod-actionbutton-background-color-down-selected, var(--spectrum-neutral-background-color-selected-down)); | ||
|
@@ -298,6 +299,16 @@ governing permissions and limitations under the License. | |
border-style: none; | ||
} | ||
|
||
&::after { | ||
position: absolute; | ||
inset: 0; | ||
margin: calc((var(--spectrum-actionbutton-focus-indicator-gap) + var(--spectrum-actionbutton-border-width)) * -1); | ||
border-radius: var(--spectrum-actionbutton-focus-indicator-border-radius); | ||
transition: box-shadow var(--highcontrast-actionbutton-animation-duration, var(--spectrum-actionbutton-animation-duration)) ease-in-out; | ||
pointer-events: none; | ||
content: ""; | ||
} | ||
|
||
&:focus { | ||
outline: none; | ||
} | ||
|
@@ -315,6 +326,13 @@ governing permissions and limitations under the License. | |
&:focus-visible { | ||
background-color: var(--highcontrast-actionbutton-background-color-default, var(--mod-actionbutton-background-color-focus, var(--spectrum-actionbutton-background-color-focus))); | ||
color: var(--highcontrast-actionbutton-content-color-default, var(--mod-actionbutton-content-color-focus, var(--spectrum-actionbutton-content-color-focus))); | ||
|
||
box-shadow: none; | ||
outline: none; | ||
|
||
&::after { | ||
box-shadow: 0 0 0 var(--spectrum-actionbutton-focus-indicator-thickness) var(--highcontrast-actionbutton-focus-indicator-color, var(--spectrum-actionbutton-focus-indicator-color)); | ||
} | ||
} | ||
|
||
&:active { | ||
|
@@ -323,8 +341,8 @@ governing permissions and limitations under the License. | |
transform: perspective(var(--spectrum-actionbutton-downstate-perspective)) translateZ(var(--spectrum-component-size-difference-down)); | ||
} | ||
|
||
&:disabled, | ||
&.is-disabled { | ||
/* ideal when we want to disable the button but still allow its content to be focused */ | ||
&:is(:disabled, .is-disabled, [aria-disabled="true"]) { | ||
background-color: var(--highcontrast-actionbutton-background-color-disabled, var(--mod-actionbutton-background-color-disabled, var(--spectrum-actionbutton-background-color-disabled))); | ||
color: var(--highcontrast-actionbutton-content-color-disabled, var(--mod-actionbutton-content-color-disabled, var(--spectrum-actionbutton-content-color-disabled))); | ||
} | ||
|
@@ -364,10 +382,6 @@ a.spectrum-ActionButton { | |
/* Fixes horizontal alignment of text in anchor buttons */ | ||
text-align: center; | ||
|
||
&:empty { | ||
display: none; | ||
} | ||
|
||
pointer-events: none; | ||
|
||
font-size: var(--mod-actionbutton-font-size, var(--spectrum-actionbutton-font-size)); | ||
|
@@ -378,40 +392,21 @@ a.spectrum-ActionButton { | |
|
||
text-overflow: ellipsis; | ||
overflow: hidden; | ||
|
||
&:empty { | ||
display: none; | ||
} | ||
} | ||
|
||
.spectrum-ActionButton-hold { | ||
position: absolute; | ||
inset-inline-end: calc(var(--mod-actionbutton-edge-to-hold-icon, var(--spectrum-actionbutton-edge-to-hold-icon)) - var(--spectrum-actionbutton-border-width)); | ||
inset-block-end: calc(var(--mod-actionbutton-edge-to-hold-icon, var(--spectrum-actionbutton-edge-to-hold-icon)) - var(--spectrum-actionbutton-border-width)); | ||
display: block; | ||
color: inherit; | ||
transform: var(--spectrum-logical-rotation); | ||
} | ||
|
||
/* Focus indicator */ | ||
.spectrum-ActionButton { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just me tidying up a bit by combining these styles in with the initial definition for .spectrum-ActionButton |
||
transition: border-color var(--highcontrast-actionbutton-animation-duration, var(--mod-actionbutton-animation-duration, var(--spectrum-actionbutton-animation-duration))) ease-in-out; | ||
|
||
&::after { | ||
position: absolute; | ||
inset: 0; | ||
margin: calc((var(--mod-actionbutton-focus-indicator-gap, var(--spectrum-actionbutton-focus-indicator-gap)) + var(--spectrum-actionbutton-border-width)) * -1); | ||
border-radius: var(--mod-actionbutton-focus-indicator-border-radius, var(--spectrum-actionbutton-focus-indicator-border-radius)); | ||
transition: box-shadow var(--highcontrast-actionbutton-animation-duration, var(--mod-actionbutton-animation-duration, var(--spectrum-actionbutton-animation-duration))) ease-in-out; | ||
pointer-events: none; | ||
content: ""; | ||
} | ||
|
||
&:focus-visible { | ||
box-shadow: none; | ||
outline: none; | ||
|
||
&::after { | ||
box-shadow: 0 0 0 var(--mod-actionbutton-focus-indicator-thickness, var(--spectrum-actionbutton-focus-indicator-thickness)) var(--highcontrast-actionbutton-focus-indicator-color, var(--mod-actionbutton-focus-indicator-color, var(--spectrum-actionbutton-focus-indicator-color))); | ||
} | ||
} | ||
} | ||
|
||
@media (forced-colors: active) { | ||
.spectrum-ActionButton { | ||
/** | ||
|
@@ -457,7 +452,7 @@ a.spectrum-ActionButton { | |
--highcontrast-actionbutton-background-color-disabled: Canvas; | ||
--highcontrast-actionbutton-content-color-default: CanvasText; | ||
|
||
&:disabled:not(.is-selected) { | ||
&:is(:disabled, .is-disabled, [aria-disabled="true"]):not(:where(.is-selected, [aria-pressed="true"], [aria-expanded="true"])) { | ||
--highcontrast-actionbutton-border-color: Canvas; | ||
} | ||
} | ||
|
@@ -469,8 +464,7 @@ a.spectrum-ActionButton { | |
--highcontrast-actionbutton-border-color: Highlight; | ||
} | ||
|
||
/* Selected always shows as a solid highlighted color. */ | ||
&.is-selected { | ||
&:is(.is-selected, [aria-pressed="true"], [aria-expanded="true"]) { | ||
--highcontrast-actionbutton-border-color: Highlight; | ||
--highcontrast-actionbutton-background-color-default: Highlight; | ||
--highcontrast-actionbutton-content-color-default: HighlightText; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,12 +17,14 @@ export const ActionButtons = (args, context) => { | |
${Template({ | ||
...args, | ||
hasPopup: "true", | ||
hasLongPress: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need an |
||
hideLabel: true, | ||
}, context)} | ||
${Template({ | ||
...args, | ||
iconName: undefined, | ||
hasPopup: "true", | ||
hasLongPress: true, | ||
}, context)} | ||
</div> | ||
`; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is for S2 and we've agreed to remove modifiers, I removed the mods on any lines I needed to update for this change anyway.