Skip to content
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

SubNav v2 #824

Merged
merged 13 commits into from
Nov 28, 2024
32 changes: 32 additions & 0 deletions .changeset/shiny-taxis-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
'@primer/react-brand': minor
---

Updates to `SubNav` component

- New anchor-based navigation pattern available:

Use `variant="anchor"` on `SubNav.SubMenu` to apply anchor navigation as an alternative to the default dropdown-based submenu.

```jsx
<SubNav>
<SubNav.Heading href="#">Heading</SubNav.Heading>
<SubNav.Link href="#" aria-current="page">
Link with anchor navigation
<SubNav.SubMenu variant="anchor">
<SubNav.Link href="#">Anchor link one</SubNav.Link>
<SubNav.Link href="#">Anchor link two</SubNav.Link>
<SubNav.Link href="#">Anchor link three</SubNav.Link>
<SubNav.Link href="#">Anchor link four</SubNav.Link>
</SubNav.SubMenu>
</SubNav.Link>
<SubNav.Link href="#">Link</SubNav.Link>
<SubNav.Link href="#">Link</SubNav.Link>
</SubNav>
```

- Overlay now closes when user clicks outside of it

- Dropdown submenus now appear with white and black background and foreground colors respectively, irrespective of color mode.

- Various other visual updates to improve brand-alignment. These include adjustments to text size, weight, color and iconography.
36 changes: 30 additions & 6 deletions apps/docs/content/components/SubNav.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ description: A sub nav is a secondary navigation element, typically positioned b

import {Label} from '@primer/react'
import {PropTableValues} from '../../src/components'
import {SubNavSubMenuVariants} from '@primer/react-brand'

```js
import {SubNav} from '@primer/react-brand'
Expand Down Expand Up @@ -63,13 +64,15 @@ import {SubNav} from '@primer/react-brand'
</Container>
```

### Optional sub menu
### Optional submenu

Submenus appear as a dropdown by default.

```jsx live
<Container sx={{position: 'relative', height: 300}}>
<SubNav hasShadow>
<SubNav.Heading href="#">Features</SubNav.Heading>
<SubNav.Link href="#">
<SubNav.Link href="#" aria-current="page">
Actions
<SubNav.SubMenu>
<SubNav.Link href="#">Actions feature one</SubNav.Link>
Expand All @@ -79,9 +82,29 @@ import {SubNav} from '@primer/react-brand'
</SubNav.SubMenu>
</SubNav.Link>
<SubNav.Link href="#">Packages</SubNav.Link>
<SubNav.Link href="#">Copilot</SubNav.Link>
<SubNav.Link href="#">Code review</SubNav.Link>
</SubNav>
</Container>
```

### Optional anchor-based submenu

```jsx live
<Container sx={{position: 'relative', height: 300}}>
<SubNav>
<SubNav.Heading href="#">Features</SubNav.Heading>
<SubNav.Link href="#" aria-current="page">
Copilot
Actions
<SubNav.SubMenu variant="anchor">
<SubNav.Link href="#">Actions feature one</SubNav.Link>
<SubNav.Link href="#">Actions feature two</SubNav.Link>
<SubNav.Link href="#">Actions feature three</SubNav.Link>
<SubNav.Link href="#">Actions feature four</SubNav.Link>
</SubNav.SubMenu>
</SubNav.Link>
<SubNav.Link href="#">Packages</SubNav.Link>
<SubNav.Link href="#">Copilot</SubNav.Link>
<SubNav.Link href="#">Code review</SubNav.Link>
</SubNav>
</Container>
Expand Down Expand Up @@ -119,6 +142,7 @@ import {SubNav} from '@primer/react-brand'

### SubNav.SubMenu

| name | type | default | required | description |
| ---------- | ------------- | ------- | -------- | ---------------------------- |
| `children` | `SubNav.Link` | | `false` | Container for sub menu links |
| name | type | default | required | description |
| ---------- | --------------------------------------------------------------------- | ----------- | -------- | ------------------------------------- |
| `children` | `SubNav.Link` | | `false` | Container for submenu links |
| `variant` | <PropTableValues values={[...SubNavSubMenuVariants]} addLineBreaks /> | `'default'` | `false` | Alternative presentation for submenus |
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ module.exports = {
dark: 'var(--brand-color-text-default)',
},
active: {
value: 'var(--base-color-scale-blue-5)',
dark: 'var(--base-color-scale-blue-3)',
value: 'var(--brand-color-text-default)',
dark: 'var(--brand-color-text-default)',
},
},
subMenu: {
bgColor: {
value: 'var(--base-color-scale-white-0)',
dark: 'var(--base-color-scale-black-0)',
dark: 'var(--base-color-scale-white-0)',
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion packages/react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@

### Patch Changes

- [#568](https://github.com/primer/brand/pull/568) [`40a129d`](https://github.com/primer/brand/commit/40a129d78024612b625238d8a826fc06aa933465) Thanks [@rezrah](https://github.com/rezrah)! - Added support for optional `Button` and sub menu's in `SubNav` component.
- [#568](https://github.com/primer/brand/pull/568) [`40a129d`](https://github.com/primer/brand/commit/40a129d78024612b625238d8a826fc06aa933465) Thanks [@rezrah](https://github.com/rezrah)! - Added support for optional `Button` and submenu's in `SubNav` component.
joshfarrant marked this conversation as resolved.
Show resolved Hide resolved

Also added `fullWidth` prop to optionally remove the default component padding.

Expand Down
133 changes: 114 additions & 19 deletions packages/react/src/SubNav/SubNav.features.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import React from 'react'
import {Meta} from '@storybook/react'
import {INITIAL_VIEWPORTS} from '@storybook/addon-viewport'
import {linkTo} from '@storybook/addon-links'

import {SubNav} from './SubNav'
import bgPath from '../fixtures/images/background-stars.png'
import {ThemeProvider} from '../ThemeProvider'
import {Box} from '../Box'
import {Hero} from '../Hero'
import {Grid} from '../Grid'
import {Heading} from '../Heading'
import {Text} from '../Text'
import {RedlineBackground} from '../component-helpers'
import {Stack} from '../Stack'
import {expect, userEvent, within} from '@storybook/test'
import {Button} from '../Button'

export default {
title: 'Components/SubNav/Features',
Expand All @@ -20,7 +25,7 @@ export default {
},
} as Meta<typeof SubNav>

export const ExampleUsage = ({hasShadow, ...args}) => (
export const DropdownVariant = ({hasShadow, ...args}) => (
<main>
<Box paddingBlockStart={64} backgroundColor="subtle" style={{position: 'relative', zIndex: 32}}></Box>
<SubNav {...args} hasShadow={hasShadow}>
Expand Down Expand Up @@ -54,31 +59,35 @@ export const ExampleUsage = ({hasShadow, ...args}) => (
</Grid>
</main>
)
ExampleUsage.parameters = {
DropdownVariant.parameters = {
layout: 'fullscreen',
}

ExampleUsage.decorators = [
Story => (
<ThemeProvider colorMode="dark">
<div style={{backgroundColor: 'black', height: '100%', minHeight: '100dvh', backgroundImage: `url(${bgPath})`}}>
<Story />
</div>
</ThemeProvider>
),
]

export const NarrowExampleUsage = args => <ExampleUsage {...args} />
NarrowExampleUsage.parameters = {
export const NarrowDropdownVariant = args => <DropdownVariant {...args} />
NarrowDropdownVariant.parameters = {
layout: 'fullscreen',
viewport: {
defaultViewport: 'iphonex',
},
}

NarrowExampleUsage.decorators = [Story => <Story />]
export const NarrowDropdownVariantMenuOpen = args => <NarrowDropdownVariant {...args} />

NarrowDropdownVariantMenuOpen.parameters = {
layout: 'fullscreen',
viewport: {
defaultViewport: 'iphonex',
},
}
NarrowDropdownVariantMenuOpen.play = async ({canvasElement}) => {
const canvas = within(canvasElement)
await userEvent.click(canvas.getByTestId('SubNav-root-button'))
const overlayMenu = canvas.getByTestId('SubNav-root-overlay')
const firstLink = within(overlayMenu).getAllByRole('link')[0]
expect(firstLink).toHaveFocus()
}

export const WithShadow = args => <ExampleUsage {...args} hasShadow={true} />
export const WithShadow = args => <DropdownVariant {...args} hasShadow={true} />
WithShadow.parameters = {
layout: 'fullscreen',
}
Expand Down Expand Up @@ -137,6 +146,92 @@ export const LongerHeading = args => (
<SubNav.Action href="#">Call to action</SubNav.Action>
</SubNav>
)
FullWidth.parameters = {
LongerHeading.parameters = {
layout: 'fullscreen',
}

const AnchorNavVariantData = {
['Scale']: 'scale',
['AI']: 'ai',
['Security']: 'security',
['Reliability']: 'reliability',
}

export const AnchorNavVariant = args => (
<main>
<Box paddingBlockStart={64} backgroundColor="subtle" style={{position: 'relative', zIndex: 32}}></Box>
<SubNav {...args}>
<SubNav.Heading
href="https://github.com/enterprise/"
onClick={e => {
e.preventDefault()
linkTo('Components/SubNav/Features', 'AnchorNavVariant')
}}
>
Enterprise
</SubNav.Heading>
<SubNav.Link href="#" aria-current="page">
Overview
<SubNav.SubMenu variant="anchor">
{Object.entries(AnchorNavVariantData).map(([label, href]) => (
<SubNav.Link key={label} href={`#${href}`}>
{label}
</SubNav.Link>
))}
</SubNav.SubMenu>
</SubNav.Link>
<SubNav.Link href="https://github.com/enterprise/advanced-security">Advanced Security</SubNav.Link>
<SubNav.Link href="https://github.com/features/copilot#enterprise">Copilot Enterprise</SubNav.Link>
<SubNav.Link href="https://github.com/premium-support">Premium Support</SubNav.Link>
</SubNav>
<Box style={{paddingBlockEnd: '100dvh'}}>
{Object.entries(AnchorNavVariantData).map(([key, value]) => (
<RedlineBackground key={key} style={{scrollPaddingTop: 64}}>
<Stack key={value} id={value} direction="vertical" style={{justifyContent: 'center', height: 1000}}>
<Heading>{key}</Heading>
<Text as="p">SubNav is a component that allows users to navigate to different sections of a page.</Text>
<Button>Learn more</Button>
</Stack>
</RedlineBackground>
))}
</Box>
</main>
)
AnchorNavVariant.parameters = {
layout: 'fullscreen',
}

const customViewports = {
Narrow: {
name: 'Narrow',
styles: {
width: '280px',
height: '600px',
},
},
}

export const NarrowAnchorNavVariant = args => <AnchorNavVariant {...args} />
NarrowAnchorNavVariant.parameters = {
layout: 'fullscreen',
viewport: {
viewports: customViewports,
defaultViewport: 'Narrow',
},
}

export const NarrowAnchorNavVariantMenuOpen = args => <NarrowAnchorNavVariant {...args} />
NarrowAnchorNavVariantMenuOpen.parameters = {
layout: 'fullscreen',
viewport: {
viewports: customViewports,
defaultViewport: 'Narrow',
},
}
NarrowAnchorNavVariantMenuOpen.play = async ({canvasElement}) => {
const canvas = within(canvasElement)
await userEvent.click(canvas.getByTestId('SubNav-root-button'))
const overlayMenu = canvas.getByTestId('SubNav-root-overlay')
const firstLink = within(overlayMenu).getAllByRole('link')[0]
expect(firstLink).toHaveFocus()
}
Loading
Loading