diff --git a/.changeset/breezy-cooks-destroy.md b/.changeset/breezy-cooks-destroy.md
new file mode 100644
index 00000000000..ef9811a393b
--- /dev/null
+++ b/.changeset/breezy-cooks-destroy.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": patch
+---
+
+Add draft of `NavList` component
diff --git a/docs/content/NavList.mdx b/docs/content/NavList.mdx
index 298ed0b3473..4521b2a9d31 100644
--- a/docs/content/NavList.mdx
+++ b/docs/content/NavList.mdx
@@ -2,44 +2,30 @@
title: NavList
status: Draft
description: Use nav list to render a vertical list of navigation links.
+source: https://github.com/primer/react/tree/main/src/NavList
---
-Not implemented yet
-
-To render a horizontal list of navigation links, consider using [UnderlineNav](/UnderlineNav).
+```js
+import {NavList} from '@primer/react/drafts'
+```
## Examples
### Simple
-```jsx
+```jsx live drafts
- Dashboard
+ Home
- Pull requests
- Issues
+ About
+ Contact
```
-
- Rendered HTML
-
-```html
-
-
-
-```
-
-
-
### With leading icons
-```jsx
+```jsx live drafts
@@ -62,88 +48,34 @@ To render a horizontal list of navigation links, consider using [UnderlineNav](/
```
-
- Rendered HTML
-
-```html
-
-
-
-```
-
-
-
### With other leading visuals
-```jsx
+```jsx live drafts
- #️⃣
+
+ #️⃣
+
General
- 🙏
+
+ 🙏
+
Q&A
- 🙌
+
+ 🙌
+
Show and tell
```
-
- Rendered HTML
-
-```html
-
-
-
-```
-
-
-
### With trailing visuals
-```jsx
+```jsx live drafts
Inbox
@@ -154,31 +86,13 @@ To render a horizontal list of navigation links, consider using [UnderlineNav](/
```
-
- Rendered HTML
-
-```html
-
-
-
-```
-
-
-
### With a heading
-```jsx
+```jsx live drafts
<>
-
Workflows
+
+ Workflows
+
All workflows
@@ -190,26 +104,9 @@ To render a horizontal list of navigation links, consider using [UnderlineNav](/
>
```
-
- Rendered HTML
-
-```html
-Workflows
-
-
-
-```
-
-
-
### With aria-label
-```jsx
+```jsx live drafts
Overview
@@ -219,24 +116,9 @@ To render a horizontal list of navigation links, consider using [UnderlineNav](/
```
-
- Rendered HTML
-
-```html
-
-
-
-```
-
-
-
### With groups
-```jsx
+```jsx live drafts
@@ -251,34 +133,10 @@ To render a horizontal list of navigation links, consider using [UnderlineNav](/
```
-
- Rendered HTML
-
-```html
-
-
-
- Overview
-
-
-
- Components
-
-
-
-
-```
-
-
-
### With sub-items
+Not implemented yet
+
```jsx
Branches
@@ -325,9 +183,11 @@ If a `NavList.Item` contains a `NavList.SubNav`, the `NavList.Item` will render
### With a divider
-```jsx
+```jsx live drafts
- Dashboard
+
+ Dashboard
+
Pull requests
Issues
@@ -336,26 +196,10 @@ If a `NavList.Item` contains a `NavList.SubNav`, the `NavList.Item` will render
```
-
- Rendered HTML
-
-```html
-
-
-
-```
-
-
-
### With React Router
+Not implemented yet
+
```jsx
import {Link, useMatch, useResolvedPath} from 'react-router-dom'
import {NavList} from '@primer/react'
@@ -383,6 +227,8 @@ function App() {
### With Next.js
+Not implemented yet
+
```jsx
import {useRouter} from 'next/router'
import Link from 'next/link'
@@ -416,8 +262,13 @@ function App() {
-
-
+ MDN
+ }
+ />
### NavList.Item
@@ -506,10 +357,10 @@ function App() {
= ({sx = {}, ...props}) => {
height: '20px', // match height of text row
flexShrink: 0,
color: getVariantStyles(variant, disabled).annotationColor,
- marginLeft: 2
+ marginLeft: 2,
+ fontWeight: 'initial'
},
sx as SxProp
)}
diff --git a/src/NavList/NavList.test.tsx b/src/NavList/NavList.test.tsx
new file mode 100644
index 00000000000..89c25d5d296
--- /dev/null
+++ b/src/NavList/NavList.test.tsx
@@ -0,0 +1,43 @@
+import {render} from '@testing-library/react'
+import React from 'react'
+import {ThemeProvider, SSRProvider} from '..'
+import {NavList} from './NavList'
+
+describe('NavList', () => {
+ it('renders a simple list', () => {
+ const {container} = render(
+
+
+
+
+ Home
+
+ About
+ Contact
+
+
+
+ )
+ expect(container).toMatchSnapshot()
+ })
+
+ it('renders with groups', () => {
+ const {container} = render(
+
+
+
+
+
+ Getting started
+
+
+
+ Avatar
+
+
+
+
+ )
+ expect(container).toMatchSnapshot()
+ })
+})
diff --git a/src/NavList/NavList.tsx b/src/NavList/NavList.tsx
new file mode 100644
index 00000000000..f3329181e8d
--- /dev/null
+++ b/src/NavList/NavList.tsx
@@ -0,0 +1,115 @@
+import React from 'react'
+import {ActionList} from '../ActionList'
+
+// ----------------------------------------------------------------------------
+// NavList
+
+export type NavListProps = {
+ children: React.ReactNode
+ // sx
+} & React.ComponentProps<'nav'>
+
+const Root = React.forwardRef(({children, ...props}, ref) => {
+ return (
+
+ {children}
+
+ )
+})
+
+Root.displayName = 'NavList'
+
+// ----------------------------------------------------------------------------
+// NavList.Item
+
+export type NavListItemProps = {
+ children: React.ReactNode
+ href?: string
+ 'aria-current'?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false' | boolean
+ // sx
+}
+
+const Item = React.forwardRef(
+ ({href, 'aria-current': ariaCurrent, children}, ref) => {
+ return (
+
+ {children}
+
+ )
+ }
+)
+
+// ----------------------------------------------------------------------------
+// NavList.LeadingVisual
+
+const LeadingVisual = ActionList.LeadingVisual
+
+LeadingVisual.displayName = 'NavList.LeadingVisual'
+
+// ----------------------------------------------------------------------------
+// NavList.TrailingVisual
+
+const TrailingVisual = ActionList.TrailingVisual
+
+TrailingVisual.displayName = 'NavList.TrailingVisual'
+
+// ----------------------------------------------------------------------------
+// NavList.Divider
+
+const Divider = ActionList.Divider
+
+Divider.displayName = 'NavList.Divider'
+
+// ----------------------------------------------------------------------------
+// NavList.Group
+
+type NavListGroupProps = React.PropsWithChildren<{
+ children: React.ReactNode
+ title?: string
+}>
+
+// TODO: Dividers between groups
+// TODO: Forward ref
+const Group = ({title, children}: NavListGroupProps) => {
+ return (
+ <>
+ {/* Hide divider if the group is the first item in the list */}
+
+ {children}
+ >
+ )
+}
+
+Group.displayName = 'NavList.Group'
+
+// ----------------------------------------------------------------------------
+// Export
+
+export const NavList = Object.assign(Root, {
+ Item,
+ LeadingVisual,
+ TrailingVisual,
+ Divider,
+ Group
+})
diff --git a/src/NavList/__snapshots__/NavList.test.tsx.snap b/src/NavList/__snapshots__/NavList.test.tsx.snap
new file mode 100644
index 00000000000..d0a6a4e1299
--- /dev/null
+++ b/src/NavList/__snapshots__/NavList.test.tsx.snap
@@ -0,0 +1,582 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`NavList renders a simple list 1`] = `
+.c4 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+ min-width: 0;
+}
+
+.c5 {
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+}
+
+.c0 {
+ margin: 0;
+ padding-inline-start: 0;
+ padding-top: 8px;
+ padding-bottom: 8px;
+}
+
+.c2 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ padding-left: 0;
+ padding-right: 0;
+ font-size: 14px;
+ padding-top: 0;
+ padding-bottom: 0;
+ line-height: 20px;
+ min-height: 5px;
+ margin-left: 8px;
+ margin-right: 8px;
+ border-radius: 6px;
+ -webkit-transition: background 33.333ms linear;
+ transition: background 33.333ms linear;
+ color: #24292f;
+ cursor: pointer;
+}
+
+.c2[aria-disabled] {
+ cursor: not-allowed;
+}
+
+.c2 [data-component="ActionList.Item--DividerContainer"] {
+ position: relative;
+}
+
+.c2 [data-component="ActionList.Item--DividerContainer"]::before {
+ content: " ";
+ display: block;
+ position: absolute;
+ width: 100%;
+ top: -7px;
+ border: 0 solid;
+ border-top-width: 0;
+ border-color: var(--divider-color,transparent);
+}
+
+.c2:not(:first-of-type) {
+ --divider-color: rgba(208,215,222,0.48);
+}
+
+[data-component="ActionList.Divider"] + .c2 {
+ --divider-color: transparent !important;
+}
+
+.c2:hover:not([aria-disabled]),
+.c2:focus:not([aria-disabled]),
+.c2[data-focus-visible-added]:not([aria-disabled]) {
+ --divider-color: transparent;
+}
+
+.c2:hover:not([aria-disabled]) + .c1,
+.c2:focus:not([aria-disabled]) + .c2,
+.c2[data-focus-visible-added] + li {
+ --divider-color: transparent;
+}
+
+.c3 {
+ color: #0969da;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ padding-left: 8px;
+ padding-right: 8px;
+ padding-top: 6px;
+ padding-bottom: 6px;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+ border-radius: 6px;
+ color: inherit;
+ position: relative;
+}
+
+.c3:hover {
+ -webkit-text-decoration: underline;
+ text-decoration: underline;
+}
+
+.c3:is(button) {
+ display: inline-block;
+ padding: 0;
+ font-size: inherit;
+ white-space: nowrap;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ background-color: transparent;
+ border: 0;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+.c3:hover {
+ color: inherit;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+}
+
+.c3[aria-current] {
+ font-weight: 600;
+ background-color: rgba(208,215,222,0.24);
+}
+
+.c3[aria-current]::after {
+ position: absolute;
+ top: calc(50% - 12px);
+ left: -8px;
+ width: 4px;
+ height: 24px;
+ content: "";
+ background-color: #0969da;
+ border-radius: 6px;
+}
+
+@media (hover:hover) and (pointer:fine) {
+ .c2:hover:not([aria-disabled]) {
+ background-color: rgba(208,215,222,0.32);
+ color: #24292f;
+ }
+
+ .c2:focus:not([data-focus-visible-added]) {
+ background-color: rgba(208,215,222,0.24);
+ color: #24292f;
+ outline: none;
+ }
+
+ .c2[data-focus-visible-added] {
+ outline: none;
+ border: 2 solid;
+ box-shadow: 0 0 0 2px #0969da;
+ }
+
+ .c2:active:not([aria-disabled]) {
+ background-color: rgba(208,215,222,0.48);
+ color: #24292f;
+ }
+}
+
+@media (forced-colors:active) {
+ .c2:focus {
+ outline: solid 1px transparent !important;
+ }
+}
+
+
+`;
+
+exports[`NavList renders with groups 1`] = `
+.c8 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+ min-width: 0;
+}
+
+.c9 {
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+}
+
+.c1 {
+ height: 1px;
+ background-color: rgba(208,215,222,0.48);
+ margin-top: calc(8px - 1px);
+ margin-bottom: 8px;
+ list-style: none;
+}
+
+.c1:first-child {
+ display: none;
+}
+
+.c2 {
+ list-style: none;
+}
+
+.c2:not(:first-child) {
+ margin-top: 8px;
+}
+
+.c3 {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ padding-left: 16px;
+ padding-right: 16px;
+ font-size: 12px;
+ font-weight: 600;
+ color: #57606a;
+}
+
+.c4 {
+ padding-inline-start: 0;
+}
+
+.c0 {
+ margin: 0;
+ padding-inline-start: 0;
+ padding-top: 8px;
+ padding-bottom: 8px;
+}
+
+.c6 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ padding-left: 0;
+ padding-right: 0;
+ font-size: 14px;
+ padding-top: 0;
+ padding-bottom: 0;
+ line-height: 20px;
+ min-height: 5px;
+ margin-left: 8px;
+ margin-right: 8px;
+ border-radius: 6px;
+ -webkit-transition: background 33.333ms linear;
+ transition: background 33.333ms linear;
+ color: #24292f;
+ cursor: pointer;
+}
+
+.c6[aria-disabled] {
+ cursor: not-allowed;
+}
+
+.c6 [data-component="ActionList.Item--DividerContainer"] {
+ position: relative;
+}
+
+.c6 [data-component="ActionList.Item--DividerContainer"]::before {
+ content: " ";
+ display: block;
+ position: absolute;
+ width: 100%;
+ top: -7px;
+ border: 0 solid;
+ border-top-width: 0;
+ border-color: var(--divider-color,transparent);
+}
+
+.c6:not(:first-of-type) {
+ --divider-color: rgba(208,215,222,0.48);
+}
+
+[data-component="ActionList.Divider"] + .c6 {
+ --divider-color: transparent !important;
+}
+
+.c6:hover:not([aria-disabled]),
+.c6:focus:not([aria-disabled]),
+.c6[data-focus-visible-added]:not([aria-disabled]) {
+ --divider-color: transparent;
+}
+
+.c6:hover:not([aria-disabled]) + .c5,
+.c6:focus:not([aria-disabled]) + .c6,
+.c6[data-focus-visible-added] + li {
+ --divider-color: transparent;
+}
+
+.c7 {
+ color: #0969da;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ padding-left: 8px;
+ padding-right: 8px;
+ padding-top: 6px;
+ padding-bottom: 6px;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+ border-radius: 6px;
+ color: inherit;
+ position: relative;
+}
+
+.c7:hover {
+ -webkit-text-decoration: underline;
+ text-decoration: underline;
+}
+
+.c7:is(button) {
+ display: inline-block;
+ padding: 0;
+ font-size: inherit;
+ white-space: nowrap;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ background-color: transparent;
+ border: 0;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+.c7:hover {
+ color: inherit;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+}
+
+.c7[aria-current] {
+ font-weight: 600;
+ background-color: rgba(208,215,222,0.24);
+}
+
+.c7[aria-current]::after {
+ position: absolute;
+ top: calc(50% - 12px);
+ left: -8px;
+ width: 4px;
+ height: 24px;
+ content: "";
+ background-color: #0969da;
+ border-radius: 6px;
+}
+
+@media (hover:hover) and (pointer:fine) {
+ .c6:hover:not([aria-disabled]) {
+ background-color: rgba(208,215,222,0.32);
+ color: #24292f;
+ }
+
+ .c6:focus:not([data-focus-visible-added]) {
+ background-color: rgba(208,215,222,0.24);
+ color: #24292f;
+ outline: none;
+ }
+
+ .c6[data-focus-visible-added] {
+ outline: none;
+ border: 2 solid;
+ box-shadow: 0 0 0 2px #0969da;
+ }
+
+ .c6:active:not([aria-disabled]) {
+ background-color: rgba(208,215,222,0.48);
+ color: #24292f;
+ }
+}
+
+@media (forced-colors:active) {
+ .c6:focus {
+ outline: solid 1px transparent !important;
+ }
+}
+
+
+
+
+
+
+
+
+ Overview
+
+
+
+
+
+
+
+
+ Components
+
+
+
+
+
+
+
+`;
diff --git a/src/NavList/index.ts b/src/NavList/index.ts
new file mode 100644
index 00000000000..f27654820fb
--- /dev/null
+++ b/src/NavList/index.ts
@@ -0,0 +1 @@
+export * from './NavList'
diff --git a/src/drafts/index.ts b/src/drafts/index.ts
index 42fe62fe81f..3cc6034face 100644
--- a/src/drafts/index.ts
+++ b/src/drafts/index.ts
@@ -4,3 +4,4 @@
* But, they are published on npm and you can import them for experimentation/feedback.
* example: import {ActionList} from '@primer/react/drafts
*/
+export * from '../NavList'