Skip to content

Commit

Permalink
[UnderlineNav2]: Follow new storybook documentation format & improvem…
Browse files Browse the repository at this point in the history
…ents (#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
  • Loading branch information
broccolinisoup committed Nov 8, 2022
1 parent 8c764f6 commit e2a2d78
Show file tree
Hide file tree
Showing 8 changed files with 510 additions and 105 deletions.
5 changes: 5 additions & 0 deletions .changeset/flat-cars-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': patch
---

UnderlineNav2: Only run overflow layout function when nav item has a width
52 changes: 52 additions & 0 deletions src/UnderlineNav2/UnderlineNav.Item.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react'
import {Meta, Story} from '@storybook/react'
import {UnderlineNav} from './index'
import {UnderlineNavItem} from './UnderlineNavItem'
import {CodeIcon, GitPullRequestIcon, PeopleIcon} from '@primer/octicons-react'
import {OcticonArgType} from '../utils/story-helpers'

export default {
title: 'Drafts/Components/UnderlineNav/UnderlineNav.Item',
component: UnderlineNavItem,
decorators: [
Story => {
return (
<UnderlineNav aria-label="Repository">
<Story />
</UnderlineNav>
)
}
],
parameters: {
controls: {
expanded: true,
exclude: ['as']
}
},
args: {
children: 'Code',
counter: '12K',
icon: PeopleIcon
},
argTypes: {
children: {
type: 'string'
},
counter: {
type: 'string'
},
icon: OcticonArgType([CodeIcon, GitPullRequestIcon, PeopleIcon])
}
} as Meta<typeof UnderlineNavItem>

// UnderlineNav.Item controls only work on the "Docs" tab. Because UnderlineNav children don't get re-rendered when they are changed.
// This is an intentional behaviour of UnderlineNav for keeping a selected menu item visible. I will update here once I find a better solution.
// In the meantime, you can use the "Docs" tab to see the controls.

export const Playground: Story = args => {
return (
<UnderlineNavItem selected {...args}>
{args.children}
</UnderlineNavItem>
)
}
24 changes: 12 additions & 12 deletions src/UnderlineNav2/UnderlineNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ const overflowEffect = (
if (childWidthArray.length === 0) {
updateListAndMenu({items: childArray, actions: []}, iconsVisible)
}

const numberOfItemsPossible = calculatePossibleItems(childWidthArray, navWidth)
const numberOfItemsWithoutIconPossible = calculatePossibleItems(noIconChildWidthArray, navWidth)
// We need to take more menu width into account when calculating the number of items possible
Expand Down Expand Up @@ -91,19 +90,20 @@ const overflowEffect = (
for (const [index, child] of childArray.entries()) {
if (index < numberOfListItems) {
items.push(child)
// We need to make sure to keep the selected item always visible.
} else if (child.props.selected) {
// If selected item can't make it to the list, we swap it with the last item in the list.
const indexToReplaceAt = numberOfListItems - 1 // because we are replacing the last item in the list
// 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.
const propsectiveAction = items.splice(indexToReplaceAt, 1, child)[0]
actions.push(propsectiveAction)
} else {
actions.push(child)
// We need to make sure to keep the selected item always visible.
if (child.props.selected) {
// If selected item couldn't make in to the list, we swap it with the last item in the list.
const indexToReplaceAt = numberOfListItems - 1 // because we are replacing the last item in the list
// 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.
const propsectiveAction = items.splice(indexToReplaceAt, 1, child)[0]
actions.push(propsectiveAction)
} else {
actions.push(child)
}
}
}
}

updateListAndMenu({items, actions}, iconsVisible)
}

Expand All @@ -124,7 +124,6 @@ const calculatePossibleItems = (childWidthArray: ChildWidthArray, navWidth: numb
sumsOfChildWidth = sumsOfChildWidth + childWidth.width + GAP
}
}

return breakpoint
}

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

if (!ariaLabel) {
Expand Down
49 changes: 49 additions & 0 deletions src/UnderlineNav2/UnderlineNav2.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react'
import {Meta, Story} from '@storybook/react'
import {UnderlineNav} from './index'
import {UnderlineNavItem} from './UnderlineNavItem'

const excludedControlKeys = ['sx', 'as', 'variant', 'align', 'afterSelect']

export default {
title: 'Drafts/Components/UnderlineNav',
component: UnderlineNav,
subcomponents: {UnderlineNavItem},
parameters: {
controls: {
expanded: true,
// variant and size are developed in the first design iteration but then they are abondened.
// Still keeping them on the source code for future reference but they are not exposed as props.
exclude: excludedControlKeys
}
},
argTypes: {
'aria-label': {
type: {
name: 'string'
}
},
loadingCounters: {
control: {
type: 'boolean'
}
}
},
args: {
'aria-label': 'Repository',
loadingCounters: false
}
} as Meta<typeof UnderlineNav>

export const Playground: Story = args => {
const children = ['Code', 'Pull requests', 'Actions', 'Projects', 'Wiki']
return (
<UnderlineNav {...args}>
{children.map((child: string, index: number) => (
<UnderlineNavItem key={index} href="#" selected={index === 0}>
{child}
</UnderlineNavItem>
))}
</UnderlineNav>
)
}
190 changes: 122 additions & 68 deletions src/UnderlineNav2/examples.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react'
import {
IconProps,
EyeIcon,
CodeIcon,
IssueOpenedIcon,
GitPullRequestIcon,
Expand All @@ -10,65 +9,58 @@ import {
ProjectIcon,
GraphIcon,
ShieldLockIcon,
GearIcon
GearIcon,
CommitIcon,
ChecklistIcon,
FileDiffIcon,
BookIcon,
RepoIcon,
PackageIcon,
StarIcon,
ThreeBarsIcon,
PeopleIcon
} from '@primer/octicons-react'
import {Meta} from '@storybook/react'
import {UnderlineNav} from './index'
import {BaseStyles, ThemeProvider} from '..'
import {Avatar, StyledOcticon, Button, Box, Heading, Link, Text, StateLabel, BranchName} from '..'

export default {
title: 'Components/UnderlineNav',
decorators: [
Story => {
return (
<ThemeProvider>
<BaseStyles>
<Story />
</BaseStyles>
</ThemeProvider>
)
}
]
title: 'Drafts/Components/UnderlineNav/Examples'
} as Meta

export const DefaultNav = () => {
export const PullRequestPage = () => {
return (
<UnderlineNav aria-label="Repository">
<UnderlineNav.Item selected>Code</UnderlineNav.Item>
<UnderlineNav.Item>Issues</UnderlineNav.Item>
<UnderlineNav.Item>Pull Requests</UnderlineNav.Item>
</UnderlineNav>
)
}

export const withIcons = () => {
return (
<UnderlineNav aria-label="Repository with icons">
<UnderlineNav.Item icon={CodeIcon}>Code</UnderlineNav.Item>
<UnderlineNav.Item icon={EyeIcon} counter={6}>
Issues
</UnderlineNav.Item>
<UnderlineNav.Item selected icon={GitPullRequestIcon}>
Pull Requests
</UnderlineNav.Item>
<UnderlineNav.Item icon={CommentDiscussionIcon} counter={7}>
Discussions
</UnderlineNav.Item>
<UnderlineNav.Item icon={ProjectIcon}>Projects</UnderlineNav.Item>
</UnderlineNav>
)
}

export const withCounterLabels = () => {
return (
<UnderlineNav aria-label="Repository with counters">
<UnderlineNav.Item selected icon={CodeIcon}>
Code
</UnderlineNav.Item>
<UnderlineNav.Item icon={IssueOpenedIcon} counter={12}>
Issues
</UnderlineNav.Item>
</UnderlineNav>
<Box sx={{display: 'flex', flexDirection: 'column', gap: 3}}>
<Box>
<Heading as="h1" sx={{fontWeight: 'normal'}}>
Switch to new UnderlineNav <Text sx={{color: 'fg.muted', fontWeight: 'light'}}>#1111</Text>
</Heading>
<Box sx={{display: 'flex', gap: 2, alignItems: 'center'}}>
<StateLabel status="pullOpened">Open</StateLabel>
<Text sx={{fontSize: 1, color: 'fg.muted'}}>
<Link href="#" muted sx={{fontWeight: 'bold'}}>
broccolinisoup
</Link>{' '}
wants to merge 3 commits into <BranchName href="#">main</BranchName> from{' '}
<BranchName href="#">broccolinisoup/switch-to-new-underlineNav</BranchName>
</Text>
</Box>
</Box>
<UnderlineNav aria-label="Pull Request">
<UnderlineNav.Item icon={CommentDiscussionIcon} counter="0" selected>
Conversation
</UnderlineNav.Item>
<UnderlineNav.Item counter={3} icon={CommitIcon}>
Commits
</UnderlineNav.Item>
<UnderlineNav.Item counter={7} icon={ChecklistIcon}>
Checks
</UnderlineNav.Item>
<UnderlineNav.Item counter={4} icon={FileDiffIcon}>
Files Changes
</UnderlineNav.Item>
</UnderlineNav>
</Box>
)
}

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

export const InternalResponsiveNav = () => {
export const ReposPage = () => {
const [selectedIndex, setSelectedIndex] = React.useState<number | null>(1)

return (
Expand All @@ -94,7 +86,10 @@ export const InternalResponsiveNav = () => {
key={item.navigation}
icon={item.icon}
selected={index === selectedIndex}
onSelect={() => setSelectedIndex(index)}
onSelect={event => {
event.preventDefault()
setSelectedIndex(index)
}}
counter={item.counter}
href={item.href}
>
Expand All @@ -105,22 +100,81 @@ export const InternalResponsiveNav = () => {
)
}

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

export const ProfilePage = () => {
const [selectedIndex, setSelectedIndex] = React.useState<number | null>(1)
return (
<UnderlineNav aria-label="Repository with loading counters" loadingCounters={true}>
{items.map((item, index) => (
<UnderlineNav.Item
key={item.navigation}
icon={item.icon}
selected={index === selectedIndex}
onSelect={() => setSelectedIndex(index)}
counter={item.counter}
<Box sx={{display: 'flex', flexDirection: 'row', gap: 3, alignItems: 'flex-start'}}>
<Box sx={{display: 'flex', flexDirection: 'column', alignItems: 'flex-start', height: '100%'}}>
<Avatar size={256} src="https://avatars.githubusercontent.com/u/92997159?v=4" alt="mona user avatar" />
<Box>
{/* Initial bio info */}
<Box sx={{paddingY: 3}}>
<Heading as="h1" sx={{fontSize: 24}}>
Monalisa Octocat
</Heading>
<Heading as="h1" sx={{fontSize: 20, fontWeight: 300, color: 'fg.subtle'}}>
mona
</Heading>
</Box>

{/* Edit Profile / Profile details */}
<Box sx={{display: 'flex', flexDirection: 'column', color: 'fg.onEmphasis'}}>
<Button sx={{width: '100%'}}>Edit Profile</Button>

<Box sx={{display: 'flex', flexDirection: 'row', alignItems: 'center', marginTop: 3}}>
<StyledOcticon icon={PeopleIcon} size={16} color="fg.subtle" sx={{marginRight: 1}} />
<Link href="https://github.com" muted sx={{marginRight: 2}}>
47 Followers
</Link>
<span> · </span>
<Link href="https://github.com" muted sx={{marginLeft: 2}}>
54 Following
</Link>
</Box>
</Box>
</Box>
</Box>
<Box sx={{flexGrow: 1}}>
<UnderlineNav aria-label="Repository">
{profileItems.map((item, index) => (
<UnderlineNav.Item
key={item.navigation}
icon={item.icon}
selected={index === selectedIndex}
onSelect={event => {
event.preventDefault()
setSelectedIndex(index)
}}
counter={item.counter}
href={item.href}
>
{item.navigation}
</UnderlineNav.Item>
))}
</UnderlineNav>
<Box
sx={{
border: '1px solid',
marginTop: 2,
borderColor: 'border.default',
borderRadius: '12px',
height: '300px',
width: '80%',
padding: 4
}}
>
{item.navigation}
</UnderlineNav.Item>
))}
</UnderlineNav>
<Text color="fg.subtle"> mona/README.md</Text>
</Box>
</Box>
</Box>
)
}
Loading

0 comments on commit e2a2d78

Please sign in to comment.