-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat(web-react): Introduce Navigation #DS-1524
- Loading branch information
Showing
29 changed files
with
776 additions
and
9 deletions.
There are no files selected for viewing
22 changes: 22 additions & 0 deletions
22
packages/web-react/src/components/Navigation/Navigation.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
'use client'; | ||
|
||
import classNames from 'classnames'; | ||
import React from 'react'; | ||
import { useStyleProps } from '../../hooks'; | ||
import { SpiritNavigationProps } from '../../types'; | ||
import { useNavigationStyleProps } from './useNavigationStyleProps'; | ||
|
||
const Navigation = (props: SpiritNavigationProps): JSX.Element => { | ||
const { children, ...restProps } = props; | ||
|
||
const { classProps } = useNavigationStyleProps(); | ||
const { styleProps, props: otherProps } = useStyleProps(restProps); | ||
|
||
return ( | ||
<nav {...otherProps} className={classNames(classProps, styleProps.className)} style={styleProps.style}> | ||
<ul>{children}</ul> | ||
</nav> | ||
); | ||
}; | ||
|
||
export default Navigation; |
19 changes: 19 additions & 0 deletions
19
packages/web-react/src/components/Navigation/NavigationItem.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
'use client'; | ||
|
||
import React from 'react'; | ||
import { useStyleProps } from '../../hooks'; | ||
import { SpiritNavigationItemProps } from '../../types'; | ||
|
||
const NavigationItem = (props: SpiritNavigationItemProps): JSX.Element => { | ||
const { children, ...restProps } = props; | ||
|
||
const { styleProps, props: otherProps } = useStyleProps(restProps); | ||
|
||
return ( | ||
<li {...otherProps} className={styleProps.className} style={styleProps.style}> | ||
{children} | ||
</li> | ||
); | ||
}; | ||
|
||
export default NavigationItem; |
43 changes: 43 additions & 0 deletions
43
packages/web-react/src/components/Navigation/NavigationLink.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
'use client'; | ||
|
||
import classNames from 'classnames'; | ||
import React, { ElementType, forwardRef } from 'react'; | ||
import { useStyleProps } from '../../hooks'; | ||
import { PolymorphicRef, SpiritNavigationLinkProps } from '../../types'; | ||
import { useNavigationLinkProps } from './useNavigationLinkProps'; | ||
import { useNavigationLinkStyleProps } from './useNavigationLinkStyleProps'; | ||
|
||
const defaultProps: Partial<SpiritNavigationLinkProps> = { | ||
elementType: 'a', | ||
}; | ||
|
||
/* We need an exception for components exported with forwardRef */ | ||
/* eslint no-underscore-dangle: ['error', { allow: ['_NavigationLink'] }] */ | ||
const _NavigationLink = <E extends ElementType = 'a'>( | ||
props: SpiritNavigationLinkProps<E>, | ||
ref: PolymorphicRef<E>, | ||
): JSX.Element => { | ||
const propsWithDefaults = { ...defaultProps, ...props }; | ||
const { elementType = defaultProps.elementType as ElementType, children, ...restProps } = propsWithDefaults; | ||
const ElementTag = propsWithDefaults.isDisabled ? 'span' : elementType; | ||
|
||
const { navigationLinkProps } = useNavigationLinkProps(propsWithDefaults); | ||
const { classProps, props: modifiedProps } = useNavigationLinkStyleProps(restProps); | ||
const { styleProps, props: otherProps } = useStyleProps(modifiedProps); | ||
|
||
return ( | ||
<ElementTag | ||
{...otherProps} | ||
{...styleProps} | ||
{...navigationLinkProps} | ||
className={classNames(classProps, styleProps.className)} | ||
ref={ref} | ||
> | ||
{children} | ||
</ElementTag> | ||
); | ||
}; | ||
|
||
const NavigationLink = forwardRef<HTMLElement, SpiritNavigationLinkProps<ElementType>>(_NavigationLink); | ||
|
||
export default NavigationLink; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
# Navigation | ||
|
||
The `Navigation` component is a container for the navigation links of the application. | ||
|
||
It consists of a these parts: | ||
|
||
- [Navigation](#navigation) | ||
- [NavigationItem](#navigation-item) | ||
- [NavigationLink](#navigation-link) | ||
|
||
## Navigation | ||
|
||
The `Navigation` is a `nav` wrapper for navigation items. | ||
|
||
```jsx | ||
import { Navigation } from '@lmc-eu/spirit-web-react'; | ||
|
||
<Navigation aria-label="Main Navigation">{/* Navigation items go here */}</Navigation>; | ||
``` | ||
|
||
It centres its children vertically, and if the children do not include `NavigationLink` components, | ||
it will apply a gap between them. | ||
|
||
ℹ️ Don't forget to add the `aria-label` attribute to the `Navigation` component for correct accessible state. | ||
|
||
### API | ||
|
||
| Name | Type | Default | Required | Description | | ||
| ---------- | --------------------------------------------------------------------------------------- | ------- | -------- | ------------------------- | | ||
| `children` | `ReactElement<HTMLLIElement>` \| `ReactElement<NavigationItem>` \| Array of these types | `null` | ✓ | Content of the Navigation | | ||
|
||
The components accept [additional attributes][readme-additional-attributes]. | ||
If you need more control over the styling of a component, you can use [style props][readme-style-props] | ||
and [escape hatches][readme-escape-hatches]. | ||
|
||
## Navigation Item | ||
|
||
The `NavigationItem` is a container for navigation links. | ||
|
||
```jsx | ||
import { NavigationItem } from '@lmc-eu/spirit-web-react'; | ||
|
||
<NavigationItem>{/* Navigation links go here */}</NavigationItem>; | ||
``` | ||
|
||
### API | ||
|
||
| Name | Type | Default | Required | Description | | ||
| ---------- | ----------------------- | ------- | -------- | ----------------------------- | | ||
| `children` | `string` \| `ReactNode` | `null` | ✓ | Content of the NavigationItem | | ||
|
||
The components accept [additional attributes][readme-additional-attributes]. | ||
If you need more control over the styling of a component, you can use [style props][readme-style-props] | ||
and [escape hatches][readme-escape-hatches]. | ||
|
||
## Navigation Link | ||
|
||
The `NavigationLink` is component that is styled to be used as a navigation link. | ||
|
||
```jsx | ||
import { NavigationLink } from '@lmc-eu/spirit-web-react'; | ||
|
||
<NavigationLink href="#">Link</NavigationLink>; | ||
``` | ||
|
||
It can obtain `isSelected` or `isDisabled` states by adding the respective props. | ||
|
||
```jsx | ||
<NavigationLink href="#" aria-current="page" isSelected>Selected Link</NavigationLink> | ||
<NavigationLink href="#" isDisabled>Disabled Link</NavigationLink> | ||
``` | ||
|
||
ℹ️ Don't forget to add the `aria-current="page"` attribute for correct accessible state if selected. | ||
|
||
ℹ️ Please note that in the `isDisabled` state the `NavigationLink` will be an `span` tag. | ||
|
||
If the `NavigationLink` is inside a [`UNSTABLE_Header`][web-react-unstable-header] component, it will | ||
inherit the height of the `Header`. | ||
|
||
### API | ||
|
||
| Name | Type | Default | Required | Description | | ||
| ------------- | --------------------------------- | ------- | -------- | ----------------------------- | | ||
| `children` | `string` \| `ReactNode` | `null` | ✓ | Content of the NavigationLink | | ||
| `elementType` | `ElementType` | `a` | ✕ | Type of element used as | | ||
| `href` | `string` | - | ✕ | URL of the link | | ||
| `isDisabled` | `boolean` | `false` | ✕ | Whether the link is disabled | | ||
| `isSelected` | `boolean` | `false` | ✕ | Whether the link is selected | | ||
| `ref` | `ForwardedRef<HTMLAnchorElement>` | — | ✕ | Anchor element reference | | ||
| `target` | `string` | `null` | ✕ | Link target | | ||
|
||
The components accept [additional attributes][readme-additional-attributes]. | ||
If you need more control over the styling of a component, you can use [style props][readme-style-props] | ||
and [escape hatches][readme-escape-hatches]. | ||
|
||
### Full Example | ||
|
||
With NavigationLink components: | ||
|
||
```jsx | ||
<Navigation aria-label="Main Navigation"> | ||
<NavigationItem> | ||
<NavigationLink href="#" aria-current="page" isSelected> | ||
Selected Link | ||
</NavigationLink> | ||
</NavigationItem> | ||
<NavigationItem> | ||
<NavigationLink href="#" isDisabled> | ||
Disabled Link | ||
</NavigationLink> | ||
</NavigationItem> | ||
<NavigationItem> | ||
<NavigationLink href="#">Link</NavigationLink> | ||
</NavigationItem> | ||
</Navigation> | ||
``` | ||
|
||
With Buttons: | ||
|
||
```jsx | ||
<Navigation aria-label="Secondary Navigation"> | ||
<NavigationItem> | ||
<ButtonLink href="#">Button</ButtonLink> | ||
</NavigationItem> | ||
<NavigationItem> | ||
<ButtonLink href="#" color="secondary">Button</Button> | ||
</NavigationItem> | ||
</Navigation> | ||
``` | ||
|
||
[readme-additional-attributes]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#additional-attributes | ||
[readme-escape-hatches]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#escape-hatches | ||
[readme-style-props]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#style-props | ||
[web-react-unstable-header]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/src/components/UNSTABLE_Header/README.md |
35 changes: 35 additions & 0 deletions
35
packages/web-react/src/components/Navigation/__tests__/Navigation.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import '@testing-library/jest-dom'; | ||
import { render, screen } from '@testing-library/react'; | ||
import React from 'react'; | ||
import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest'; | ||
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest'; | ||
import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest'; | ||
import Navigation from '../Navigation'; | ||
|
||
describe('Navigation', () => { | ||
classNamePrefixProviderTest(Navigation, 'Navigation'); | ||
|
||
stylePropsTest(Navigation); | ||
|
||
restPropsTest(Navigation, 'nav'); | ||
|
||
beforeEach(() => { | ||
render( | ||
<Navigation> | ||
<li>Content</li> | ||
</Navigation>, | ||
); | ||
}); | ||
|
||
it('should have default classname', () => { | ||
expect(screen.getByRole('navigation')).toHaveClass('Navigation'); | ||
}); | ||
|
||
it('should render list and children', () => { | ||
expect(screen.getByRole('list')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should render children', () => { | ||
expect(screen.getByText('Content')).toBeInTheDocument(); | ||
}); | ||
}); |
24 changes: 24 additions & 0 deletions
24
packages/web-react/src/components/Navigation/__tests__/NavigationItem.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import '@testing-library/jest-dom'; | ||
import { render, screen } from '@testing-library/react'; | ||
import React from 'react'; | ||
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest'; | ||
import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest'; | ||
import NavigationItem from '../NavigationItem'; | ||
|
||
describe('NavigationItem', () => { | ||
stylePropsTest(NavigationItem); | ||
|
||
restPropsTest(NavigationItem, 'li'); | ||
|
||
it('should have correct role', () => { | ||
render(<NavigationItem>Content</NavigationItem>); | ||
|
||
expect(screen.getByRole('listitem')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should render children', () => { | ||
render(<NavigationItem>Content</NavigationItem>); | ||
|
||
expect(screen.getByRole('listitem')).toHaveTextContent('Content'); | ||
}); | ||
}); |
49 changes: 49 additions & 0 deletions
49
packages/web-react/src/components/Navigation/__tests__/NavigationLink.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import '@testing-library/jest-dom'; | ||
import { render, screen } from '@testing-library/react'; | ||
import React from 'react'; | ||
import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest'; | ||
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest'; | ||
import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest'; | ||
import NavigationLink from '../NavigationLink'; | ||
|
||
describe('NavigationLink', () => { | ||
classNamePrefixProviderTest(NavigationLink, 'NavigationLink'); | ||
|
||
stylePropsTest(NavigationLink); | ||
|
||
restPropsTest(NavigationLink, 'a'); | ||
|
||
it('should have default classname', () => { | ||
render(<NavigationLink href="/">Content</NavigationLink>); | ||
|
||
expect(screen.getByRole('link')).toHaveClass('NavigationLink'); | ||
}); | ||
|
||
it('should have selected classname', () => { | ||
render( | ||
<NavigationLink href="/" isSelected> | ||
Content | ||
</NavigationLink>, | ||
); | ||
|
||
expect(screen.getByRole('link')).toHaveClass('NavigationLink NavigationLink--selected'); | ||
}); | ||
|
||
it('should have disabled classname and correct elementType', () => { | ||
render( | ||
<NavigationLink href="/" isDisabled> | ||
Content | ||
</NavigationLink>, | ||
); | ||
|
||
expect(screen.getByText('Content')).toHaveClass('NavigationLink NavigationLink--disabled'); | ||
expect(screen.getByText('Content')).toContainHTML('span'); | ||
expect(screen.queryByRole('link')).not.toBeInTheDocument(); | ||
}); | ||
|
||
it('should render children', () => { | ||
render(<NavigationLink href="/">Content</NavigationLink>); | ||
|
||
expect(screen.getByText('Content')).toBeInTheDocument(); | ||
}); | ||
}); |
Oops, something went wrong.