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 - -``` - -
- ### 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 ( + + ) +}) + +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; + } +} + +
+ +
+`; 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'