Skip to content

Commit e2a2d78

Browse files
[UnderlineNav2]: Follow new storybook documentation format & improvements (#2485)
* Follow new storybook doc format * add example stories * add subcomponent * improvement on stories * add a helper for icon selection on controls * remove theme and style wrappers * don't worry calling overflowEffect function if the navwidth is 0 * add some interaction stories * disable chromatic snapshot * do not rely on storybook's viewport resize * refactor some interaction tests * wait after resizing the window * add changeset * rename stories * update keyboard navigation interaction tests
1 parent 8c764f6 commit e2a2d78

File tree

8 files changed

+510
-105
lines changed

8 files changed

+510
-105
lines changed

.changeset/flat-cars-type.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react': patch
3+
---
4+
5+
UnderlineNav2: Only run overflow layout function when nav item has a width
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from 'react'
2+
import {Meta, Story} from '@storybook/react'
3+
import {UnderlineNav} from './index'
4+
import {UnderlineNavItem} from './UnderlineNavItem'
5+
import {CodeIcon, GitPullRequestIcon, PeopleIcon} from '@primer/octicons-react'
6+
import {OcticonArgType} from '../utils/story-helpers'
7+
8+
export default {
9+
title: 'Drafts/Components/UnderlineNav/UnderlineNav.Item',
10+
component: UnderlineNavItem,
11+
decorators: [
12+
Story => {
13+
return (
14+
<UnderlineNav aria-label="Repository">
15+
<Story />
16+
</UnderlineNav>
17+
)
18+
}
19+
],
20+
parameters: {
21+
controls: {
22+
expanded: true,
23+
exclude: ['as']
24+
}
25+
},
26+
args: {
27+
children: 'Code',
28+
counter: '12K',
29+
icon: PeopleIcon
30+
},
31+
argTypes: {
32+
children: {
33+
type: 'string'
34+
},
35+
counter: {
36+
type: 'string'
37+
},
38+
icon: OcticonArgType([CodeIcon, GitPullRequestIcon, PeopleIcon])
39+
}
40+
} as Meta<typeof UnderlineNavItem>
41+
42+
// UnderlineNav.Item controls only work on the "Docs" tab. Because UnderlineNav children don't get re-rendered when they are changed.
43+
// This is an intentional behaviour of UnderlineNav for keeping a selected menu item visible. I will update here once I find a better solution.
44+
// In the meantime, you can use the "Docs" tab to see the controls.
45+
46+
export const Playground: Story = args => {
47+
return (
48+
<UnderlineNavItem selected {...args}>
49+
{args.children}
50+
</UnderlineNavItem>
51+
)
52+
}

src/UnderlineNav2/UnderlineNav.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ const overflowEffect = (
5757
if (childWidthArray.length === 0) {
5858
updateListAndMenu({items: childArray, actions: []}, iconsVisible)
5959
}
60-
6160
const numberOfItemsPossible = calculatePossibleItems(childWidthArray, navWidth)
6261
const numberOfItemsWithoutIconPossible = calculatePossibleItems(noIconChildWidthArray, navWidth)
6362
// We need to take more menu width into account when calculating the number of items possible
@@ -91,19 +90,20 @@ const overflowEffect = (
9190
for (const [index, child] of childArray.entries()) {
9291
if (index < numberOfListItems) {
9392
items.push(child)
94-
// We need to make sure to keep the selected item always visible.
95-
} else if (child.props.selected) {
96-
// If selected item can't make it to the list, we swap it with the last item in the list.
97-
const indexToReplaceAt = numberOfListItems - 1 // because we are replacing the last item in the list
98-
// splice method modifies the array by removing 1 item here at the given index and replace it with the "child" element then returns the removed item.
99-
const propsectiveAction = items.splice(indexToReplaceAt, 1, child)[0]
100-
actions.push(propsectiveAction)
10193
} else {
102-
actions.push(child)
94+
// We need to make sure to keep the selected item always visible.
95+
if (child.props.selected) {
96+
// If selected item couldn't make in to the list, we swap it with the last item in the list.
97+
const indexToReplaceAt = numberOfListItems - 1 // because we are replacing the last item in the list
98+
// splice method modifies the array by removing 1 item here at the given index and replace it with the "child" element then returns the removed item.
99+
const propsectiveAction = items.splice(indexToReplaceAt, 1, child)[0]
100+
actions.push(propsectiveAction)
101+
} else {
102+
actions.push(child)
103+
}
103104
}
104105
}
105106
}
106-
107107
updateListAndMenu({items, actions}, iconsVisible)
108108
}
109109

@@ -124,7 +124,6 @@ const calculatePossibleItems = (childWidthArray: ChildWidthArray, navWidth: numb
124124
sumsOfChildWidth = sumsOfChildWidth + childWidth.width + GAP
125125
}
126126
}
127-
128127
return breakpoint
129128
}
130129

@@ -247,7 +246,8 @@ export const UnderlineNav = forwardRef(
247246
const childArray = getValidChildren(children)
248247
const navWidth = resizeObserverEntries[0].contentRect.width
249248
const moreMenuWidth = moreMenuRef.current?.getBoundingClientRect().width ?? 0
250-
overflowEffect(navWidth, moreMenuWidth, childArray, childWidthArray, noIconChildWidthArray, updateListAndMenu)
249+
navWidth !== 0 &&
250+
overflowEffect(navWidth, moreMenuWidth, childArray, childWidthArray, noIconChildWidthArray, updateListAndMenu)
251251
}, navRef as RefObject<HTMLElement>)
252252

253253
if (!ariaLabel) {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from 'react'
2+
import {Meta, Story} from '@storybook/react'
3+
import {UnderlineNav} from './index'
4+
import {UnderlineNavItem} from './UnderlineNavItem'
5+
6+
const excludedControlKeys = ['sx', 'as', 'variant', 'align', 'afterSelect']
7+
8+
export default {
9+
title: 'Drafts/Components/UnderlineNav',
10+
component: UnderlineNav,
11+
subcomponents: {UnderlineNavItem},
12+
parameters: {
13+
controls: {
14+
expanded: true,
15+
// variant and size are developed in the first design iteration but then they are abondened.
16+
// Still keeping them on the source code for future reference but they are not exposed as props.
17+
exclude: excludedControlKeys
18+
}
19+
},
20+
argTypes: {
21+
'aria-label': {
22+
type: {
23+
name: 'string'
24+
}
25+
},
26+
loadingCounters: {
27+
control: {
28+
type: 'boolean'
29+
}
30+
}
31+
},
32+
args: {
33+
'aria-label': 'Repository',
34+
loadingCounters: false
35+
}
36+
} as Meta<typeof UnderlineNav>
37+
38+
export const Playground: Story = args => {
39+
const children = ['Code', 'Pull requests', 'Actions', 'Projects', 'Wiki']
40+
return (
41+
<UnderlineNav {...args}>
42+
{children.map((child: string, index: number) => (
43+
<UnderlineNavItem key={index} href="#" selected={index === 0}>
44+
{child}
45+
</UnderlineNavItem>
46+
))}
47+
</UnderlineNav>
48+
)
49+
}
Lines changed: 122 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React from 'react'
22
import {
33
IconProps,
4-
EyeIcon,
54
CodeIcon,
65
IssueOpenedIcon,
76
GitPullRequestIcon,
@@ -10,65 +9,58 @@ import {
109
ProjectIcon,
1110
GraphIcon,
1211
ShieldLockIcon,
13-
GearIcon
12+
GearIcon,
13+
CommitIcon,
14+
ChecklistIcon,
15+
FileDiffIcon,
16+
BookIcon,
17+
RepoIcon,
18+
PackageIcon,
19+
StarIcon,
20+
ThreeBarsIcon,
21+
PeopleIcon
1422
} from '@primer/octicons-react'
1523
import {Meta} from '@storybook/react'
1624
import {UnderlineNav} from './index'
17-
import {BaseStyles, ThemeProvider} from '..'
25+
import {Avatar, StyledOcticon, Button, Box, Heading, Link, Text, StateLabel, BranchName} from '..'
1826

1927
export default {
20-
title: 'Components/UnderlineNav',
21-
decorators: [
22-
Story => {
23-
return (
24-
<ThemeProvider>
25-
<BaseStyles>
26-
<Story />
27-
</BaseStyles>
28-
</ThemeProvider>
29-
)
30-
}
31-
]
28+
title: 'Drafts/Components/UnderlineNav/Examples'
3229
} as Meta
3330

34-
export const DefaultNav = () => {
31+
export const PullRequestPage = () => {
3532
return (
36-
<UnderlineNav aria-label="Repository">
37-
<UnderlineNav.Item selected>Code</UnderlineNav.Item>
38-
<UnderlineNav.Item>Issues</UnderlineNav.Item>
39-
<UnderlineNav.Item>Pull Requests</UnderlineNav.Item>
40-
</UnderlineNav>
41-
)
42-
}
43-
44-
export const withIcons = () => {
45-
return (
46-
<UnderlineNav aria-label="Repository with icons">
47-
<UnderlineNav.Item icon={CodeIcon}>Code</UnderlineNav.Item>
48-
<UnderlineNav.Item icon={EyeIcon} counter={6}>
49-
Issues
50-
</UnderlineNav.Item>
51-
<UnderlineNav.Item selected icon={GitPullRequestIcon}>
52-
Pull Requests
53-
</UnderlineNav.Item>
54-
<UnderlineNav.Item icon={CommentDiscussionIcon} counter={7}>
55-
Discussions
56-
</UnderlineNav.Item>
57-
<UnderlineNav.Item icon={ProjectIcon}>Projects</UnderlineNav.Item>
58-
</UnderlineNav>
59-
)
60-
}
61-
62-
export const withCounterLabels = () => {
63-
return (
64-
<UnderlineNav aria-label="Repository with counters">
65-
<UnderlineNav.Item selected icon={CodeIcon}>
66-
Code
67-
</UnderlineNav.Item>
68-
<UnderlineNav.Item icon={IssueOpenedIcon} counter={12}>
69-
Issues
70-
</UnderlineNav.Item>
71-
</UnderlineNav>
33+
<Box sx={{display: 'flex', flexDirection: 'column', gap: 3}}>
34+
<Box>
35+
<Heading as="h1" sx={{fontWeight: 'normal'}}>
36+
Switch to new UnderlineNav <Text sx={{color: 'fg.muted', fontWeight: 'light'}}>#1111</Text>
37+
</Heading>
38+
<Box sx={{display: 'flex', gap: 2, alignItems: 'center'}}>
39+
<StateLabel status="pullOpened">Open</StateLabel>
40+
<Text sx={{fontSize: 1, color: 'fg.muted'}}>
41+
<Link href="#" muted sx={{fontWeight: 'bold'}}>
42+
broccolinisoup
43+
</Link>{' '}
44+
wants to merge 3 commits into <BranchName href="#">main</BranchName> from{' '}
45+
<BranchName href="#">broccolinisoup/switch-to-new-underlineNav</BranchName>
46+
</Text>
47+
</Box>
48+
</Box>
49+
<UnderlineNav aria-label="Pull Request">
50+
<UnderlineNav.Item icon={CommentDiscussionIcon} counter="0" selected>
51+
Conversation
52+
</UnderlineNav.Item>
53+
<UnderlineNav.Item counter={3} icon={CommitIcon}>
54+
Commits
55+
</UnderlineNav.Item>
56+
<UnderlineNav.Item counter={7} icon={ChecklistIcon}>
57+
Checks
58+
</UnderlineNav.Item>
59+
<UnderlineNav.Item counter={4} icon={FileDiffIcon}>
60+
Files Changes
61+
</UnderlineNav.Item>
62+
</UnderlineNav>
63+
</Box>
7264
)
7365
}
7466

@@ -84,7 +76,7 @@ const items: {navigation: string; icon: React.FC<IconProps>; counter?: number |
8476
{navigation: 'Security', icon: ShieldLockIcon, href: '#security'}
8577
]
8678

87-
export const InternalResponsiveNav = () => {
79+
export const ReposPage = () => {
8880
const [selectedIndex, setSelectedIndex] = React.useState<number | null>(1)
8981

9082
return (
@@ -94,7 +86,10 @@ export const InternalResponsiveNav = () => {
9486
key={item.navigation}
9587
icon={item.icon}
9688
selected={index === selectedIndex}
97-
onSelect={() => setSelectedIndex(index)}
89+
onSelect={event => {
90+
event.preventDefault()
91+
setSelectedIndex(index)
92+
}}
9893
counter={item.counter}
9994
href={item.href}
10095
>
@@ -105,22 +100,81 @@ export const InternalResponsiveNav = () => {
105100
)
106101
}
107102

108-
export const CountersLoadingState = () => {
109-
const [selectedIndex, setSelectedIndex] = React.useState<number | null>(1)
103+
const profileItems: {navigation: string; icon: React.FC<IconProps>; counter?: number | string; href?: string}[] = [
104+
{navigation: 'Overview', icon: BookIcon, href: '#overview'},
105+
{navigation: 'Repositories', icon: RepoIcon, counter: '12', href: '#repositories'},
106+
{navigation: 'Projects', icon: ProjectIcon, counter: 3, href: '#projects'},
107+
{navigation: 'Packages', icon: PackageIcon, counter: '0', href: '#packages'},
108+
{navigation: 'Stars', icon: StarIcon, counter: '0', href: '#stars'},
109+
{navigation: 'Activity', icon: ThreeBarsIcon, counter: 67, href: '#activity'}
110+
]
110111

112+
export const ProfilePage = () => {
113+
const [selectedIndex, setSelectedIndex] = React.useState<number | null>(1)
111114
return (
112-
<UnderlineNav aria-label="Repository with loading counters" loadingCounters={true}>
113-
{items.map((item, index) => (
114-
<UnderlineNav.Item
115-
key={item.navigation}
116-
icon={item.icon}
117-
selected={index === selectedIndex}
118-
onSelect={() => setSelectedIndex(index)}
119-
counter={item.counter}
115+
<Box sx={{display: 'flex', flexDirection: 'row', gap: 3, alignItems: 'flex-start'}}>
116+
<Box sx={{display: 'flex', flexDirection: 'column', alignItems: 'flex-start', height: '100%'}}>
117+
<Avatar size={256} src="https://avatars.githubusercontent.com/u/92997159?v=4" alt="mona user avatar" />
118+
<Box>
119+
{/* Initial bio info */}
120+
<Box sx={{paddingY: 3}}>
121+
<Heading as="h1" sx={{fontSize: 24}}>
122+
Monalisa Octocat
123+
</Heading>
124+
<Heading as="h1" sx={{fontSize: 20, fontWeight: 300, color: 'fg.subtle'}}>
125+
mona
126+
</Heading>
127+
</Box>
128+
129+
{/* Edit Profile / Profile details */}
130+
<Box sx={{display: 'flex', flexDirection: 'column', color: 'fg.onEmphasis'}}>
131+
<Button sx={{width: '100%'}}>Edit Profile</Button>
132+
133+
<Box sx={{display: 'flex', flexDirection: 'row', alignItems: 'center', marginTop: 3}}>
134+
<StyledOcticon icon={PeopleIcon} size={16} color="fg.subtle" sx={{marginRight: 1}} />
135+
<Link href="https://github.com" muted sx={{marginRight: 2}}>
136+
47 Followers
137+
</Link>
138+
<span> · </span>
139+
<Link href="https://github.com" muted sx={{marginLeft: 2}}>
140+
54 Following
141+
</Link>
142+
</Box>
143+
</Box>
144+
</Box>
145+
</Box>
146+
<Box sx={{flexGrow: 1}}>
147+
<UnderlineNav aria-label="Repository">
148+
{profileItems.map((item, index) => (
149+
<UnderlineNav.Item
150+
key={item.navigation}
151+
icon={item.icon}
152+
selected={index === selectedIndex}
153+
onSelect={event => {
154+
event.preventDefault()
155+
setSelectedIndex(index)
156+
}}
157+
counter={item.counter}
158+
href={item.href}
159+
>
160+
{item.navigation}
161+
</UnderlineNav.Item>
162+
))}
163+
</UnderlineNav>
164+
<Box
165+
sx={{
166+
border: '1px solid',
167+
marginTop: 2,
168+
borderColor: 'border.default',
169+
borderRadius: '12px',
170+
height: '300px',
171+
width: '80%',
172+
padding: 4
173+
}}
120174
>
121-
{item.navigation}
122-
</UnderlineNav.Item>
123-
))}
124-
</UnderlineNav>
175+
<Text color="fg.subtle"> mona/README.md</Text>
176+
</Box>
177+
</Box>
178+
</Box>
125179
)
126180
}

0 commit comments

Comments
 (0)