Skip to content

Commit

Permalink
Add expand to NavList (#4686)
Browse files Browse the repository at this point in the history
* Add expand to `NavList`

* Improved semantics

* Remove styles

* Add extra story

* Add unit test coverage

* Add Group unit test coverage

* Add e2e test coverage

* Add expanded to groups

* Fix import

* Change structure

* Update stories, add ref

* Update tests, add focus target

* Add changeset

* Remove `GroupContent`

* Update to use context

* Add useRef usage

* Change name to `NavList.ShowMoreItem`

* Update docs

* test(vrt): update snapshots

* Update .changeset/many-rivers-deny.md

Co-authored-by: Josh Black <[email protected]>

* Address some feedback

* Memoize id value

* Update w/ new prop `Pages` based on suggestion from Primer patterns

* test(vrt): update snapshots

* Remove story

* Remove story test

* Remove the correct story

* Add condition for css modules feature flag

* Update tests, docs, add ternary

* Only allow for `Item` in `ShowMoreItem`

* Fix lint issue

* Add dependencies

* Remove comment

* Add data API

* Some clean up for `NavList`

* More clean up

* Use `useMemo` instead

* Update docs

* Fix tests

* Remove auto-import

* Add `key` to story

* PR review feedback

* Type check

* Update packages/react/src/NavList/NavList.tsx

Co-authored-by: Josh Black <[email protected]>

---------

Co-authored-by: Kate Higa <[email protected]>
Co-authored-by: TylerJDev <[email protected]>
Co-authored-by: Josh Black <[email protected]>
  • Loading branch information
4 people authored Feb 24, 2025
1 parent a93a3fc commit 6b137a4
Show file tree
Hide file tree
Showing 25 changed files with 685 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/many-rivers-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Add `NavList.ShowMoreItem` component to support showing more content within a `NavList`.
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.
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.
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.
56 changes: 56 additions & 0 deletions e2e/components/NavList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,60 @@ test.describe('NavList', () => {
})
}
})

test.describe('With expand', () => {
for (const theme of themes) {
test.describe(theme, () => {
test('default @vrt', async ({page}) => {
await visit(page, {
id: 'components-navlist--with-expand',
globals: {
colorScheme: theme,
},
})

// Default state
expect(await page.screenshot()).toMatchSnapshot(`NavList.With expand.${theme}.png`)
})

test('axe @aat', async ({page}) => {
await visit(page, {
id: 'components-navlist--with-expand',
globals: {
colorScheme: theme,
},
})
await expect(page).toHaveNoViolations()
})
})
}
})

test.describe('With group expand', () => {
for (const theme of themes) {
test.describe(theme, () => {
test('default @vrt', async ({page}) => {
await visit(page, {
id: 'components-navlist--with-group-expand',
globals: {
colorScheme: theme,
},
})

// Default state
expect(await page.screenshot()).toMatchSnapshot(`NavList.With group expand.${theme}.png`)
})

test('axe @aat', async ({page}) => {
await visit(page, {
id: 'components-navlist--with-group-expand',
globals: {
colorScheme: theme,
},
})
await expect(page).toHaveNoViolations()
})
})
}
})
})
35 changes: 35 additions & 0 deletions packages/react/src/NavList/NavList.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,41 @@
"description": "href when the TrailingAction is rendered as a link."
}
]
},
{
"name": "NavList.GroupExpand",
"props": [
{
"name": "label",
"type": "string",
"defaultValue": "",
"required": true,
"description": "Acccessible name for the control."
},
{
"name": "pages",
"type": "number",
"defaultValue": "0",
"required": false,
"description": "The total number of pages, used to calculate the number of items to show at a given time."
},
{
"name": "items",
"type": "Array<GroupItems>",
"required": true,
"description": "The NavList.Items to render in the group."
},
{
"name": "renderItem",
"type": "(item: {text: string}) => React.ReactNode",
"required": false,
"description": "A function that returns a ReactNode to render in the group. If this is not provided, the group will only render the items in the array."
},
{
"name": "ref",
"type": "React.RefObject<HTMLButtonElement>"
}
]
}
]
}
272 changes: 271 additions & 1 deletion packages/react/src/NavList/NavList.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,25 @@ import type {Meta, StoryFn} from '@storybook/react'
import React from 'react'
import {PageLayout} from '../PageLayout'
import {NavList} from './NavList'
import {ArrowRightIcon, ArrowLeftIcon, BookIcon, FileDirectoryIcon} from '@primer/octicons-react'
import {
type Icon,
ArrowRightIcon,
ArrowLeftIcon,
BookIcon,
FileDirectoryIcon,
CodeIcon,
RepoIcon,
IssueOpenedIcon,
GitPullRequestIcon,
CommentDiscussionIcon,
PeopleIcon,
GitCommitIcon,
PackageIcon,
MilestoneIcon,
TelescopeIcon,
} from '@primer/octicons-react'
import Octicon from '../Octicon'
import VisuallyHidden from '../_VisuallyHidden'

const meta: Meta = {
title: 'Components/NavList',
Expand Down Expand Up @@ -328,4 +346,256 @@ export const WithTrailingActionInSubItem = () => {
)
}

export const WithExpand: StoryFn = () => {
const items = [
{href: '#', text: 'Item 4'},
{href: '#', text: 'Item 5'},
{href: '#', text: 'Item 6'},
{href: '#', text: 'Item 7'},
{href: '#', text: 'Item 8'},
{href: '#', text: 'Item 9'},
]

return (
<PageLayout>
<PageLayout.Pane position="start">
<NavList>
<NavList.Item href="#" aria-current="page">
Item 1
</NavList.Item>
<NavList.Item href="#">Item 2</NavList.Item>
<NavList.Item href="#">Item 3</NavList.Item>
<NavList.GroupExpand label="Show more" items={items} />
</NavList>
</PageLayout.Pane>
<PageLayout.Content></PageLayout.Content>
</PageLayout>
)
}

export const WithExpandAndIcons: StoryFn = () => {
const items = [
{href: '#', text: 'Item 4'},
{href: '#', text: 'Item 5'},
{href: '#', text: 'Item 6'},
{href: '#', text: 'Item 7'},
{href: '#', text: 'Item 8'},
{href: '#', text: 'Item 9'},
]

return (
<PageLayout>
<PageLayout.Pane position="start">
<NavList>
<NavList.Item href="#" aria-current="page">
Item 1
</NavList.Item>
<NavList.Item href="#">Item 2</NavList.Item>
<NavList.Item href="#">Item 3</NavList.Item>
<NavList.GroupExpand label="Show more" items={items} />
</NavList>
</PageLayout.Pane>
<PageLayout.Content></PageLayout.Content>
</PageLayout>
)
}

type CustomItemProps = {
text: string
leadingVisual?: Icon
trailingVisual?: Icon | string
}

export const ExpandWithCustomItems: StoryFn = () => {
const items: {href: string; text: string; 'aria-current'?: 'page'}[] = [
{href: '#', text: 'Item 4', 'aria-current': 'page'},
{href: '#', text: 'Item 5'},
{href: '#', text: 'Item 6'},
{href: '#', text: 'Item 7'},
{href: '#', text: 'Item 8'},
{href: '#', text: 'Item 9'},
]

const Item = ({leadingVisual, text, trailingVisual, ...rest}: CustomItemProps) => {
return (
<NavList.Item key={text} onClick={() => {}} href="#" {...rest}>
{leadingVisual ? (
<NavList.LeadingVisual>
<Octicon icon={leadingVisual} />
</NavList.LeadingVisual>
) : null}
{text}

{trailingVisual ? (
<NavList.TrailingVisual>
{typeof trailingVisual === 'string' ? (
trailingVisual
) : (
<Octicon icon={trailingVisual as React.ElementType} />
)}
<VisuallyHidden>results</VisuallyHidden>
</NavList.TrailingVisual>
) : null}
</NavList.Item>
)
}

return (
<PageLayout>
<PageLayout.Pane position="start">
<NavList>
<NavList.Item href="#">Item 1</NavList.Item>
<NavList.Item href="#">Item 2</NavList.Item>
<NavList.Item href="#">Item 3</NavList.Item>
<NavList.GroupExpand label="Show more" items={items} renderItem={Item} />
</NavList>
</PageLayout.Pane>
<PageLayout.Content></PageLayout.Content>
</PageLayout>
)
}

export const ExpandWithPages: StoryFn = () => {
const items = [
{href: '#', text: 'Item 4'},
{href: '#', text: 'Item 5'},
{href: '#', text: 'Item 6'},
{href: '#', text: 'Item 7'},
{href: '#', text: 'Item 8'},
{href: '#', text: 'Item 9'},
]

return (
<PageLayout>
<PageLayout.Pane position="start">
<NavList>
<NavList.Item href="#" aria-current="page">
Item 1
</NavList.Item>
<NavList.Item href="#">Item 2</NavList.Item>
<NavList.Item href="#">Item 3</NavList.Item>
<NavList.GroupExpand pages={2} label="Show more" items={items} />
</NavList>
</PageLayout.Pane>
<PageLayout.Content></PageLayout.Content>
</PageLayout>
)
}

export const WithGroupExpand = () => {
const items1 = [
{href: '#', text: 'Item 1D'},
{href: '#', text: 'Item 1E', trailingAction: {label: 'Some action', icon: ArrowRightIcon}},
]

const items2 = [
{href: '#', text: 'Item 2D', trailingVisual: BookIcon},
{href: '#', text: 'Item 2E', trailingVisual: FileDirectoryIcon},
]

return (
<PageLayout>
<PageLayout.Pane position="start">
<NavList>
<NavList.Group title="Group 1">
<NavList.Item aria-current="true" href="#">
Item 1A
</NavList.Item>
<NavList.Item href="#">Item 1B</NavList.Item>
<NavList.Item href="#">Item 1C</NavList.Item>
<NavList.GroupExpand label="More" items={items1} />
</NavList.Group>
<NavList.Group title="Group 2">
<NavList.Item href="#">Item 2A</NavList.Item>
<NavList.Item href="#">Item 2B</NavList.Item>
<NavList.Item href="#">Item 2C</NavList.Item>
<NavList.GroupExpand label="Show" items={items2} />
</NavList.Group>
</NavList>
</PageLayout.Pane>
<PageLayout.Content></PageLayout.Content>
</PageLayout>
)
}

export const GroupWithExpandAndCustomItems = () => {
const Item = ({leadingVisual: LeadingVisual, text, trailingVisual: TrailingVisual, ...rest}: CustomItemProps) => {
return (
<NavList.Item onClick={() => {}} href="#" {...rest} key={text}>
{LeadingVisual ? (
<NavList.LeadingVisual>
<LeadingVisual />
</NavList.LeadingVisual>
) : null}
{text}

{TrailingVisual ? (
<NavList.TrailingVisual>
{typeof TrailingVisual === 'string' ? TrailingVisual : <TrailingVisual />}
<VisuallyHidden>results</VisuallyHidden>
</NavList.TrailingVisual>
) : null}
</NavList.Item>
)
}

const items = [
{href: '#', text: 'Commits', leadingVisual: GitCommitIcon, trailingVisual: '32k'},
{href: '#', text: 'Packages', leadingVisual: PackageIcon, trailingVisual: '1k'},
{href: '#', text: 'Wikis', leadingVisual: BookIcon, trailingVisual: '121'},
{href: '#', text: 'Topics', leadingVisual: MilestoneIcon, trailingVisual: '12k'},
{href: '#', text: 'Marketplace', leadingVisual: TelescopeIcon, trailingVisual: ArrowRightIcon},
]

return (
<NavList>
<NavList.Group>
<NavList.Item aria-current="page">
<NavList.LeadingVisual>
<CodeIcon />
</NavList.LeadingVisual>
Code
<NavList.TrailingVisual>3k</NavList.TrailingVisual>
</NavList.Item>
<NavList.Item>
<NavList.LeadingVisual>
<RepoIcon />
</NavList.LeadingVisual>
Repositories
<NavList.TrailingVisual>713</NavList.TrailingVisual>
</NavList.Item>
<NavList.Item>
<NavList.LeadingVisual>
<IssueOpenedIcon />
</NavList.LeadingVisual>
Issues
<NavList.TrailingVisual>6k</NavList.TrailingVisual>
</NavList.Item>
<NavList.Item>
<NavList.LeadingVisual>
<GitPullRequestIcon />
</NavList.LeadingVisual>
Pull requests
<NavList.TrailingVisual>4k</NavList.TrailingVisual>
</NavList.Item>
<NavList.Item>
<NavList.LeadingVisual>
<CommentDiscussionIcon />
</NavList.LeadingVisual>
Discussions
<NavList.TrailingVisual>236</NavList.TrailingVisual>
</NavList.Item>
<NavList.Item>
<NavList.LeadingVisual>
<PeopleIcon />
</NavList.LeadingVisual>
Users
<NavList.TrailingVisual>10k</NavList.TrailingVisual>
</NavList.Item>
<NavList.GroupExpand items={items} renderItem={Item} />
</NavList.Group>
</NavList>
)
}

export default meta
Loading

0 comments on commit 6b137a4

Please sign in to comment.