Skip to content

Commit d1cfc06

Browse files
Add SubdomainNavBar variants (#1468)
1 parent bfabda2 commit d1cfc06

40 files changed

Lines changed: 500 additions & 56 deletions

File tree

‎.changeset/bright-dots-navigate.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@primer/react-brand': minor
3+
---
4+
5+
Updated `SubdomainNavBar` with a new minimal visual appearance and layout that replaces the previous default.
6+
7+
⚠️ This is a visual and rendered-content breaking change. Please manually verify the update on your pages. The new default no longer applies the GitHub prefix to the site title.
8+
9+
To preserve the previous appearance, set `variant="featured"`.

‎apps/next-docs/content/components/SubdomainNavBar/index.mdx‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,36 @@ Please refer to our [Storybook examples](https://primer.style/brand/storybook/?p
4242
</div>
4343
```
4444

45+
### Featured variant
46+
47+
The `featured` variant displays the GitHub name alongside the site title and an expanded search field on desktop.
48+
49+
<Note>
50+
51+
This variant is for internal use only.
52+
53+
Please check with the Primer Brand team before using this variant.
54+
55+
</Note>
56+
57+
```jsx live
58+
<div style={{width: '100%'}}>
59+
<SubdomainNavBar title="Site title" variant="featured" fixed={false} fullWidth>
60+
<SubdomainNavBar.Link href="#">Overview</SubdomainNavBar.Link>
61+
<SubdomainNavBar.Link href="#">Documentation</SubdomainNavBar.Link>
62+
<SubdomainNavBar.Link href="#">Resources</SubdomainNavBar.Link>
63+
<SubdomainNavBar.Search
64+
placeholder="Search Site title"
65+
searchTerm=""
66+
searchResults={[]}
67+
onChange={() => undefined}
68+
onSubmit={event => event.preventDefault()}
69+
/>
70+
<SubdomainNavBar.PrimaryAction href="#">Primary CTA</SubdomainNavBar.PrimaryAction>
71+
</SubdomainNavBar>
72+
</div>
73+
```
74+
4575
### Search
4676

4777
`SubdomainNavBar` offers an optional search form control that supports both `onSubmit` and `onChange`; use `onChange` to display inline results. The `placeholder` labels the input in the opened dialog and defaults to `Search {title}`, or `Search` when no navigation title is available.
@@ -189,6 +219,7 @@ render(<LocalizedMenu />)
189219
| `style` | `React.CSSProperties` | | Forwards custom inline styles to the root element |
190220
| `fixed` | `boolean` | `true` | Fixes the navigation bar to the top of the viewport |
191221
| `fullWidth` | `boolean` | `false` | Allows the inner content to fill the available width |
222+
| `variant` | `'default' \| 'featured'` | `'default'` | Sets the navigation layout |
192223
| `logoHref` | `string` | `https://github.com` | Changes the URL of the GitHub logo |
193224
| `title` | `string` | | Required subdomain name used visibly and by assistive technologies |
194225
| `titleHref` | `string` | `/` | Links the title to the subdomain root |
@@ -197,7 +228,7 @@ render(<LocalizedMenu />)
197228
| `ref` | `React.Ref<SubdomainNavBarHandle>` | | Ref to the root element with `openSearch()` and `closeSearch()` methods |
198229
| `onNarrowMenuToggle` | `(isOpen: boolean) => void` | | Called with the new state when the narrow menu opens or closes |
199230

200-
`SubdomainNavBarProps`, `SubdomainNavBarHandle`, and `SubdomainNavBarMenuLabels` are exported from `@primer/react-brand`.
231+
`SubdomainNavBarProps`, `SubdomainNavBarHandle`, `SubdomainNavBarMenuLabels`, and `SubdomainNavBarVariant` are exported from `@primer/react-brand`.
201232

202233
### Menu labels
203234

‎packages/react/src/SubdomainNavBar/SubdomainNavBar.features.stories.tsx‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,3 +991,96 @@ export const SkipToMainTagWithId: Story = {
991991
await expect(skipLink).toHaveAttribute('href', '#the-main-tag')
992992
},
993993
}
994+
995+
const FeaturedVariantExample = ({withCustomComponents = false}: {withCustomComponents?: boolean}) => (
996+
<SubdomainNavBar
997+
title="Site title"
998+
titleHref="/"
999+
variant="featured"
1000+
fullWidth
1001+
fixed={false}
1002+
leadingComponent={withCustomComponents ? <Token>Public beta</Token> : undefined}
1003+
trailingComponent={
1004+
withCustomComponents ? (
1005+
<Button as="a" href="#repository" variant="secondary" size="small">
1006+
View repository
1007+
</Button>
1008+
) : undefined
1009+
}
1010+
>
1011+
{navigationLinks.map(link => (
1012+
<SubdomainNavBar.Link key={link} href={`#${link}`}>
1013+
{link
1014+
.toLowerCase()
1015+
.split(' ')
1016+
.map(word => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)
1017+
.join(' ')}
1018+
</SubdomainNavBar.Link>
1019+
))}
1020+
<SubdomainNavBar.Search
1021+
placeholder="Search Site title"
1022+
keyboardShortcut="/"
1023+
shortcutLabel="/"
1024+
searchTerm=""
1025+
searchResults={[]}
1026+
onSubmit={event => event.preventDefault()}
1027+
onChange={() => undefined}
1028+
/>
1029+
<SubdomainNavBar.SecondaryAction href="#sign-in">Sign in</SubdomainNavBar.SecondaryAction>
1030+
<SubdomainNavBar.PrimaryAction href="#get-started">Get started</SubdomainNavBar.PrimaryAction>
1031+
</SubdomainNavBar>
1032+
)
1033+
1034+
export const FeaturedVariant: Story = {
1035+
render: () => <FeaturedVariantExample />,
1036+
play: async ({canvasElement}) => {
1037+
const canvas = within(canvasElement)
1038+
const titleLink = canvas.getByRole('link', {name: 'Site title home'})
1039+
const searchPlaceholder = canvas.getByText('Search Site title')
1040+
const navbar = titleLink.closest('header')
1041+
1042+
await expect(titleLink).toHaveTextContent('GitHub Site title')
1043+
await expect(navbar).toHaveClass('SubdomainNavBar--variant-featured')
1044+
await expect(getComputedStyle(searchPlaceholder).display).not.toBe('none')
1045+
},
1046+
name: 'Featured Variant',
1047+
}
1048+
1049+
export const FeaturedVariantWithLeadingAndTrailingComponents: Story = {
1050+
render: () => <FeaturedVariantExample withCustomComponents />,
1051+
name: 'Featured Variant With Leading and Trailing Components',
1052+
}
1053+
1054+
export const FeaturedVariantTabletMenuOpen: Story = {
1055+
render: () => <FeaturedVariantExample />,
1056+
globals: {
1057+
viewport: {value: 'ipad'},
1058+
},
1059+
play: async ({canvasElement}) => {
1060+
const canvas = within(canvasElement)
1061+
await userEvent.click(canvas.getByRole('button', {name: 'Menu'}))
1062+
const closeButton = canvas.getByRole('button', {name: 'Close'})
1063+
const menu = canvasElement.ownerDocument.getElementById(closeButton.getAttribute('aria-controls') as string)
1064+
1065+
await expect(closeButton).toHaveAttribute('aria-expanded', 'true')
1066+
await expect(menu).toBeVisible()
1067+
},
1068+
name: 'Featured Variant Tablet Menu Open',
1069+
}
1070+
1071+
export const FeaturedVariantMobile: Story = {
1072+
render: () => <FeaturedVariantExample />,
1073+
globals: {
1074+
viewport: {value: 'iphonex'},
1075+
},
1076+
play: async ({canvasElement}) => {
1077+
const canvas = within(canvasElement)
1078+
const menuButton = canvas.getByRole('button', {name: 'Menu'})
1079+
const innerContainer = menuButton.closest('[data-testid="SubdomainNavBar-inner-container"]')
1080+
const menuButtonRect = menuButton.getBoundingClientRect()
1081+
const innerContainerRect = innerContainer?.getBoundingClientRect()
1082+
1083+
await expect(Math.abs(menuButtonRect.right - (innerContainerRect?.right ?? 0))).toBeLessThanOrEqual(1)
1084+
},
1085+
name: 'Featured Variant Mobile',
1086+
}

‎packages/react/src/SubdomainNavBar/SubdomainNavBar.module.css‎

Lines changed: 117 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,18 @@
22
* Main styles for the subdomain navigation bar.
33
*/
44
.SubdomainNavBar {
5-
--SubdomainNavBar-height: var(--base-size-64);
6-
75
position: relative;
86
z-index: 90;
97
font-weight: 550;
108
background: var(--brand-color-canvas-default);
11-
border-block-end: var(--brand-borderWidth-thin) solid var(--brand-color-border-muted);
129
height: var(--SubdomainNavBar-height);
1310
}
1411

1512
.SubdomainNavBar::after {
1613
content: '';
1714
position: absolute;
1815
inset-inline: 0;
19-
inset-block-start: calc(100% + var(--brand-borderWidth-thin));
16+
inset-block-start: 100%;
2017
height: var(--brand-borderWidth-thin);
2118
background-color: var(--brand-color-text-emphasized);
2219
pointer-events: none;
@@ -105,16 +102,18 @@
105102

106103
.SubdomainNavBar-back-arrow {
107104
position: absolute;
105+
display: flex;
108106
inset-inline-start: calc(-1 * var(--base-size-24) + var(--base-size-4));
107+
inset-block-start: 50%;
109108
color: var(--brand-color-text-muted);
110109
opacity: 0;
111-
transform: translateX(var(--base-size-4));
110+
transform: translate(var(--base-size-4), -50%);
112111
transition: opacity var(--brand-animation-duration-fast), transform var(--brand-animation-duration-fast) ease;
113112
}
114113

115114
.SubdomainNavBar-logo-mark:hover .SubdomainNavBar-back-arrow {
116115
opacity: 1;
117-
transform: translateX(0);
116+
transform: translate(0, -50%);
118117
}
119118

120119
.SubdomainNavBar-logo-mark svg {
@@ -133,12 +132,29 @@
133132
text-decoration: none;
134133
}
135134

136-
.SubdomainNavBar-title-prefix {
135+
.SubdomainNavBar--variant-featured .SubdomainNavBar-title-prefix {
137136
color: var(--brand-color-text-default);
138137
}
139138

140139
.SubdomainNavBar-title-label {
141-
color: var(--brand-color-text-muted);
140+
color: inherit;
141+
}
142+
143+
.SubdomainNavBar--variant-default.SubdomainNavBar--has-title .SubdomainNavBar-title-area > li:first-child {
144+
display: flex;
145+
align-items: center;
146+
align-self: stretch;
147+
}
148+
149+
.SubdomainNavBar--variant-default.SubdomainNavBar--has-title .SubdomainNavBar-title-area > li:first-child::after {
150+
content: '';
151+
box-sizing: border-box;
152+
align-self: center;
153+
flex: none;
154+
width: var(--brand-borderWidth-thin);
155+
height: var(--SubdomainNavBar-height);
156+
margin-inline: var(--base-size-16);
157+
background-color: var(--brand-color-border-muted);
142158
}
143159

144160
.SubdomainNavBar-leading-component,
@@ -420,7 +436,7 @@
420436
position: absolute;
421437
z-index: 100;
422438
right: auto;
423-
background-color: var(--base-color-scale-white-0);
439+
background-color: var(--brand-color-canvas-default);
424440
border-radius: var(--brand-borderRadius-large);
425441
animation: fade-in-down 0.25s, enlarge-shadow 250ms forwards;
426442
animation-timing-function: var(--brand-animation-easing-default);
@@ -784,6 +800,11 @@
784800
animation-timing-function: var(--brand-animation-easing-default);
785801
}
786802

803+
.SubdomainNavBar--variant-default .SubdomainNavBar-menu-wrapper {
804+
inset-inline-start: auto;
805+
min-width: calc(var(--base-size-128) * 3);
806+
}
807+
787808
.SubdomainNavBar-menu-wrapper--close {
788809
display: none;
789810
}
@@ -1018,7 +1039,6 @@
10181039

10191040
@media screen and (max-width: 47.99rem) {
10201041
.SubdomainNavBar {
1021-
--SubdomainNavBar-height: calc(var(--base-size-64) - var(--base-size-8));
10221042
--SubdomainNavBar-mobile-control-size: var(--base-size-48);
10231043
}
10241044

@@ -1352,6 +1372,93 @@
13521372

13531373
align-self: stretch;
13541374
}
1375+
1376+
.SubdomainNavBar--variant-default.SubdomainNavBar--has-primary-nav {
1377+
border-block-end: var(--brand-borderWidth-thin) solid var(--brand-color-border-muted);
1378+
height: calc(var(--SubdomainNavBar-height) + var(--base-size-48));
1379+
}
1380+
1381+
.SubdomainNavBar--variant-default::after {
1382+
inset-block-start: var(--SubdomainNavBar-height);
1383+
}
1384+
1385+
.SubdomainNavBar--variant-default .SubdomainNavBar-inner-container {
1386+
display: grid;
1387+
grid-template-columns: auto minmax(0, 1fr) auto;
1388+
grid-template-rows: var(--SubdomainNavBar-height);
1389+
}
1390+
1391+
.SubdomainNavBar--variant-default.SubdomainNavBar--has-primary-nav .SubdomainNavBar-inner-container {
1392+
grid-template-rows: var(--SubdomainNavBar-height) var(--base-size-48);
1393+
}
1394+
1395+
.SubdomainNavBar--variant-default .SubdomainNavBar-title-navigation {
1396+
grid-column: 1;
1397+
grid-row: 1;
1398+
min-width: 0;
1399+
}
1400+
1401+
.SubdomainNavBar--variant-default .SubdomainNavBar-primary-nav {
1402+
grid-column: 1 / -1;
1403+
grid-row: 2;
1404+
min-width: 0;
1405+
border-block-start: var(--brand-borderWidth-thin) solid var(--brand-color-border-muted);
1406+
}
1407+
1408+
.SubdomainNavBar--variant-default .SubdomainNavBar-primary-nav-list {
1409+
padding-inline-start: calc(var(--base-size-64) + var(--brand-borderWidth-thin));
1410+
}
1411+
1412+
.SubdomainNavBar--variant-default .SubdomainNavBar-inner-container > .SubdomainNavBar-leading-component {
1413+
grid-column: 2;
1414+
grid-row: 1;
1415+
justify-self: start;
1416+
border-inline-start: 0;
1417+
}
1418+
1419+
.SubdomainNavBar--variant-default .SubdomainNavBar-inner-container > .SubdomainNavBar-secondary-nav {
1420+
grid-column: 3;
1421+
grid-row: 1;
1422+
}
1423+
1424+
.SubdomainNavBar--variant-default .SubdomainNavBar-search-trigger {
1425+
border-inline-start: var(--brand-borderWidth-thin) solid var(--brand-color-border-muted);
1426+
}
1427+
}
1428+
1429+
.SubdomainNavBar--variant-default .SubdomainNavBar-search-input-button {
1430+
justify-content: center;
1431+
gap: 0;
1432+
width: var(--base-size-32);
1433+
padding: var(--base-size-8);
1434+
background-color: transparent;
1435+
}
1436+
1437+
.SubdomainNavBar--variant-default .SubdomainNavBar-search-input-button-placeholder {
1438+
gap: 0;
1439+
}
1440+
1441+
.SubdomainNavBar--variant-default .SubdomainNavBar-search-input-button-placeholder-text,
1442+
.SubdomainNavBar--variant-default .SubdomainNavBar-search-input-button-shortcut {
1443+
display: none;
1444+
}
1445+
1446+
@media screen and (min-width: 48rem) {
1447+
.SubdomainNavBar--variant-default .SubdomainNavBar-search-trigger {
1448+
flex: 0 0 var(--base-size-48);
1449+
width: var(--base-size-48);
1450+
min-width: var(--base-size-48);
1451+
}
1452+
1453+
.SubdomainNavBar--variant-default .SubdomainNavBar-search-trigger {
1454+
padding-inline: 0;
1455+
}
1456+
1457+
.SubdomainNavBar--variant-default .SubdomainNavBar-search-input-button {
1458+
width: 100%;
1459+
block-size: 100%;
1460+
padding-inline: var(--base-size-16);
1461+
}
13551462
}
13561463

13571464
@media screen and (min-width: 1024px) {

‎packages/react/src/SubdomainNavBar/SubdomainNavBar.module.css.d.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
declare const styles: {
22
readonly "SubdomainNavBar": string;
3+
readonly "SubdomainNavBar--has-primary-nav": string;
4+
readonly "SubdomainNavBar--has-title": string;
5+
readonly "SubdomainNavBar--variant-default": string;
6+
readonly "SubdomainNavBar--variant-featured": string;
37
readonly "SubdomainNavBar-back-arrow": string;
48
readonly "SubdomainNavBar-button-area": string;
59
readonly "SubdomainNavBar-button-area--has-leading-item": string;
@@ -50,6 +54,7 @@ declare const styles: {
5054
readonly "SubdomainNavBar-search-input-area": string;
5155
readonly "SubdomainNavBar-search-input-button": string;
5256
readonly "SubdomainNavBar-search-input-button-placeholder": string;
57+
readonly "SubdomainNavBar-search-input-button-placeholder-text": string;
5358
readonly "SubdomainNavBar-search-input-button-shortcut": string;
5459
readonly "SubdomainNavBar-search-result-group": string;
5560
readonly "SubdomainNavBar-search-result-group-heading": string;
@@ -71,6 +76,7 @@ declare const styles: {
7176
readonly "SubdomainNavBar-title": string;
7277
readonly "SubdomainNavBar-title-area": string;
7378
readonly "SubdomainNavBar-title-label": string;
79+
readonly "SubdomainNavBar-title-navigation": string;
7480
readonly "SubdomainNavBar-title-prefix": string;
7581
readonly "SubdomainNavBar-trailing-component": string;
7682
readonly "enlarge-shadow": string;

‎packages/react/src/SubdomainNavBar/SubdomainNavBar.stories.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ const meta = {
3232
titleHref: {
3333
control: 'text',
3434
},
35+
variant: {
36+
control: 'select',
37+
options: ['default', 'featured'],
38+
description: 'Use `featured` for the GitHub-prefixed single-row navigation with an expanded search field.',
39+
},
3540
},
3641
} satisfies Meta<StoryArgs>
3742

0 commit comments

Comments
 (0)