Skip to content
Open
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
885e96d
Add SubdomainNavBar gridline variant
danielguillan Jul 1, 2026
6ff3df3
Add SubdomainNavBar custom slots
danielguillan Jul 1, 2026
4ec09e6
Add SubdomainNavBar slot stories
danielguillan Jul 1, 2026
9856f7e
Update search button
danielguillan Jul 1, 2026
6d8f8c8
Add SubdomainNavBar input search trigger
danielguillan Jul 1, 2026
31f0b85
Update SubdomainNavBar search modal
danielguillan Jul 2, 2026
0f9ba23
Refactor SubdomainNavBar search dialog
danielguillan Jul 7, 2026
4e02e92
Add SubdomainNavBar search shortcut
danielguillan Jul 7, 2026
c33a41c
Improve SubdomainNavBar overflow handling
danielguillan Jul 8, 2026
5761d8c
Fix responsive SubdomainNavBar search
danielguillan Jul 14, 2026
0e4f984
Update gridline SubdomainNavBar mobile styles
danielguillan Jul 14, 2026
5a9c1c7
Refine responsive SubdomainNavBar layout
danielguillan Jul 14, 2026
2ac053b
Update changeset
danielguillan Jul 14, 2026
b14db09
Make slot-only mobile navigation accessible
danielguillan Jul 14, 2026
755014e
Responsive search update
danielguillan Jul 15, 2026
7891518
Merge origin/main into SubdomainNavBar refactor
danielguillan Jul 15, 2026
c179c2f
Type id prop
danielguillan Jul 15, 2026
fba43cb
Update snapshots
danielguillan Jul 15, 2026
b60a21b
Update actions padding
danielguillan Jul 15, 2026
57760d6
Fix SubdomainNavBar review findings
danielguillan Jul 15, 2026
a24d43f
Update SubdomainNavBar mobile action snapshots
danielguillan Jul 15, 2026
4b99a2d
Make gridline the default style
danielguillan Jul 16, 2026
61d0eff
Update snapshots
danielguillan Jul 17, 2026
e077038
Merge branch 'main' into danielguillan/brand-refactor-update-subdomai…
danielguillan Jul 17, 2026
e36c57d
Update `SubdomainNavBar` desktop pills
danielguillan Jul 22, 2026
5d48f6b
Merge main into SubdomainNavBar refactor
danielguillan Jul 22, 2026
85151cc
Update visual snapshots after merging main
danielguillan Jul 22, 2026
02121b6
Improve docs
danielguillan Jul 22, 2026
aef1eb7
Update `SubdomainNavBar` tablet menu
danielguillan Jul 22, 2026
a1c7ae7
Merge main into SubdomainNavBar refactor
danielguillan Aug 5, 2026
7e7b932
Simplify SubdomainNavBar search trigger
danielguillan Aug 5, 2026
cd4d008
Update SubdomainNavBar visual snapshots
danielguillan Aug 5, 2026
c224d1d
Merge branch 'main' into danielguillan/brand-refactor-update-subdomai…
danielguillan Aug 6, 2026
2e276e0
Update changeset
danielguillan Aug 6, 2026
b5633f6
Address feedback
danielguillan Aug 6, 2026
86fa8fb
Fix a11y issues
danielguillan Aug 6, 2026
34298c0
Update SubdomainNavBar responsive navigation
danielguillan Aug 6, 2026
c472eb5
Cleanup
danielguillan Aug 6, 2026
f2995a4
Update snapshots
danielguillan Aug 6, 2026
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/quiet-pills-align.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react-brand': patch
---

Updated `SubdomainNavBar` desktop navigation links with compact hover, current-page, focus, and expanded overflow-trigger pill states.
89 changes: 89 additions & 0 deletions .changeset/tasty-cameras-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
'@primer/react-brand': minor
'@primer/brand-primitives': patch
---

Updated `SubdomainNavBar` with a gridline visual design, content slots, search APIs, and responsive navigation behavior.

- Added `leadingComponent` and `trailingComponent` props for rendering custom content around the navigation links and actions.
- Added a typed `id` prop for setting an HTML ID on the root element.
- Added a `style` prop for forwarding custom styles to the root element.

```tsx
<SubdomainNavBar
title="Subdomain"
fullWidth
leadingComponent={<span>Leading content</span>}
trailingComponent={<span>Trailing content</span>}
>
<SubdomainNavBar.Link href="/item-1">Item 1</SubdomainNavBar.Link>
<SubdomainNavBar.Link href="/item-2">Item 2</SubdomainNavBar.Link>
<SubdomainNavBar.PrimaryAction href="/action">Action</SubdomainNavBar.PrimaryAction>
</SubdomainNavBar>
```

- Added a responsive input-style search trigger that collapses to an icon-only button on smaller viewports, custom placeholder and shortcut labels, configurable keyboard shortcuts, grouped results, and a `labels` prop for localizing visible and accessible search text. The default `/` shortcut can be remapped or disabled with `keyboardShortcut`.

Exported `SubdomainNavBarSearchLabels` and `SubdomainNavBarSearchProps` for typed search configuration. The `SubdomainNavBar` ref now exposes `openSearch()` and `closeSearch()` methods.

```tsx
import * as React from 'react'
import {
Button,
SubdomainNavBar,
type SubdomainNavBarHandle,
type SubdomainNavBarSearchLabels,
type SubdomainNavBarSearchProps,
type SubdomainNavBarSearchResults,
} from '@primer/react-brand'

function Example() {
const navRef = React.useRef<SubdomainNavBarHandle | null>(null)
const [searchTerm, setSearchTerm] = React.useState('')
const searchResults: SubdomainNavBarSearchResults = [
{
title: 'Group',
results: [
{
title: 'Result',
description: 'Result description',
url: '/result',
date: '2026-01-01',
},
],
},
]
const labels = {
searchLabel: 'Buscar',
closeLabel: 'Cerrar',
formatResultsHeading: (term: string) => `Resultados para “${term}”`,
formatSuggestions: (count: number) => `${count} sugerencias.`,
} satisfies Partial<SubdomainNavBarSearchLabels>
const searchProps: SubdomainNavBarSearchProps = {
placeholder: 'Buscar',
keyboardShortcut: 'Command+Option+k',
shortcutLabel: '⌘+⌥+k',
labels,
searchResults,
searchTerm,
onChange: event => setSearchTerm(event.currentTarget.value),
onSubmit: event => event.preventDefault(),
}

return (
<>
<SubdomainNavBar ref={navRef} title="Subdomain">
<SubdomainNavBar.Link href="/item">Item</SubdomainNavBar.Link>
<SubdomainNavBar.Search {...searchProps} />
</SubdomainNavBar>

<Button onClick={() => navRef.current?.openSearch()}>Open search</Button>
<Button onClick={() => navRef.current?.closeSearch()}>Close search</Button>
</>
)
}
```

- Updated desktop overflow handling so a contiguous set of links moves into the More menu when space is limited and returns when space becomes available. Overflowed links are removed from keyboard and assistive technology navigation, and focus returns to the More button when its menu closes.

- Updated responsive and mobile menu behavior. Mobile navigation stays out of the desktop overflow menu, disclosures remain keyboard accessible when only slot content is present, and each control is associated with its menu. Leading content renders before navigation links, while actions and trailing content render in the menu footer.
292 changes: 206 additions & 86 deletions apps/next-docs/content/components/SubdomainNavBar/index.mdx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ module.exports = {
},
},
},
searchDialog: {
shadowColor: {
value: 'rgba(0, 0, 0, 0.1)',
dark: 'rgba(0, 0, 0, 0.1)',
},
backdropColor: {
value: 'rgba(0, 0, 0, 0.2)',
dark: 'rgba(0, 0, 0, 0.2)',
},
},
border: {
nav: {
default: {
Expand Down
40 changes: 37 additions & 3 deletions packages/e2e/scripts/playwright/playwright.generate-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const waitForTimeoutLookup = {
'components-subdomainnavbar--mobile-menu-open-many-items': 5500, // for all staggered animations
'components-subdomainnavbar--mobile-search-results-visible': 5500, // for the animation
'components-subdomainnavbar--mobile-no-links': 5500, // for the animation
'components-subdomainnavbar--mobile-leading-component-only-menu-open': 5500, // for the animation
'components-subdomainnavbar--overflow-menu-open': 1500, // wait for responsive overflow measurement
'components-subdomainnavbar--reversed-button-order-narrow': 5500, // for the animation
'components-button-features--primary-focus-non-standard-bg': 2000, // for the interaction test
'components-button-features--primary-focus': 2000, // for the interaction test
Expand Down Expand Up @@ -113,6 +115,28 @@ const waitForTimeoutLookup = {
'recipes-flexsuite-details--ai': 7000, // for the youtube video posters to load
}

const beforeScreenshotLookup: Partial<Record<string, string>> = {
'components-subdomainnavbar--overflow-menu-open': `
const moreButton = page.getByRole('button', {name: 'More'})
if ((await moreButton.getAttribute('aria-expanded')) !== 'true') {
await moreButton.click()
}
const overflowMenu = page.locator(\`[id="\${await moreButton.getAttribute('aria-controls')}"]\`)
await expect(moreButton).toHaveAttribute('aria-expanded', 'true')
await expect(overflowMenu).toBeVisible()
await expect(overflowMenu.getByRole('link', {name: 'Resources'})).toBeVisible()
`,
}

const screenshotOptionsLookup: Partial<Record<string, string>> = {
'components-subdomainnavbar--overflow-menu-open': `{animations: 'allow'}`,
}

const viewportLookup: Partial<Record<string, {width: number; height: number}>> = {
'components-subdomainnavbar--desktop-pill-states': {width: 1440, height: 900},
'components-subdomainnavbar--overflow-menu-open': {width: 1440, height: 900},
}

// const skipLocalizationsTestsFor = [
// 'components-actionmenu-features--disabled-item', // for the menu to open
// 'components-actionmenu-features--anchored-positioning', // for the menu to open
Expand Down Expand Up @@ -145,7 +169,6 @@ const skipTestLookup = [
'components-logosuite-features--mixed-width', // animation only
'components-logosuite-features--following-hero', // animation only
'components-logosuite-features--stacked', // animation only
'components-subdomainnavbar--overflow-menu-open', // flakey despite timeout
'components-ide-features--editor-only', // animation too long
'components-ide-features--editor-no-replay-button', // animation too long
'components-ide-features--chat-only', // animation too long
Expand Down Expand Up @@ -226,6 +249,7 @@ for (const key of Object.keys(categorisedStories)) {
)

const requiresTabletViewport = storyName.toLowerCase().includes('tablet')
const viewport = viewportLookup[id]
const requiresTouch = touchTestLookup.includes(id)
if (skipTestLookup.includes(id)) {
return acc
Expand All @@ -242,8 +266,8 @@ for (const key of Object.keys(categorisedStories)) {
await page.goto('http://localhost:${port}/iframe.html?${localeParam}args=&id=${id}&viewMode=story', { waitUntil: 'networkidle' })
await page.locator('body.sb-show-main').waitFor({ state: 'visible' })

${timeout ? `await page.waitForTimeout(${timeout})` : ''}
await expect(page).toHaveScreenshot({ fullPage: true })
${timeout ? `await page.waitForTimeout(${timeout})` : ''}${beforeScreenshotLookup[id] ?? ''}
await expect(page).toHaveScreenshot(${screenshotOptionsLookup[id] ?? '{fullPage: true}'})
});

`
Expand All @@ -252,6 +276,16 @@ for (const key of Object.keys(categorisedStories)) {
const languagesToTest = ['en']
const allLanguageTests = languagesToTest.map(language => generateTestForLanguage(language)).join('')

if (viewport) {
return (acc += `
// eslint-disable-next-line i18n-text/no-en
test.describe('Custom viewport test for ${storyName}', () => {
test.use({ viewport: { width: ${viewport.width}, height: ${viewport.height} } });
${allLanguageTests}
});
`)
}

if (requiresMobileViewport) {
return (acc += `
// eslint-disable-next-line i18n-text/no-en
Expand Down
Loading
Loading