Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
80dabd1
remove muted variant
giamir Nov 12, 2025
d48130c
switch to underscore for selection
giamir Nov 13, 2025
9dc2155
Merge branch 'beta' into SPARK-105/navigation
giamir Nov 13, 2025
092efc4
vertical navigation should not have underline
giamir Nov 13, 2025
99f905e
adjust small variant
giamir Nov 13, 2025
0e4f857
Merge branch 'beta' into SPARK-105/navigation
giamir Nov 17, 2025
dfe3544
adjust padding for vertical items with no icon
giamir Nov 17, 2025
8c1873b
add support for icons
giamir Nov 17, 2025
1b6f15e
style title and adjust highcontrast mode
giamir Nov 17, 2025
fd572d9
add activity and avatar examples
giamir Nov 17, 2025
37ac670
update migration guide
giamir Nov 17, 2025
c3e4104
Merge branch 'beta' into SPARK-105/navigation
giamir Nov 17, 2025
cd8614e
Merge branch 'beta' into SPARK-105/navigation
giamir Nov 18, 2025
fab994f
adding a button in one of the examples
giamir Nov 18, 2025
600b0dd
add badge example
giamir Nov 18, 2025
3cbbdef
adjust a11y and visual tests
giamir Nov 18, 2025
3aaf689
adjust padding for vertical navigation
giamir Nov 18, 2025
70f81d8
first pass at svelte component creation (missing stories and tests)
giamir Nov 18, 2025
6627600
fix linting issues
giamir Nov 18, 2025
741c49e
Remove dupe text from docs page
dancormier Nov 18, 2025
c0e90ec
Merge branch 'beta' into SPARK-105/navigation
giamir Nov 19, 2025
05685a2
update dropdown to new chevron
giamir Nov 19, 2025
c255657
ensure no layout shift for hc mode selected
giamir Nov 19, 2025
0fc6e60
Merge branch 'beta' into SPARK-105/navigation
giamir Nov 19, 2025
b780c69
popover reference should support references with children of role button
giamir Nov 19, 2025
79f89db
write navigation stories
giamir Nov 19, 2025
4cb976d
Merge branch 'beta' into SPARK-105/navigation
giamir Nov 19, 2025
e8a3691
fix linting
giamir Nov 19, 2025
a0c9edd
add unit tests
giamir Nov 20, 2025
e231b70
add stories and unit tests for avatar variant
giamir Nov 20, 2025
898a7ca
add border to title separators
giamir Nov 20, 2025
0626902
add animation for chevron
giamir Nov 20, 2025
726a491
fix avatar unit tests
giamir Nov 20, 2025
ce6a5ac
update baseline images
giamir Nov 20, 2025
29efe57
Merge branch 'beta' into SPARK-105/navigation
giamir Nov 21, 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
5 changes: 5 additions & 0 deletions .changeset/plain-teams-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@stackoverflow/stacks-svelte": minor
---

Added new Navigation Component
6 changes: 6 additions & 0 deletions .changeset/seven-comics-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@stackoverflow/stacks": minor
---

Navigation updates:
- Deprecated muted variant (`.s-navigation__muted`)
6 changes: 6 additions & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ The menu component has been updated to use new class names and structure. The fo
- `.s-tag__xs` removed
- `.s-tag__md` removed

#### Navigation
- Removed muted variant (`.s-navigation__muted`)
- Selected page are now highlighted by an underscore (no pill shape background)
- New `.s-navigation--icon` class to style icons alongside navigation items
- New `.s-navigation--avatar` class to style avatars alongside navigation items

# Migrating from Stacks Svelte v0 to v1

## Breaking changes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { html } from "@open-wc/testing";
import { runA11yTests } from "../../test/a11y-test-utils";
import { WCAGNonTextContrast } from "../../test/assertions";
import "../../index";

const items = [
Expand Down Expand Up @@ -57,7 +56,7 @@ const getChildren = (includeTitles = false): string =>
describe("navigation", () => {
runA11yTests({
baseClass: "s-navigation",
variants: ["vertical", "muted"],
variants: ["vertical"],
modifiers: {
primary: ["scroll", "sm"],
},
Expand All @@ -74,8 +73,5 @@ describe("navigation", () => {
${component}
</nav>
`,
additionalAssertions: [WCAGNonTextContrast],
// TODO: fix non-text-contrast SC for s-navigation__muted
skippedTestids: [/s-navigation-(light|dark)-muted/],
});
});
96 changes: 60 additions & 36 deletions packages/stacks-classic/lib/components/navigation/navigation.less
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@
--_na-p: var(--su2) 0;
--_na-gap: var(--su4);
--_na-item-bg: none;
--_na-item-fc: var(--black-500);
--_na-item-fc: var(--black-400);
--_na-item-fs: unset;
--_na-item-p: var(--su6) var(--su12);
--_na-item-py: var(--su12);
--_na-item-p: calc(var(--su12) - var(--su1)) var(--su16);
--_na-item-ws: nowrap;
--_na-item-bg-hover: var(--black-200);
--_na-item-bg-hover: var(--black-150);
--_na-item-fc-hover: var(--_na-item-fc);
--_na-item-selected-bg: var(--theme-primary);
--_na-item-selected-fc: var(--white);
--_na-item-selected-bg-hover: var(--theme-primary-500);
--_na-item-selected-bg: none;
--_na-item-selected-fc: var(--black-600);
--_na-item-selected-bg-hover: var(--_na-item-bg-hover);
--_na-item-selected-h: var(--su-static2);
--_na-item-text-ta: center;
--_na-title-mt: var(--su16);
--_na-after-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M11.35 4.35 6 9.71.65 4.35l.7-.7L6 8.29l4.65-4.64z'/%3E%3C/svg%3E");
--_na-after-bg-color: var(--black-400);

// CONTEXTUAL STYLES
.highcontrast-mode({
Expand All @@ -33,7 +35,7 @@

&&__sm {
--_na-item-fs: var(--fs-caption);
--_na-item-p: var(--su4) var(--su12);
--_na-item-p: var(--su6) var(--su4);
}

&&__vertical {
Expand All @@ -42,19 +44,20 @@
--_na-p: 0;
--_na-item-text-ta: unset;
--_na-item-ws: normal;
}
--_na-item-selected-h: 0;
--_na-item-p: var(--su6) var(--su8);
& .s-navigation--item {
&.is-selected {
--_na-item-bg: var(--black-150);
}

// VARIANTS
&&__muted {
--_na-item-selected-bg: var(--black-150);
--_na-item-selected-fc: var(--black-600);
--_na-item-selected-bg-hover: var(--_na-item-bg);

.highcontrast-mode({
--_na-item-selected-bg: var(--black-600);
--_na-item-selected-fc: var(--black-150);
--_na-item-selected-bg-hover: var(--black-600);
});
&:has(.s-navigation--icon) {
--_na-item-p: calc(var(--su12) - var(--su1)) var(--su8);
}
&:has(.s-navigation--avatar) {
--_na-item-p: var(--su12) var(--su8);
}
}
}

// CHILD ELEMENTS
Expand All @@ -65,8 +68,22 @@
--_na-item-fc-hover: var(--_na-item-fc);
--_na-item-bg-hover: var(--_na-item-selected-bg-hover);

&:before {
.highcontrast-mode({
height: 0;
});
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: var(--_na-item-selected-h);
background-color: var(--_na-item-selected-fc);
}

.highcontrast-mode({
text-decoration: none;
--_na-item-fc-hover: var(--white);
box-shadow: inset 0 0 0 var(--su-static1) var(--black-500);
});

font-weight: bold;
Expand All @@ -75,22 +92,22 @@
// TODO: include child component class (without variant) on selector
&__dropdown {
&:after {
border-style: solid;
border-width: var(--su-static4) var(--su-static4) 0 var(--su-static4);
border-color: currentColor transparent;
mask-image: var(--_na-after-mask);
mask-size: contain;
mask-repeat: no-repeat;
background-color: var(--_na-after-bg-color);
content: "";
pointer-events: none;
position: absolute;
right: 0.9em;
top: calc(50% - var(--su-static2)); // 50% - 2px
z-index: var(--zi-active);
height: var(--su12);
width: var(--su12);
margin-left: var(--su8);
}

padding-right: 2em;
}

&:hover,
&:active {
.highcontrast-mode({
--_na-after-bg-color: var(--white);
});
background-color: var(--_na-item-bg-hover);
color: var(--_na-item-fc-hover);
}
Expand All @@ -108,7 +125,7 @@

align-items: center;
border: none; // Reset some things for when the navigation item is also a button
border-radius: 1000px;
border-radius: var(--br-md);
box-shadow: none;
cursor: pointer;
display: flex;
Expand Down Expand Up @@ -137,11 +154,18 @@
}

margin-top: var(--_na-title-mt);

font-size: var(--fs-fine);
font-weight: bold;
padding: var(--su6) var(--su12);
text-transform: uppercase;
color: var(--black-400);
padding: var(--su16) var(--su8);
}

& &--icon {
color: inherit;
margin-right: var(--su4);
}

& &--avatar {
margin-right: var(--su8);
}

flex-direction: var(--_na-fd);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { html } from "@open-wc/testing";
import { runVisualTests } from "../../test/visual-test-utils";
import { IconHome, IconHomeFill } from "@stackoverflow/stacks-icons/icons";
import "../../index";

const outlineIcon = IconHome.replace('class="', 'class="s-navigation--icon ');
const filledIcon = IconHomeFill.replace(
'class="',
'class="s-navigation--icon '
);

const items = [
{
label: "Group 1",
Expand Down Expand Up @@ -38,31 +45,36 @@ const items = [
},
];

const getChildren = (includeTitles = false): string =>
const getChildren = (includeTitles = false, includeIcons = false): string =>
items
.map((item) => {
if (item.title) {
return includeTitles
? `<li class="s-navigation--title">${item.label}</li>`
: "";
}
const icon = includeIcons
? item.selected
? filledIcon
: outlineIcon
: "";
const classes = `s-navigation--item${
item.selected ? " is-selected" : ""
}${item.dropdown ? " s-navigation--item__dropdown" : ""}`;
return `<li><a href="#" class="${classes}">${item.label}</a></li>`;
return `<li><a href="#" class="${classes}">${icon}${item.label}</a></li>`;
})
.join("");

describe("navigation", () => {
runVisualTests({
baseClass: "s-navigation",
variants: ["muted"],
modifiers: {
primary: ["scroll", "sm"],
},
tag: "ul",
children: {
default: getChildren(),
icon: getChildren(false, true),
},
template: ({ component, testid }) => html`
<nav
Expand All @@ -73,14 +85,21 @@ describe("navigation", () => {
${component}
</nav>
`,
excludedTestids: [
/^s-navigation-(?=.*sm).*icon$/, // s-navigation with icon and sm modifier not supported
],
});

runVisualTests({
baseClass: "s-navigation",
variants: ["vertical"],
modifiers: {
primary: ["sm"],
},
tag: "ul",
children: {
default: getChildren(true),
icon: getChildren(true, true),
},
template: ({ component, testid }) => html`
<nav
Expand All @@ -91,6 +110,9 @@ describe("navigation", () => {
${component}
</nav>
`,
excludedTestids: [
/^s-navigation-(?=.*sm).*icon$/, // s-navigation with icon and sm modifier not supported
],
options: {
includeNullVariant: false,
},
Expand Down
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Loading