@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
653 lines (611 loc) • 25 kB
Markdown
# SideNavigation
Import: `import { SideNavigation } from '@neo4j-ndl/react'`
## Props
### SideNavigation
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `ariaLabel` | `string` | ✅ | | Aria label for the side navigation. Needed for menubar accessibility. |
| `children` | `ReactNode` | | | Content displayed inside the side navigation |
| `expandedWidth` | `Width<string \| number>` | | | Width used when expanded. Accepts any valid CSS width value or a number |
| `isExpanded` | `boolean` | | `false` | Whether the side navigation is expanded |
| `onPinButtonClick` | `(() => void)` | | | Callback function triggered when the pin button is clicked |
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
| `shouldOverlayOnInteraction` | `boolean` | | `true` | Whether the side navigation expands on hover/focus when not pinned or compact |
### SideNavigation.CategoryHeader
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` | | | Content displayed inside the side navigation category header |
| `ref` | `Ref<HTMLLIElement>` | | | A ref to apply to the root element. |
### SideNavigation.CategoryItem
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `badge` | `ItemBadgeProps` | | | Badge displayed inside the side navigation item. Represented by an object with a number and a type (e.g., 'info', 'warning', 'danger'). |
| `children` | `ReactNode` | | | Content displayed inside the submenu popover. Typically a list of `SideNavigation.NavItem`. |
| `icon` | `ReactNode` | | | Icon displayed inside the side navigation item |
| `isActive` | `boolean` | | | Whether the side navigation item is selected |
| `isMenuOpen` | `boolean` | | `false` | If true, the menu will be open. Otherwise, the menu will open and close based on the user hovering the item. |
| `label` | `string` | | | Text label of the side navigation item |
| `ref` | `Ref<HTMLButtonElement>` | | | A ref to apply to the root element. |
### SideNavigation.Divider
_No props documented._
### SideNavigation.ItemBadge
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `number` | `number` | ✅ | | Number displayed inside the side navigation item badge |
| `ref` | `Ref<HTMLSpanElement>` | | | A ref to apply to the root element. |
| `type` | `'danger' \| 'info' \| 'warning'` | ✅ | | Type of the side navigation item badge |
### SideNavigation.ListItem
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` | | | Content displayed inside the side navigation list item |
| `ref` | `Ref<HTMLLIElement>` | | | A ref to apply to the root element. |
### SideNavigation.NavItem
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `as` | `("button" & ElementType<any, keyof IntrinsicElements>) \| ("symbol" & ElementType<any, keyof IntrinsicElements>) \| ... 177 more ... \| (FunctionComponent<...> & ElementType<...>)` | | | An override of the default HTML tag of the root of the component. Can also be another React component. What HTML element to render the root element as. |
| `badge` | `ItemBadgeProps` | | | Badge displayed inside the side navigation item. Represented by an object with a number and a type (e.g., 'info', 'warning', 'danger'). |
| `href` | `string` | | | The href of the link. Only applicable if `as` is `'a'`. |
| `icon` | `ReactNode` | | | Icon displayed inside the side navigation item |
| `isActive` | `boolean` | | | Whether the side navigation item is selected |
| `label` | `string` | | | Text label of the side navigation item |
| `onClick` | `MouseEventHandler<HTMLButtonElement>` | | | Event handler for when the button is clicked. Only applicable if `as` is `'button'`. |
| `ref` | `any` | | | A ref to apply to the root element. |
| `target` | `HTMLAttributeAnchorTarget` | | | The target of the link. Only applicable if `as` is `'a'`. |
| `trailingElement` | `ReactNode` | | | Element displayed on the leading side of the side navigation item (e.g., counter) |
## Examples
### Side Nav Badges
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { SideNavigation, Typography } from '@neo4j-ndl/react';
import {
CodeBracketIconOutline,
HomeIconOutline,
MagnifyingGlassIconOutline,
ServerIconOutline,
WrenchScrewdriverIconOutline,
} from '@neo4j-ndl/react/icons';
import { useState } from 'react';
const Component = () => {
const [active, setActive] = useState<string | null>(null);
const [isExpanded, setIsExpanded] = useState(false);
return (
<div className="n-h-[calc(40vh-32px)] n-min-h-[700px] n-flex n-bg-neutral-bg-default">
<SideNavigation
ariaLabel="Main side navigation"
isExpanded={isExpanded}
shouldOverlayOnInteraction={true}
onPinButtonClick={() => setIsExpanded(!isExpanded)}
>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<HomeIconOutline />}
label="Info Badge"
isActive={active === 'nav-item-1'}
onClick={() => setActive('nav-item-1')}
badge={{
number: 1,
type: 'info',
}}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<CodeBracketIconOutline />}
label="Warning Badge"
isActive={active === 'nav-item-2'}
onClick={() => setActive('nav-item-2')}
badge={{
number: 1,
type: 'warning',
}}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<ServerIconOutline />}
label="Critical Badge"
isActive={active === 'nav-item-3'}
onClick={() => setActive('nav-item-3')}
badge={{
number: 1,
type: 'danger',
}}
/>
</SideNavigation.ListItem>
<SideNavigation.Divider />
<SideNavigation.ListItem>
<SideNavigation.CategoryItem
icon={<WrenchScrewdriverIconOutline />}
label="Category Item"
isActive={['nav-item-4', 'nav-item-5', 'nav-item-6'].includes(
active ?? '',
)}
badge={{
number: 3,
type: 'danger',
}}
>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<MagnifyingGlassIconOutline />}
label="Info Badge"
isActive={active === 'nav-item-4'}
onClick={() => setActive('nav-item-4')}
badge={{
number: 1,
type: 'info',
}}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<MagnifyingGlassIconOutline />}
label="Warning Badge"
isActive={active === 'nav-item-5'}
onClick={() => setActive('nav-item-5')}
badge={{
number: 1,
type: 'warning',
}}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<MagnifyingGlassIconOutline />}
label="Critical Badge"
isActive={active === 'nav-item-6'}
onClick={() => setActive('nav-item-6')}
badge={{
number: 1,
type: 'danger',
}}
/>
</SideNavigation.ListItem>
</SideNavigation.CategoryItem>
</SideNavigation.ListItem>
</SideNavigation>
<div className="n-h-full n-w-full">
<div className="n-flex n-flex-col n-p-8 n-w-full n-max-w-[1024px] n-mx-auto">
<div className="n-flex n-flex-col n-gap-token-8 n-bg-neutral-bg-weak n-p-token-32 n-rounded-lg n-items-center n-justify-center">
<Typography variant="title-4">Side Nav with Badges</Typography>
<Typography variant="body-medium">
Badges can be used to indicate that there are actions that need
attention from the user on the specific page. The badge can have
three different types of severity:
<b className="n-text-primary-text"> info</b>,
<b className="n-text-warning-text"> warning</b> and
<b className="n-text-danger-text"> danger</b>.
<br />
<br />
The badge on a <b>CategoryItem</b> should be the type of the
highest priority badge of the items in the category. The number
should be the total number of badges in the category.
</Typography>
</div>
</div>
</div>
</div>
);
};
export default Component;
```
### Side Nav Compact
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { SideNavigation, Typography } from '@neo4j-ndl/react';
import {
AcademicCapIconOutline,
ChartBarIconOutline,
CodeBracketIconOutline,
Cog6ToothIconOutline,
HomeIconOutline,
MagnifyingGlassIconOutline,
ServerIconOutline,
WrenchScrewdriverIconOutline,
} from '@neo4j-ndl/react/icons';
import { useState } from 'react';
const Component = () => {
const [active, setActive] = useState<string | null>(null);
return (
<div className="n-h-[calc(40vh-32px)] n-min-h-[700px] n-flex n-bg-neutral-bg-default">
<SideNavigation
isExpanded={false}
shouldOverlayOnInteraction={false}
ariaLabel="Main side navigation"
>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<HomeIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-1'}
onClick={() => setActive('nav-item-1')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<CodeBracketIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-2'}
onClick={() => setActive('nav-item-2')}
/>
</SideNavigation.ListItem>
<SideNavigation.Divider />
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<ServerIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-3'}
onClick={() => setActive('nav-item-3')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.CategoryItem
icon={<WrenchScrewdriverIconOutline />}
label="Category Item"
isActive={['nav-item-4', 'nav-item-5'].includes(active ?? '')}
>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<MagnifyingGlassIconOutline />}
label="Nested Nav Item"
isActive={active === 'nav-item-4'}
onClick={() => setActive('nav-item-4')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<MagnifyingGlassIconOutline />}
label="Nested Nav Item"
isActive={active === 'nav-item-5'}
onClick={() => setActive('nav-item-5')}
/>
</SideNavigation.ListItem>
</SideNavigation.CategoryItem>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<ChartBarIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-6'}
onClick={() => setActive('nav-item-6')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<Cog6ToothIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-7'}
onClick={() => setActive('nav-item-7')}
/>
</SideNavigation.ListItem>
<SideNavigation.Divider />
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<AcademicCapIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-8'}
onClick={() => setActive('nav-item-8')}
/>
</SideNavigation.ListItem>
</SideNavigation>
<div className="n-h-full n-w-full">
<div className="n-flex n-flex-col n-p-8 n-w-full n-max-w-[1024px] n-mx-auto">
<div className="n-flex n-flex-col n-gap-token-8 n-bg-neutral-bg-weak n-p-token-32 n-rounded-lg n-items-center n-justify-center">
<Typography variant="title-4">Compact Side Nav</Typography>
<Typography variant="body-medium">
When using a compact side nav you must make sure to set a
<b> label</b> on the NavItems, as the label will be used for the
built in tooltip. It is also needed for screen readers to behave
as correctly.
</Typography>
</div>
</div>
</div>
</div>
);
};
export default Component;
```
### Side Nav Default
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { SideNavigation, Typography } from '@neo4j-ndl/react';
import {
AcademicCapIconOutline,
ChartBarIconOutline,
CodeBracketIconOutline,
Cog6ToothIconOutline,
HomeIconOutline,
MagnifyingGlassIconOutline,
ServerIconOutline,
WrenchScrewdriverIconOutline,
} from '@neo4j-ndl/react/icons';
import { useState } from 'react';
const Component = () => {
const [active, setActive] = useState<string | null>(null);
const [isExpanded, setIsExpanded] = useState(false);
return (
<div className="n-h-[calc(40vh-32px)] n-min-h-[700px] n-flex n-bg-neutral-bg-default">
<SideNavigation
isExpanded={isExpanded}
shouldOverlayOnInteraction={true}
onPinButtonClick={() => setIsExpanded(!isExpanded)}
ariaLabel="Main side navigation"
>
<SideNavigation.CategoryHeader>
Example Category Header
</SideNavigation.CategoryHeader>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<HomeIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-1'}
onClick={() => setActive('nav-item-1')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<CodeBracketIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-2'}
onClick={() => setActive('nav-item-2')}
/>
</SideNavigation.ListItem>
<SideNavigation.Divider />
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<ServerIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-3'}
onClick={() => setActive('nav-item-3')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.CategoryItem
icon={<WrenchScrewdriverIconOutline />}
label="Category Item"
isActive={['nav-item-4', 'nav-item-5'].includes(active ?? '')}
>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<MagnifyingGlassIconOutline />}
label="Nested Nav Item"
isActive={active === 'nav-item-4'}
onClick={() => setActive('nav-item-4')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<MagnifyingGlassIconOutline />}
label="Nested Nav Item"
isActive={active === 'nav-item-5'}
onClick={() => setActive('nav-item-5')}
/>
</SideNavigation.ListItem>
</SideNavigation.CategoryItem>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<ChartBarIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-6'}
onClick={() => setActive('nav-item-6')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<Cog6ToothIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-7'}
onClick={() => setActive('nav-item-7')}
/>
</SideNavigation.ListItem>
<SideNavigation.Divider />
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<AcademicCapIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-8'}
onClick={() => setActive('nav-item-8')}
/>
</SideNavigation.ListItem>
</SideNavigation>
<div className="n-h-full n-w-full">
<div className="n-flex n-flex-col n-p-8 n-w-full n-max-w-[1024px] n-mx-auto">
<div className="n-flex n-flex-col n-gap-token-8 n-bg-neutral-bg-weak n-p-token-32 n-rounded-lg n-items-center n-justify-center">
<Typography variant="title-4">Default Side Nav</Typography>
<Typography variant="body-medium">
This is the default behavior for a side nav. It expands on hover,
and can be pinned to stay expanded.
</Typography>
</div>
</div>
</div>
</div>
);
};
export default Component;
```
### Side Nav Secondary
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { SideNavigation, Typography } from '@neo4j-ndl/react';
import {
AcademicCapIconOutline,
ChartBarIconOutline,
CodeBracketIconOutline,
Cog6ToothIconOutline,
HomeIconOutline,
MagnifyingGlassIconOutline,
ServerIconOutline,
WrenchScrewdriverIconOutline,
} from '@neo4j-ndl/react/icons';
import { useState } from 'react';
const Component = () => {
const [active, setActive] = useState<string>('nav-item-8');
const [secondaryActive, setSecondaryActive] = useState<string>('beginner');
const [isExpanded, setIsExpanded] = useState(false);
return (
<div className="n-h-[calc(40vh-32px)] n-min-h-[700px] n-flex n-bg-neutral-bg-default n-overflow-hidden">
<SideNavigation
isExpanded={isExpanded}
shouldOverlayOnInteraction={true}
onPinButtonClick={() => setIsExpanded(!isExpanded)}
ariaLabel="Main side navigation"
>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<HomeIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-1'}
onClick={() => setActive('nav-item-1')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<CodeBracketIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-2'}
onClick={() => setActive('nav-item-2')}
/>
</SideNavigation.ListItem>
<SideNavigation.Divider />
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<ServerIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-3'}
onClick={() => setActive('nav-item-3')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.CategoryItem
icon={<WrenchScrewdriverIconOutline />}
label="Category Item"
isActive={['nav-item-4', 'nav-item-5'].includes(active)}
>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<MagnifyingGlassIconOutline />}
label="Nested Nav Item"
isActive={active === 'nav-item-4'}
onClick={() => setActive('nav-item-4')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<MagnifyingGlassIconOutline />}
label="Nested Nav Item"
isActive={active === 'nav-item-5'}
onClick={() => setActive('nav-item-5')}
/>
</SideNavigation.ListItem>
</SideNavigation.CategoryItem>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<ChartBarIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-6'}
onClick={() => setActive('nav-item-6')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<Cog6ToothIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-7'}
onClick={() => setActive('nav-item-7')}
/>
</SideNavigation.ListItem>
<SideNavigation.Divider />
<SideNavigation.ListItem>
<SideNavigation.NavItem
icon={<AcademicCapIconOutline />}
label="Nav Item"
isActive={active === 'nav-item-8'}
onClick={() => setActive('nav-item-8')}
/>
</SideNavigation.ListItem>
</SideNavigation>
<SideNavigation
isExpanded={true}
shouldOverlayOnInteraction={false}
expandedWidth={220}
ariaLabel="Secondary side navigation"
>
<SideNavigation.CategoryHeader>
Category Header
</SideNavigation.CategoryHeader>
<SideNavigation.ListItem>
<SideNavigation.NavItem
label="Secondary Nav Item"
isActive={secondaryActive === 'secondary-nav-item-1'}
onClick={() => setSecondaryActive('secondary-nav-item-1')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
label="Secondary Nav Item"
isActive={secondaryActive === 'secondary-nav-item-2'}
onClick={() => setSecondaryActive('secondary-nav-item-2')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
label="Secondary Nav Item"
isActive={secondaryActive === 'secondary-nav-item-3'}
onClick={() => setSecondaryActive('secondary-nav-item-3')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
label="Secondary Nav Item"
isActive={secondaryActive === 'secondary-nav-item-4'}
onClick={() => setSecondaryActive('secondary-nav-item-4')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
label="Secondary Nav Item"
isActive={secondaryActive === 'secondary-nav-item-5'}
onClick={() => setSecondaryActive('secondary-nav-item-5')}
/>
</SideNavigation.ListItem>
<SideNavigation.CategoryHeader>
Category Header
</SideNavigation.CategoryHeader>
<SideNavigation.ListItem>
<SideNavigation.NavItem
label="Secondary Nav Item"
icon={<MagnifyingGlassIconOutline />}
isActive={secondaryActive === 'secondary-nav-item-6'}
onClick={() => setSecondaryActive('secondary-nav-item-6')}
/>
</SideNavigation.ListItem>
<SideNavigation.ListItem>
<SideNavigation.NavItem
label="Secondary Nav Item"
icon={<MagnifyingGlassIconOutline />}
isActive={secondaryActive === 'secondary-nav-item-7'}
onClick={() => setSecondaryActive('secondary-nav-item-7')}
/>
</SideNavigation.ListItem>
</SideNavigation>
<div className="n-h-full n-w-full">
<div className="n-flex n-flex-col n-p-8 n-w-full n-max-w-[1024px] n-mx-auto">
<div className="n-flex n-flex-col n-gap-token-8 n-bg-neutral-bg-weak n-p-token-32 n-rounded-lg n-items-center n-justify-center">
<Typography variant="title-4">Secondary Side Nav</Typography>
<Typography variant="body-medium">
If a page requires its own navigation, you can use a secondary
side nav. The secondary side nav is always expanded and does not
have a pin button.
</Typography>
</div>
</div>
</div>
</div>
);
};
export default Component;
```