Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7475b64
chore(menu): update to SHINE styles
dancormier Nov 12, 2025
a410479
Minor related popover style tweaks
dancormier Nov 12, 2025
4bb57d9
Add svelte menu component
dancormier Nov 12, 2025
923a512
Update a11y tests
dancormier Nov 12, 2025
e751adb
Update migration guide
dancormier Nov 12, 2025
87705b0
Update visual test (just the test file)
dancormier Nov 12, 2025
6f9b955
add changeset
dancormier Nov 12, 2025
a83f8cc
formatting
dancormier Nov 12, 2025
743314f
linting
dancormier Nov 12, 2025
bfc8a44
Update menu components outside of menu
dancormier Nov 12, 2025
359b739
Add Svelte and Figma links in frontmatter
dancormier Nov 13, 2025
b364354
Eating dogfood (popover in menu story)
dancormier Nov 13, 2025
356ebcc
Remove MenuItemControl
dancormier Nov 13, 2025
787d6f8
Rename MenuItemLink to MenuItem
dancormier Nov 13, 2025
970fa6b
Rename MenuItem input
dancormier Nov 13, 2025
51cdac2
Rename s-menu--link to s-menu--action
dancormier Nov 13, 2025
f4aa2d8
Improve selected, radio, checkbox styles
dancormier Nov 14, 2025
14a6c1b
Stick to `ul`, remove `iconTitle`
dancormier Nov 14, 2025
32e3619
Simplify tests; add i18nSelectedLabel; cleanup
dancormier Nov 14, 2025
7b44768
Update migration guide
dancormier Nov 14, 2025
b3097e3
add comment for selected checkmark
dancormier Nov 14, 2025
6ede6fb
Merge branch 'beta' into SPARK-71/menu
dancormier Nov 17, 2025
47225d2
Let's just put that comment inline
dancormier Nov 17, 2025
8c4c113
fix checkmark background image
dancormier Nov 18, 2025
f0eff98
proper indentation!
dancormier Nov 18, 2025
1c910f0
Improve custom checkmark styling
dancormier Nov 20, 2025
a1f1aca
Final cleanup
dancormier Nov 20, 2025
27e1da8
Merge branch 'beta' into SPARK-71/menu
dancormier Nov 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/legal-jobs-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@stackoverflow/stacks": minor
"@stackoverflow/stacks-svelte": minor
---

Update Menu to SHINE styles; add Svelte component
41 changes: 41 additions & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,47 @@
- The Award Bling component has been renamed to Bling.
- Bling no longer accepts children elements besides those for screen readers. Please include any visually represented strings (such as counts) as siblings to the bling component.

#### Menu

The menu component has been updated to use new class names and structure. The following changes are breaking:

- **Link classes**: Replace `.s-block-link` with `.s-menu--action` on all menu links and buttons.
- **Danger state**: Replace `.s-block-link__danger` with `.s-menu--action__danger`.
- **Selected state**: The selected state has changed from `.s-block-link__left.is-selected` to just `.is-selected` on the `.s-menu--action` element. The `.is-selected` class should be applied to the `<a>` or `<button>` element, not the `<li>`.
- **Menu item class**: All menu item `<li>` elements must now include the `.s-menu--item` class.
- **Menu label removed**: The `.s-menu--label` class has replaced with `.s-menu--title` for section titles instead.

**Before:**
```html
<ul class="s-menu" role="menu">
<li role="menuitem">
<a href="#" class="s-block-link">Example link</a>
</li>
<li role="menuitem">
<a href="#" class="s-block-link s-block-link__left is-selected">Selected link</a>
</li>
<li role="menuitem" class="s-menu--label">Example label</li>
<li role="menuitem">
<a href="#" class="s-block-link s-block-link__danger">Danger link</a>
</li>
</ul>
```

**After:**
```html
<ul class="s-menu" role="menu">
<li class="s-menu--item" role="menuitem">
<a href="#" class="s-menu--action">Example link</a>
</li>
<li class="s-menu--item" role="menuitem">
<a href="#" class="s-menu--action is-selected">Selected link</a>
</li>
<li class="s-menu--title" role="separator">Section Title</li>
<li class="s-menu--item" role="menuitem">
<a href="#" class="s-menu--action s-menu--action__danger">Danger link</a>
</li>
</ul>
```
#### Pagination
- The next and previous button now uses an `ArrowRight` and `ArrowLeft` icon instead of text. To apply the new styling, use the class `.s-pagination--item__nav`. Since these buttons use icons to represent their behavior, make sure to include descriptive text for screen readers.

Expand Down
23 changes: 11 additions & 12 deletions packages/stacks-classic/lib/components/menu/menu.a11y.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,22 @@ describe("menu", () => {
children: {
default: `
<li class="s-menu--title" role="separator">Title 1</li>
<li role="menuitem">
<a href="#" class="s-block-link">Example li</a>
<li class="s-menu--item" role="menuitem">
<a href="#" class="s-menu--action">Example link</a>
</li>
<li class="s-menu--title" role="separator">Title 2</li>
<li role="menuitem">
<a href="#" class="s-block-link s-block-link__left is-selected">Selected link</a>
<li class="s-menu--item" role="menuitem">
<button class="s-menu--action">Example button</button>
</li>
<li role="menuitem">
<a href="#" class="s-block-link">Example li</a>
<li class="s-menu--title" role="separator">Title 2</li>
<li class="s-menu--item" role="menuitem">
<a href="#" class="s-menu--action is-selected">Selected link</a>
</li>
<li role="menuitem" class="s-menu--label">Example label</li>
<li role="menuitem">
<a href="#" class="s-block-link">Block link</a>
<li class="s-menu--item" role="menuitem">
<a href="#" class="s-menu--action">Example link</a>
</li>
<li class="s-menu--divider" role="separator"></li>
<li role="menuitem">
<a href="" class="s-block-link s-block-link__danger">Danger link</a>
<li class="s-menu--item" role="menuitem">
<a href="#" class="s-menu--action s-menu--action__danger">Danger link</a>
</li>
`,
},
Expand Down
121 changes: 100 additions & 21 deletions packages/stacks-classic/lib/components/menu/menu.less
Original file line number Diff line number Diff line change
@@ -1,39 +1,118 @@
.s-menu {
--_me-divider-bg: var(--bc-light);
--_me-label-btc: var(--bc-light);
--_me-label-cursor: pointer;

// CONTEXTUAL STYLES
.dark-mode({
--_me-divider-bg: var(--bc-light);
--_me-label-btc: var(--bc-light);
});
--_me-action-bg: unset;
--_me-action-fc: var(--black-500);
--_me-item-p: var(--su8);
--_me-after-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m14 3.88-.44.44-7.34 7.35-.44.44-.44-.44-2.9-2.9L2 8.34l.89-.88.44.44 2.45 2.45 6.9-6.9.44-.44z'/%3E%3C/svg%3E"); // check16

// CHILD ELEMENTS
& &--divider {
background-color: var(--_me-divider-bg);
background-color: var(--black-200);
height: var(--su-static1);
margin: var(--su8) 0;
}

& &--label {
&.is-disabled {
--_me-label-cursor: not-allowed;
& &--icon {
color: inherit;
margin-right: var(--su8);
}

& &--item {
&:has(> .s-menu--action){
--_me-item-p: 0;
}

&.s-check-control {
--_me-item-p: var(--su6) var(--su8);

.s-radio,
.s-checkbox {
margin-top: var(--su4);
}

align-items: flex-start;
}

&:not(.s-check-control) {
.s-radio,
.s-checkbox {
height: 0;
pointer-events: none;
position: absolute;
opacity: 0;
width: 0;
}
}

color: var(--_me-action-fc);
padding: var(--_me-item-p);

align-items: center;
display: flex;
width: 100%;
}

& &--action {
&:focus-visible,
&:has(> input:focus-visible) {
.focus-styles(true, false);
}

&:focus-visible,
&:has(> input:focus-visible),
&:hover {
--_me-action-bg: var(--black-150);
--_me-action-fc: var(--black-600);
}

&__danger {
--_me-action-fc: var(--red-400);

&:focus-visible,
&:hover {
--_me-action-fc: var(--red-500);
}
}

border-top: var(--su-static1) solid var(--_me-label-btc);
cursor: var(--_me-label-cursor);
padding: var(--su12);
&.is-selected,
&:has(input:checked) {
&:after {
background-color: var(--_me-action-fc);
content: "";
height: var(--su16);
margin-left: auto;
width: var(--su16);
mask-image: var(--_me-after-mask);
mask-size: contain;
mask-repeat: no-repeat;
}

}

background-color: var(--_me-action-bg);
color: var(--_me-action-fc);

align-items: center;
border-radius: var(--br-md);
cursor: pointer;
display: flex;
padding: var(--su8);
width: 100%;
}

& button&--action { // Normalize for when applying to a button
border: none;
font-family: inherit;
line-height: inherit;
text-align: left;
}

& &--title {
color: var(--black-500);
font-size: var(--fs-fine);
padding: var(--su8) var(--su12);
text-transform: uppercase;
color: var(--black-600);
font-weight: 700;
padding: var(--su6) var(--su8);
}

& li + &--title {
& &--item + &--title {
margin-top: var(--su12);
}

Expand Down
23 changes: 11 additions & 12 deletions packages/stacks-classic/lib/components/menu/menu.visual.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,22 @@ describe("menu", () => {
children: {
default: `
<li class="s-menu--title" role="separator">Title 1</li>
<li role="menuitem">
<a href="#" class="s-block-link">Example li</a>
<li class="s-menu--item" role="menuitem">
<a href="#" class="s-menu--action">Example link</a>
</li>
<li class="s-menu--title" role="separator">Title 2</li>
<li role="menuitem">
<a href="#" class="s-block-link s-block-link__left is-selected">Selected link</a>
<li class="s-menu--item" role="menuitem">
<button class="s-menu--action">Example button</button>
</li>
<li role="menuitem">
<a href="#" class="s-block-link">Example li</a>
<li class="s-menu--title" role="separator">Title 2</li>
<li class="s-menu--item" role="menuitem">
<a href="#" class="s-menu--action is-selected">Selected link</a>
</li>
<li role="menuitem" class="s-menu--label">Example label</li>
<li role="menuitem">
<a href="#" class="s-block-link">Block link</a>
<li class="s-menu--item" role="menuitem">
<a href="#" class="s-menu--action">Example link</a>
</li>
<li class="s-menu--divider" role="separator"></li>
<li role="menuitem">
<a href="" class="s-block-link s-block-link__danger">Danger link</a>
<li class="s-menu--item" role="menuitem">
<a href="#" class="s-menu--action s-menu--action__danger">Danger link</a>
</li>
`,
},
Expand Down
6 changes: 2 additions & 4 deletions packages/stacks-classic/lib/components/popover/popover.less
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@

// CONTEXTUAL STYLES
.dark-mode({
--_po-bg: var(--black-200);
--_po-bc: var(--bc-light);
--_po-bs: var(--bs-lg);
--_po-bg: var(--black-100);
});

// MODIFIERS
Expand Down Expand Up @@ -44,7 +42,7 @@
}

background-color: var(--_po-bg);
border: 1px solid var(--_po-bc);
border: var(--su-static1) solid var(--_po-bc);
box-shadow: var(--_po-bs);
display: var(--_po-d);
min-width: var(--_po-wmn);
Expand Down
34 changes: 27 additions & 7 deletions packages/stacks-docs/_data/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,42 @@
{
"class": ".s-menu",
"applies": "N/A",
"description": "Base container styling for a menu"
"description": "Base container styling for a menu."
},
{
"class": ".s-menu--title",
"class": ".s-menu--divider",
"applies": ".s-menu",
"description": "Adds appropriate styling for a title within a menu"
"description": "Adds a divider line between menu sections."
},
{
"class": ".s-menu--divider",
"class": ".s-menu--item",
Comment thread
dancormier marked this conversation as resolved.
"applies": ".s-menu",
"description": "Styles a divider and adds the right amount of spacing"
"description": "Applies link styling to link within a menu. Used for actionable elements."
},
{
"class": ".s-menu--label",
"class": ".s-menu--title",
"applies": ".s-menu",
"description": "This gets applied to radio button labels within a menu for proper cursors and aesthetics"
"description": "Adds appropriate styling for a title within a menu."
},
{
"class": ".s-menu--icon",
"applies": ".s-menu--item",
"description": "Applies styling to an icon."
},
{
"class": ".s-menu--action",
"applies": ".s-menu--item",
"description": "Applies link styling to link within a menu. Used for actionable elements."
},
{
"class": ".s-menu--action__danger",
"applies": ".s-menu--action",
"description": "Applies danger styling to a menu link. Used for destructive actions."
},
{
"class": ".is-selected",
"applies": ".s-menu--action",
"description": "Applies a checkmark to indicate the selected state of a menu item."
}
]
}
3 changes: 2 additions & 1 deletion packages/stacks-docs/_data/site-navigation.json
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@
},
{
"title": "Menus",
"url": "/product/components/menus/"
"url": "/product/components/menus/",
"new": true
},
{
"title": "Modals",
Expand Down
2 changes: 1 addition & 1 deletion packages/stacks-docs/assets/js/entry.popovers.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ $(document).ready(function() {
load() {
if (this.loaded) { return; }
setTimeout(() => {
Stacks.attachPopover(this.element, `<div class="s-popover wmx1"><ul class="s-menu mxn12 myn8" role="menu"><li role="menuitem"><button class="s-block-link">Share</button></li><li role="menuitem"><button class="s-block-link">Edit</button></li><li role="menuitem"><button class="s-block-link">Flag</button></li><li class="s-menu--divider" role="separator"></li><li role="menuitem"><button class="s-block-link s-block-link__danger">Delete</button></li></ul></div>`, { autoShow: true, toggleOnClick: true, placement: "bottom" });
Stacks.attachPopover(this.element, `<div class="s-popover wmx1 p8"><ul class="s-menu" role="menu"><li class="s-menu--item" role="menuitem"><button class="s-menu--action">Share</button></li><li class="s-menu--item" role="menuitem"><button class="s-menu--action">Edit</button></li><li class="s-menu--item" role="menuitem"><button class="s-menu--action">Flag</button></li><li class="s-menu--divider mxn8" role="separator"></li><li class="s-menu--item" role="menuitem"><button class="s-menu--action s-menu--action__danger">Delete</button></li></ul></div>`, { autoShow: true, toggleOnClick: true, placement: "bottom" });
this.loaded = true;
});
},
Expand Down
Loading