Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Nav component with some Storybook configuration #290

Merged
merged 17 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export const parameters = {
<Stories />
</>
),
canvas: {
sourceState: 'shown',
},
},
options: {
storySort: {
Expand Down
64 changes: 64 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@radix-ui/react-checkbox": "^1.0.0",
"@radix-ui/react-dialog": "^1.0.0",
"@radix-ui/react-dropdown-menu": "^1.0.1-rc.6",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-switch": "^1.0.0",
"@radix-ui/react-tabs": "^1.0.0",
"@radix-ui/react-tooltip": "^1.0.0",
Expand Down
2 changes: 1 addition & 1 deletion src/system/Link/Link.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import type { StoryObj } from '@storybook/react';
import { Link } from '..';

export default {
title: 'Navigation/Link',
component: Link,
title: 'Link',
};

type Story = StoryObj< typeof Link >;
Expand Down
117 changes: 117 additions & 0 deletions src/system/Nav/Nav.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* External dependencies
*/
import type { StoryObj } from '@storybook/react';

/**
* Internal dependencies
*/
import { Nav, NavItem } from '../../system';

export default {
title: 'Navigation/Nav',
component: Nav,
parameters: {
docs: {
description: {
component: `
A navigation menu is a list of links used to navigate a website. It is usually placed in a prominent position at the top of a site, or anywhere that needs a linked-navigation.

## Guidance

### When to use the Nav component

- To link internal or external links in a menu format.
- To link to pages that are not part of the main navigation.

### When to consider something else

- If you have content inside the same page that will not affect the page Route/URL, use [Tabs](/docs/tabs--docs) component instead.
- If you are planning to have buttons in your navigation, use another navigation solution, for example: [Dropdown](/docs/dropdown--docs) component instead.

## Accessibility Considerations guidance

This component is based on the Radix Navigation Menu primitive, so it contains all the accessibility features from the primitive.

- Adheres to the [navigation role requirements.](https://www.w3.org/TR/wai-aria-1.2/#navigation)
- Keyboard Interactions: https://www.radix-ui.com/primitives/docs/components/navigation-menu#keyboard-interactions

### Usability guidance

Pick one of the available variants: Primary or Tabs. You can use the components directly from the \`Nav\` component:

~~~jsx filename="index.jsx"
import { Nav, NavItem } from '@automattic/components';

<Nav.Primary> or <Nav.Tab>
<NavItem.Primary> or <NavItem.Tab>
~~~

### Usage with Next.js framwork

~~~jsx filename="index.jsx"
import Link from 'next/link';

<Nav.Primary label="Etc">
<NavItem.Primary
active
href="https://google.com"
asChild // This is important to pass the link styles to the child
>
<Link href={ \`/orgs/\${ id }/sso/configurations/\${ idP }/edit/\${ tab.path }\` }>
Your page name
</Link>
</NavItem.Primary>
</Nav.Primary>
~~~

-------

## Component Properties
`,
},
},
},
};

type Story = StoryObj< typeof Nav >;

export const Default: Story = {
render: () => (
<>
<p>
<strong>Variant: Primary</strong>
</p>
<Nav.Primary sx={ { mb: 4 } } label="Nav Primary">
<NavItem.Primary active href="#">
PHP
</NavItem.Primary>
<NavItem.Primary href="https://wordpress.com">WordPress</NavItem.Primary>
<NavItem.Primary href="htpps://newrelic.com/">New Relic</NavItem.Primary>
<NavItem.Primary disabled href="https://google.com/">
Not accessible
</NavItem.Primary>
</Nav.Primary>
</>
),
};

export const Tab: Story = {
render: () => (
<>
<p>
<strong>Variant: Tab</strong>
</p>
<Nav.Tab sx={ { mb: 4 } } label="Nav Tab">
<NavItem.Tab active href="#">
PHP
</NavItem.Tab>
<NavItem.Tab href="https://wordpress.com">WordPress</NavItem.Tab>
<NavItem.Tab href="htpps://newrelic.com/">New Relic</NavItem.Tab>
<NavItem.Tab disabled href="https://google.com/">
Not accessible
</NavItem.Tab>
</Nav.Tab>
</>
),
};
51 changes: 51 additions & 0 deletions src/system/Nav/Nav.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/** @jsxImportSource theme-ui */
/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
/**
* External dependencies
*/
import { render, screen } from '@testing-library/react';
import { axe } from 'jest-axe';
import { ThemeUIProvider } from 'theme-ui';

/**
* Internal dependencies
*/
import { Nav, NavItem } from './';

import { theme } from '../';

const renderWithTheme = children =>
render( <ThemeUIProvider theme={ theme }>{ children }</ThemeUIProvider> );

const renderComponent = () =>
renderWithTheme(
<Nav.Primary variant="primary" label="Main">
<NavItem.Primary href="#">PHP</NavItem.Primary>
<NavItem.Primary href="https://wordpress.com">WordPress</NavItem.Primary>
<NavItem.Primary active href="htpps://newrelic.com/">
New Relic
</NavItem.Primary>
<NavItem.Primary disabled href="https://google.com/">
Not accessible
</NavItem.Primary>
</Nav.Primary>
);

describe( '<Nav />', () => {
it( 'renders the Nav component with default value visible', async () => {
const { container } = renderComponent();

// Should find the nav label
expect( screen.getByLabelText( 'Main' ) ).toBeInTheDocument();

// Should find all links
expect( screen.queryByText( 'PHP' ) ).toBeInTheDocument();
expect( screen.queryByText( 'WordPress' ) ).toBeInTheDocument();
expect( screen.queryByText( 'New Relic' ) ).toBeInTheDocument();
expect( screen.queryByText( 'Not accessible' ) ).toHaveAttribute( 'aria-disabled', 'true' );

// Check for accessibility issues
expect( await axe( container ) ).toHaveNoViolations();
} );
} );
62 changes: 62 additions & 0 deletions src/system/Nav/Nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/** @jsxImportSource theme-ui */
chriszarate marked this conversation as resolved.
Show resolved Hide resolved
import React, { Ref, forwardRef } from 'react';
import * as NavigationMenu from '@radix-ui/react-navigation-menu';
import classNames from 'classnames';

import { VIP_NAV } from '.';
import { ThemeUIStyleObject } from 'theme-ui';

export type NavVariant = 'primary' | 'tabs';

export interface NavProps extends NavigationMenu.NavigationMenuProps {
className?: string;
variant?: NavVariant;
sx?: ThemeUIStyleObject;
label: string;
}

const Nav = forwardRef< HTMLElement, NavProps >(
(
{ className, children, orientation, variant = 'primary', sx = {}, label }: NavProps,
ref: Ref< HTMLElement >
) => (
<NavigationMenu.Root
aria-label={ label }
ref={ ref }
className={ classNames( VIP_NAV, className ) }
sx={ {
position: 'relative',
display: 'flex',
width: variant === 'tabs' ? '100%' : 'max-content',
zIndex: 1,
pb: 0,
borderBottom: '1px solid',
borderColor: variant === 'tabs' ? 'borders.2' : 'transparent',
...sx,
} }
orientation={ orientation }
>
<NavigationMenu.List
className={ classNames( `${ VIP_NAV }-list` ) }
sx={ {
display: 'flex',
listStyle: 'none',
justifyContent: 'flex-start',
m: 0,
px: 0,
flexDirection: 'row',
} }
>
{ children }
</NavigationMenu.List>
</NavigationMenu.Root>
)
);

export const NavPrimary = forwardRef< HTMLElement, NavProps >(
( props: NavProps, ref: Ref< HTMLElement > ) => <Nav { ...props } variant="primary" ref={ ref } />
);

export const NavTab = forwardRef< HTMLElement, NavProps >(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like you only need NavPrimary and NavTabs since that is the only thing variant is used for.

( props: NavProps, ref: Ref< HTMLElement > ) => <Nav { ...props } variant="tabs" ref={ ref } />
);
Loading