Skip to content

Commit

Permalink
Merge branch 'main' into rezrah/fix-axe-lint-issues
Browse files Browse the repository at this point in the history
  • Loading branch information
rezrah authored Nov 14, 2023
2 parents e576516 + c3d7bdb commit f01d204
Show file tree
Hide file tree
Showing 14 changed files with 188 additions and 11 deletions.
15 changes: 15 additions & 0 deletions .changeset/odd-vans-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@primer/react-brand': patch
---

Added secondary action support to anchor nav.

When two actions are present, the first action becomes a primary button variant.

```jsx
<AnchorNav>
{/* ... */}
<AnchorNav.Action href="#">Get started</AnchorNav.Action>
<AnchorNav.SecondaryAction href="#">Compare plans</AnchorNav.SecondaryAction>
</AnchorNav>
```
62 changes: 59 additions & 3 deletions apps/docs/content/components/AnchorNav.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,50 @@ Please refer to our [Storybook examples](https://primer.style/brand/storybook/?p
</Container>
```

### Secondary action

`AnchorNav` can also feature secondary actions. When two actions are present, the first will be presented as the `primary` variant.

```jsx live
<Container sx={{position: 'relative', overflowX: 'scroll'}}>
<AnchorNav>
<AnchorNav.Link href="#fewer-links-section1">Section one</AnchorNav.Link>
<AnchorNav.Link href="#fewer-links-section2">Section two</AnchorNav.Link>
<AnchorNav.Link href="#fewer-links-section3">Section three</AnchorNav.Link>
<AnchorNav.Action href="#">Sign up</AnchorNav.Action>
<AnchorNav.SecondaryAction href="#">Learn more</AnchorNav.SecondaryAction>
</AnchorNav>

<section
id="fewer-links-section1"
style={{
padding: '100px 2rem',
backgroundColor: 'var(--base-color-scale-lemon-0)',
}}
>
Section 1
</section>
<section
id="fewer-links-section2"
style={{
padding: '100px 2rem',
backgroundColor: 'var(--base-color-scale-yellow-0)',
}}
>
Section 2
</section>
<section
id="fewer-links-section3"
style={{
padding: '100px 2rem',
backgroundColor: 'var(--base-color-scale-lime-0)',
}}
>
Section 3
</section>
</Container>
```

### Fewer links

`AnchorNav` implementations with fewer than `5` links, will automatically align links to the `start`.
Expand Down Expand Up @@ -160,9 +204,7 @@ Please refer to our [Storybook examples](https://primer.style/brand/storybook/?p

Additional props can be passed to the `<a>` element. [See MDN for a list of props](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attributes) accepted by the `<anchor>` element.

<h3 id="AnchorNav-action">
AnchorNav.Action <Label>Required</Label>
</h3>
<h3 id="AnchorNav-action">AnchorNav.Action</h3>

`AnchorNav.Action` is the primary call to action for the AnchorNav

Expand All @@ -175,3 +217,17 @@ Additional props can be passed to the `<a>` element. [See MDN for a list of prop
| `ref` | `React.RefObject` | | Forward a Ref to the underlying DOM node |

Additional props can be passed to the `<a>` element. [See MDN for a list of props](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attributes) accepted by the `<anchor>` element.

<h3 id="AnchorNav-secondary-action">AnchorNav.SecondaryAction</h3>

`AnchorNav.SecondaryAction` is an alternative call to action available for the AnchorNav

| Name | Type | Default | Description |
| :---------- | :---------------- | :-----: | :-------------------------------------------------- |
| `children` | `string` | | Required. Label text |
| `className` | `string` | | Applies a custom class |
| `href` | `string` | | Required. Local identifier for the anchored element |
| `id` | `string` | | Sets a custom id |
| `ref` | `React.RefObject` | | Forward a Ref to the underlying DOM node |

Additional props can be passed to the `<a>` element. [See MDN for a list of props](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attributes) accepted by the `<anchor>` element.
13 changes: 12 additions & 1 deletion packages/react/src/AnchorNav/AnchorNav.features.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ const Template: StoryFn<typeof AnchorNav> = (_, storyArgs: any) => {
{key}
</AnchorNav.Link>
))}
<AnchorNav.Action href="#">Sign up</AnchorNav.Action>
<AnchorNav.Action href="#">Get started</AnchorNav.Action>
<AnchorNav.SecondaryAction href="#">Compare plans</AnchorNav.SecondaryAction>
</AnchorNav>
{/**
* The following markup is provided for demonstration purposes only.
Expand Down Expand Up @@ -152,6 +153,16 @@ ShorterLabels.args = {
},
} as never

export const LongerLabels = Template.bind({})
LongerLabels.storyName = 'Longer labels'
LongerLabels.args = {
data: {
['Donec ultricies euismod']: 'donecultricieseuismodporttitor',
['Pellentesque eleifend metus']: 'pellentesqueeleifendmetus',
['Vestibulum consequat at']: 'vestibulumconsequatat',
},
} as never

export const CustomBackground = ({data, ...args}: {data: MockData; offset: number}) => {
return (
<div style={{backgroundColor: 'var(--base-color-scale-green-0)', paddingTop: args.offset}}>
Expand Down
28 changes: 24 additions & 4 deletions packages/react/src/AnchorNav/AnchorNav.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,11 @@
* 6. AnchorNav action
*/

.AnchorNav__actionsContainer {
display: flex;
gap: var(--base-size-8);
}

.AnchorNav-action {
z-index: 1;
display: flex;
Expand All @@ -356,25 +361,40 @@
align-self: flex-start;
}

@media screen and (max-width: 34rem) {
@media screen and (max-width: 40rem) {
.AnchorNav-action {
display: none;
}

.AnchorNav__actionsContainer {
flex-direction: column;
}

.AnchorNav--expanded .AnchorNav-action {
display: flex;
width: 100%;
margin-top: var(--base-size-8);
}
}

@media screen and (min-width: 34rem) and (max-width: 63.25rem) {
.AnchorNav-action {
display: flex;
@media screen and (min-width: 40rem) and (max-width: 63.25rem) {
.AnchorNav__actionsContainer {
position: absolute;
top: 0;
right: 0;
}
}

@media screen and (min-width: 40rem) {
.AnchorNav__actionsContainer {
gap: var(--base-size-16);
}

.AnchorNav__actionsContainer--multiple {
margin-inline-start: var(--base-size-16);
}
}

/*
* 7. Custom animations
*/
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/AnchorNav/AnchorNav.module.css.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ declare const styles: {
readonly "AnchorNav-menu-button": string;
readonly "AnchorNav-menu-button-arrow": string;
readonly "AnchorNav-overlay--expanded": string;
readonly "AnchorNav__actionsContainer": string;
readonly "AnchorNav-action": string;
readonly "AnchorNav__actionsContainer--multiple": string;
};
export = styles;

25 changes: 24 additions & 1 deletion packages/react/src/AnchorNav/AnchorNav.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const mockData = [
{title: 'Section four', id: 'section4'},
{title: 'Section five', id: 'section5'},
]
const MockAnchorNavFixture = ({data = mockData, ...rest}) => {
const MockAnchorNavFixture = ({data = mockData, withSecondAction = false, ...rest}) => {
return (
<>
<AnchorNav data-testid={AnchorNav.testIds.root} {...rest}>
Expand All @@ -26,6 +26,7 @@ const MockAnchorNavFixture = ({data = mockData, ...rest}) => {
)
})}
<AnchorNav.Action href="#">Action</AnchorNav.Action>
{withSecondAction && <AnchorNav.SecondaryAction href="#">Secondary action</AnchorNav.SecondaryAction>}
</AnchorNav>
{data.map((item, index) => {
return (
Expand Down Expand Up @@ -111,6 +112,28 @@ describe('AnchorNav', () => {
expect(actionEl).toHaveAttribute('href', '#') // renders with correct href
})

it('renders an secondary correctly', () => {
const {getByTestId} = render(<MockAnchorNavFixture withSecondAction />)
const secondaryActionEl = getByTestId(AnchorNav.testIds.secondaryAction)
expect(secondaryActionEl).toBeInTheDocument() // renders
expect(secondaryActionEl).toBeInTheDocument() // renders as an anchor
expect(secondaryActionEl).toHaveAttribute('href', '#') // renders with correct href
})

it('applies secondary as default variant when one action is present', () => {
const {getByTestId} = render(<MockAnchorNavFixture />)
const actionEl = getByTestId(AnchorNav.testIds.action)
expect(actionEl).toHaveClass('Button--secondary')
})

it('applies correct variant attributes to multiple actions', () => {
const {getByTestId} = render(<MockAnchorNavFixture withSecondAction />)
const actionEl = getByTestId(AnchorNav.testIds.action)
const secondaryActionEl = getByTestId(AnchorNav.testIds.secondaryAction)
expect(actionEl).toHaveClass('Button--primary')
expect(secondaryActionEl).toHaveClass('Button--secondary')
})

it('renders applies correct behaviors when menu button is toggled', () => {
const {getByTestId} = render(<MockAnchorNavFixture />)
const menuButton = getByTestId(AnchorNav.testIds.menuButton)
Expand Down
45 changes: 43 additions & 2 deletions packages/react/src/AnchorNav/AnchorNav.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import clsx from 'clsx'
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import React, {ReactElement, useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {ChevronDownIcon, ChevronUpIcon} from '@primer/octicons-react'
import {useId} from '@reach/auto-id'

Expand Down Expand Up @@ -29,6 +29,9 @@ const testIds = {
get action() {
return `${this.root}-action`
},
get secondaryAction() {
return `${this.root}-secondary-action`
},
}

export type AnchorNavProps = BaseProps<HTMLElement> & {
Expand Down Expand Up @@ -168,6 +171,13 @@ function _AnchorNav({children, enableDefaultBgColor = false, hideUntilSticky = f
return null
}).filter(Boolean)

const SecondaryAction = ValidChildren.map(child => {
if (React.isValidElement(child) && child.type === _AnchorNavActionSecondary) {
return React.cloneElement(child)
}
return null
}).filter(Boolean)

/* On page load, the rootMargin positions and/or thresholds of the IntersectionObserver
* may not be met depending on the position of the AnchorNav on the page.
* The following useEffect ensures that the first link always marked as the active link, until
Expand All @@ -179,6 +189,8 @@ function _AnchorNav({children, enableDefaultBgColor = false, hideUntilSticky = f
}
}, [currentActiveNavItem, Links])

const hasTwoActions = Action.length > 0 && SecondaryAction.length > 0

return (
<div ref={wrapperRef}>
<nav
Expand Down Expand Up @@ -227,7 +239,19 @@ function _AnchorNav({children, enableDefaultBgColor = false, hideUntilSticky = f
>
{Links}
</div>
{Action}
<span
className={clsx(
styles['AnchorNav__actionsContainer'],
hasTwoActions && styles['AnchorNav__actionsContainer--multiple'],
)}
>
{Action.length && SecondaryAction.length && React.isValidElement(Action[0])
? React.cloneElement(Action[0] as ReactElement, {
variant: 'primary',
})
: Action}
{SecondaryAction}
</span>
</div>
<span
className={clsx(menuOpen && styles['AnchorNav-overlay--expanded'])}
Expand Down Expand Up @@ -397,12 +421,29 @@ function _AnchorNavAction({children, href, ...rest}: AnchorNavActionProps) {
)
}

function _AnchorNavActionSecondary({children, href, ...rest}: AnchorNavActionProps) {
return (
<Button
as="a"
variant="secondary"
className={clsx(styles['AnchorNav-action'])}
href={href}
hasArrow={false}
data-testid={testIds.secondaryAction}
{...rest}
>
{children}
</Button>
)
}

/**
* AnchorNav allows users to navigate to different sections of a page.
* @see https://primer.style/brand/components/AnchorNav
*/
export const AnchorNav = Object.assign(_AnchorNav, {
Link: _AnchorNavLink,
Action: _AnchorNavAction,
SecondaryAction: _AnchorNavActionSecondary,
testIds,
})
9 changes: 9 additions & 0 deletions packages/react/src/AnchorNav/AnchorNav.visual.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ test.describe('Visual Comparison: AnchorNav', () => {
expect(await page.screenshot()).toMatchSnapshot()
})

test('AnchorNav / Longer labels', async ({page}) => {
await page.goto(
'http://localhost:6006/iframe.html?args=&id=components-anchornav-features--longer-labels&viewMode=story',
)

await page.waitForTimeout(500)
expect(await page.screenshot()).toMatchSnapshot()
})

test('AnchorNav / Custom Background', async ({page}) => {
await page.goto(
'http://localhost:6006/iframe.html?args=&id=components-anchornav-features--custom-background&viewMode=story',
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit f01d204

Please sign in to comment.