Skip to content

Commit cad2bc0

Browse files
broccolinisouppksjcecolebemis
authored
UnderlineNav overflow behaviour implementation (#2297)
* initial impl for overflow behaviour * scroll behaviour initial commit - wip * overflow and scroll behaviour and styles * introduce scroll arrow buttons * scroll behaviour in dept * revert back stories to original state * Fix types and align/variant issues * Use separate component for arrow buttons * fade out affects on scroll ends * type issues and some refactor * style refactor & theming & scrollIntoView from primer behaviour * Update documentation * Update documentation * add changeset * remove scroll story until finding a way to simulate coarse pointer * add single variant selection to ActionList * take more button into account when calculating & code review feedback * remove scroll behaviour - due to accessibility concerns * hover state remove on mobile and improvments on more btn functionality * remove prop controls and align story * update tests and docs * update docs * overflow effect improvments * Revert "remove scroll behaviour - due to accessibility concerns" This reverts commit eade73d. * scroll behaviour feedback * update tests * keyboard navigation tab through * Update .changeset/sweet-eggs-complain.md Co-authored-by: Cole Bemis <[email protected]> * Update docs/content/drafts/UnderlineNav2.mdx Co-authored-by: Pavithra Kodmad <[email protected]> Co-authored-by: Cole Bemis <[email protected]>
1 parent 33ba836 commit cad2bc0

File tree

10 files changed

+691
-281
lines changed

10 files changed

+691
-281
lines changed

.changeset/sweet-eggs-complain.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react': minor
3+
---
4+
5+
UnderlineNav2: Introducing overflow behavior for fine and coarse pointer devices

docs/content/drafts/UnderlineNav2.mdx

Lines changed: 88 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
---
22
title: UnderlineNav v2
3+
componentId: underline_nav_2
34
status: Draft
45
description: Use an underlined nav to allow tab like navigation with overflow behaviour in your UI.
6+
source: https://github.com/primer/react/tree/main/src/UnderlineNav2
7+
storybook: https://primer.style/react/storybook/?path=/story/layout-underlinenav
58
---
69

10+
```js
11+
import {UnderlineNav} from '@primer/react/drafts'
12+
```
13+
714
## Examples
815

916
### Simple
1017

1118
```jsx live drafts
12-
<UnderlineNav label="simple nav">
19+
<UnderlineNav>
1320
<UnderlineNav.Item selected>Item 1</UnderlineNav.Item>
1421
<UnderlineNav.Item>Item 2</UnderlineNav.Item>
1522
<UnderlineNav.Item>Item 3</UnderlineNav.Item>
@@ -19,20 +26,88 @@ description: Use an underlined nav to allow tab like navigation with overflow be
1926
### With icons
2027

2128
```jsx live drafts
22-
<UnderlineNav label="simple nav with icons">
23-
<UnderlineNav.Item selected leadingIcon={EyeIcon}>
24-
Item 1
29+
<UnderlineNav>
30+
<UnderlineNav.Item selected icon={CodeIcon}>
31+
Code
32+
</UnderlineNav.Item>
33+
<UnderlineNav.Item icon={IssueOpenedIcon} counter={30}>
34+
Issues
35+
</UnderlineNav.Item>
36+
<UnderlineNav.Item icon={GitPullRequestIcon} counter={3}>
37+
Pull requests
38+
</UnderlineNav.Item>
39+
<UnderlineNav.Item icon={CommentDiscussionIcon}>Discussions</UnderlineNav.Item>
40+
<UnderlineNav.Item icon={EyeIcon} counter={9}>
41+
Actions
42+
</UnderlineNav.Item>
43+
<UnderlineNav.Item icon={EyeIcon} counter={7}>
44+
Projects
2545
</UnderlineNav.Item>
26-
<UnderlineNav.Item>Item 2</UnderlineNav.Item>
2746
</UnderlineNav>
2847
```
2948

30-
### Small variant
49+
### Overflow Behaviour
50+
51+
When overflow occurs, the component first hides icons if present to optimize for space and show as many items as possible. (Only for fine pointer devices)
52+
53+
#### Items without Icons
3154

3255
```jsx live drafts
33-
<UnderlineNav label="small variant" variant="small">
34-
<UnderlineNav.Item selected>Item 1</UnderlineNav.Item>
35-
<UnderlineNav.Item>Item 2</UnderlineNav.Item>
56+
<UnderlineNav>
57+
<UnderlineNav.Item selected icon={CodeIcon}>
58+
Code
59+
</UnderlineNav.Item>
60+
<UnderlineNav.Item icon={IssueOpenedIcon} counter={30}>
61+
Issues
62+
</UnderlineNav.Item>
63+
<UnderlineNav.Item icon={GitPullRequestIcon} counter={3}>
64+
Pull requests
65+
</UnderlineNav.Item>
66+
<UnderlineNav.Item icon={CommentDiscussionIcon}>Discussions</UnderlineNav.Item>
67+
<UnderlineNav.Item icon={PlayIcon} counter={9}>
68+
Actions
69+
</UnderlineNav.Item>
70+
<UnderlineNav.Item icon={ProjectIcon} counter={7}>
71+
Projects
72+
</UnderlineNav.Item>
73+
<UnderlineNav.Item icon={ShieldLockIcon}>Security</UnderlineNav.Item>
74+
<UnderlineNav.Item icon={GraphIcon}>Insights</UnderlineNav.Item>
75+
<UnderlineNav.Item icon={GearIcon} counter={1}>
76+
Settings
77+
</UnderlineNav.Item>
78+
</UnderlineNav>
79+
```
80+
81+
#### Display `More` menu
82+
83+
If there is still overflow, the component will behave depending on the pointer.
84+
85+
```jsx live drafts
86+
<UnderlineNav>
87+
<UnderlineNav.Item selected icon={CodeIcon}>
88+
Code
89+
</UnderlineNav.Item>
90+
<UnderlineNav.Item icon={IssueOpenedIcon} counter={30}>
91+
Issues
92+
</UnderlineNav.Item>
93+
<UnderlineNav.Item icon={GitPullRequestIcon} counter={3}>
94+
Pull requests
95+
</UnderlineNav.Item>
96+
<UnderlineNav.Item icon={CommentDiscussionIcon}>Discussions</UnderlineNav.Item>
97+
<UnderlineNav.Item icon={EyeIcon} counter={9}>
98+
Actions
99+
</UnderlineNav.Item>
100+
<UnderlineNav.Item icon={EyeIcon} counter={7}>
101+
Projects
102+
</UnderlineNav.Item>
103+
<UnderlineNav.Item icon={EyeIcon}>Security</UnderlineNav.Item>
104+
<UnderlineNav.Item icon={EyeIcon} counter={14}>
105+
Insights
106+
</UnderlineNav.Item>
107+
<UnderlineNav.Item icon={EyeIcon} counter={1}>
108+
Settings
109+
</UnderlineNav.Item>
110+
<UnderlineNav.Item icon={EyeIcon}>Wiki</UnderlineNav.Item>
36111
</UnderlineNav>
37112
```
38113

@@ -44,19 +119,6 @@ description: Use an underlined nav to allow tab like navigation with overflow be
44119
<PropsTableRow name="aria-label" type="string" />
45120
<PropsTableRow name="aria-labelledby" type="string" />
46121
<PropsTableRow name="aria-describedby" type="string" />
47-
<PropsTableRow
48-
name="overflow"
49-
type="'auto' | 'menu' | 'scroll'"
50-
defaultValue="auto"
51-
description="Controls the type of overflow behaviour in smaller screens"
52-
/>
53-
<PropsTableRow name="align" type="right | left" defaultValue="left" description="The alignment of the nav links" />
54-
<PropsTableRow
55-
name="variant"
56-
type="default | small"
57-
defaultValue="default"
58-
description="The alignment of the nav links"
59-
/>
60122
<PropsTableRow
61123
name="afterSelect"
62124
type="(event) => void"
@@ -68,7 +130,7 @@ description: Use an underlined nav to allow tab like navigation with overflow be
68130
### UnderlineNav.Item
69131

70132
<PropsTable>
71-
<PropsTableRow name="leadingIcon" type="Component" description="The leading icon comes before item label" />
133+
<PropsTableRow name="icon" type="Component" description="The leading icon comes before item label" />
72134
<PropsTableRow name="selected" type="boolean" description="Whether the link is selected" />
73135
<PropsTableRow
74136
name="onSelect"
@@ -89,9 +151,9 @@ description: Use an underlined nav to allow tab like navigation with overflow be
89151
<ComponentChecklist
90152
items={{
91153
propsDocumented: true,
92-
noUnnecessaryDeps: false,
93-
adaptsToThemes: false,
94-
adaptsToScreenSizes: false,
154+
noUnnecessaryDeps: true,
155+
adaptsToThemes: true,
156+
adaptsToScreenSizes: true,
95157
fullTestCoverage: false,
96158
usedInProduction: false,
97159
usageExamplesDocumented: false,

src/UnderlineNav2/UnderlineNav.test.tsx

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,30 @@
11
import React from 'react'
22
import '@testing-library/jest-dom/extend-expect'
3-
import {render} from '@testing-library/react'
3+
import {fireEvent, render} from '@testing-library/react'
4+
import {CodeIcon, EyeIcon} from '@primer/octicons-react'
45

56
import {UnderlineNav} from '.'
67

8+
// window.matchMedia() is not implemented by JSDOM so we have to create a mock:
9+
// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
10+
Object.defineProperty(window, 'matchMedia', {
11+
writable: true,
12+
value: jest.fn().mockImplementation(query => ({
13+
matches: false,
14+
media: query,
15+
onchange: null,
16+
addListener: jest.fn(), // deprecated
17+
removeListener: jest.fn(), // deprecated
18+
addEventListener: jest.fn(),
19+
removeEventListener: jest.fn(),
20+
dispatchEvent: jest.fn()
21+
}))
22+
})
23+
24+
Object.defineProperty(window.Element.prototype, 'scrollTo', {
25+
value: jest.fn(),
26+
writable: true
27+
})
728
describe('UnderlineNav', () => {
829
test('selected nav', () => {
930
const {getByText} = render(
@@ -30,15 +51,47 @@ describe('UnderlineNav', () => {
3051

3152
expect(nav.getAttribute('aria-label')).toBe('Test nav')
3253
})
33-
test('respect align prop', () => {
54+
test('with icons', () => {
3455
const {container} = render(
56+
<UnderlineNav label="Test nav">
57+
<UnderlineNav.Item icon={CodeIcon}>Code</UnderlineNav.Item>
58+
<UnderlineNav.Item icon={EyeIcon} counter={6}>
59+
Issues
60+
</UnderlineNav.Item>
61+
<UnderlineNav.Item>Pull Request</UnderlineNav.Item>
62+
</UnderlineNav>
63+
)
64+
const nav = container.getElementsByTagName('nav')[0]
65+
expect(nav.getElementsByTagName('svg').length).toEqual(2)
66+
})
67+
test('should fire onSelect on click and keypress', async () => {
68+
const onSelect = jest.fn()
69+
const {getByText} = render(
70+
<UnderlineNav label="Test nav">
71+
<UnderlineNav.Item onSelect={onSelect}>Item 1</UnderlineNav.Item>
72+
<UnderlineNav.Item onSelect={onSelect}>Item 2</UnderlineNav.Item>
73+
<UnderlineNav.Item onSelect={onSelect}>Item 3</UnderlineNav.Item>
74+
</UnderlineNav>
75+
)
76+
const item = getByText('Item 1')
77+
fireEvent.click(item)
78+
expect(onSelect).toHaveBeenCalledTimes(1)
79+
fireEvent.keyPress(item, {key: 'Enter', code: 13, charCode: 13})
80+
expect(onSelect).toHaveBeenCalledTimes(2)
81+
})
82+
test('respect counter prop', () => {
83+
const {getByText} = render(
3584
<UnderlineNav label="Test nav" align="right">
36-
<UnderlineNav.Item selected>Item 1</UnderlineNav.Item>
85+
<UnderlineNav.Item counter={8} selected>
86+
Item 1
87+
</UnderlineNav.Item>
3788
<UnderlineNav.Item>Item 2</UnderlineNav.Item>
3889
<UnderlineNav.Item>Item 3</UnderlineNav.Item>
3990
</UnderlineNav>
4091
)
41-
const nav = container.getElementsByTagName('nav')[0]
42-
expect(nav).toHaveStyle(`justify-content:flex-end`)
92+
const item = getByText('Item 1').closest('a')
93+
const counter = item?.getElementsByTagName('span')[2]
94+
expect(counter?.className).toContain('CounterLabel')
95+
expect(counter?.textContent).toBe('8')
4396
})
4497
})

0 commit comments

Comments
 (0)