On this page
Import
import {
Badge,
Breadcrumbs,
Button,
Column,
CommandPalette,
DescriptionList,
IconButton,
Link,
LinkProvider,
List,
NavigationBar,
NavigationDrawer,
NavigationRail,
Page,
PageHeader,
Pager,
Panes,
Row,
Scaffold,
Surface,
Tabs,
Text,
Tooltip,
TopBar,
} from '@envsemble/envui';
Usage
Build one array of destinations, each with its label, icon and href, current on the place shown, and optionally a count, a group and an onClick.
- Give the same array to a NavigationBar, a NavigationRail and a NavigationDrawer, in the Scaffold's
navigationasbar,railanddrawer. - Render every
hrefthrough the router's link withLinkProvider. - After the router shows the new page, set
document.title, such as "Projects – Northwind", and move focus to the body or the page's heading.
import { useRef, useState, type CSSProperties, type ReactNode } from 'react';
import {
NavigationBar,
NavigationDrawer,
Page,
PageHeader,
NavigationRail,
Scaffold,
Text,
TopBar,
type Destination,
} from '@envsemble/envui';
import { IconCategory } from '@envsemble/envui/icons/Category';
import { IconDescription } from '@envsemble/envui/icons/Description';
import { IconGroup } from '@envsemble/envui/icons/Group';
import { IconHome } from '@envsemble/envui/icons/Home';
import { IconInbox } from '@envsemble/envui/icons/Inbox';
import { IconManageAccounts } from '@envsemble/envui/icons/ManageAccounts';
import { IconSettings } from '@envsemble/envui/icons/Settings';
/**
* Stands in for the window. An application renders its Scaffold at its root, as tall as the window;
* this preview is a box 26 rem tall and adds no landmarks to the page around it.
*/
const preview = {
blockSize: '26rem',
borderRadius: '1.75rem',
} as CSSProperties;
export default function PrimaryNavigation() {
const [current, setCurrent] = useState('projects');
const main = useRef<HTMLElement>(null);
const page = [...destinations, ...workspace].find(({ id }) => id === current) ?? destinations[0];
const go = (id: string) => {
setCurrent(id);
// After a change of page, focus moves to the body, so the keyboard and the screen reader go on
// from the new page. An application also sets document.title to the page's name.
main.current?.focus();
};
// One array of destinations, which every navigation form takes; the router renders the page,
// and the example keeps it in state.
const items: Destination[] = [...destinations, ...workspace].map(
({ id, label, href, icon, unread }, index) => ({
label,
href,
icon,
count: unread,
group: index < destinations.length ? undefined : 'Workspace',
current: id === current,
onClick: () => go(id),
}),
);
return (
<Scaffold
scroll="body"
landmarks={false}
style={preview}
bodyRef={main}
topBar={<TopBar title="Northwind" />}
// The one primary navigation, named Main, its current page marked, in each form.
navigation={{
bar: <NavigationBar destinations={items} />,
rail: <NavigationRail destinations={items} />,
drawer: <NavigationDrawer destinations={items} headingLevel={3} />,
}}
>
<Page width="md" header={<PageHeader title={page.label} headingLevel={3} />}>
<Text emphasis="muted">{page.description}</Text>
</Page>
</Scaffold>
);
}
interface Place {
id: string;
label: string;
href: string;
icon: ReactNode;
description: string;
unread?: number;
}
const destinations: Place[] = [
{
id: 'home',
label: 'Home',
href: '/',
icon: <IconHome />,
description: 'What changed in your projects since you last looked.',
},
{
id: 'inbox',
label: 'Inbox',
href: '/inbox',
icon: <IconInbox />,
description: 'Comments and requests that wait for you.',
unread: 3,
},
{
id: 'projects',
label: 'Projects',
href: '/projects',
icon: <IconCategory />,
description: 'Website refresh, Quarterly report and four more projects.',
},
{
id: 'documents',
label: 'Documents',
href: '/documents',
icon: <IconDescription />,
description: 'Every document in the workspace, newest first.',
},
];
const workspace: Place[] = [
{
id: 'team',
label: 'Team',
href: '/team',
icon: <IconGroup />,
description: 'Alex Morgan, Sam Lee, Priya Shah and the rest of Northwind.',
},
{
id: 'members',
label: 'Members',
href: '/settings/members',
icon: <IconManageAccounts />,
description: 'Who can sign in to Northwind, and what each person can do.',
},
{
id: 'settings',
label: 'Settings',
href: '/settings',
icon: <IconSettings />,
description: 'The workspace’s name, its notifications and its appearance.',
},
];Hierarchy
Pass Breadcrumbs with wrap={false} to the PageHeader's breadcrumbs, from two levels down. Each level above the page is an item with label and href; the last item, the page, has only its label.
<PageHeader
title="Brand guide"
breadcrumbs={
<Breadcrumbs
wrap={false}
items={[
{ label: 'Projects', href: '/projects' },
{ label: 'Website refresh', href: '/projects/website-refresh' },
{ label: 'Brand guide' },
]}
/>
}
/>
Views and quick open
Tabs go under the PageHeader for the views of the object the page is about; a link to another page, such as its settings, goes in the PageHeader's actions. A CommandPalette, opened by a search button in the TopBar and by ⌘K or Ctrl+K, lists the destinations as items and opens the chosen one through the router.
List and detail
Panes.Root with behavior="list-detail" and a backLabel that names the list, such as "All projects", holds a Panes.List, a Panes.Divider and a Panes.Detail, each with its label.
<Panes.Root
behavior="list-detail"
open={detailShown}
onOpenChange={setDetailShown}
backLabel="All projects"
>
<Panes.List label="Projects">{list}</Panes.List>
<Panes.Divider label="Resize project list" />
<Panes.Detail label={chosen.name}>{detail}</Panes.Detail>
</Panes.Root>
Composition
The primary navigation is a NavigationRail here, from Scaffold's navigation; the trail is Breadcrumbs in the PageHeader, the views Tabs below it, and quick open an IconButton in the TopBar's actions. A Pager ends a page read in order. The parts are the rail, env-navigation-rail, the quick-open button, env-top-bar-actions > env-icon-button, the trail, env-breadcrumbs, and the tabs, env-tabs-list.
Accessibility
You provide
currenton the place shown, focus after each change of page, anddocument.title.LinkProviderwith the router's link, and anhrefon every place.
envUI provides
- Skip links, the banner and main landmarks, and a body that takes focus without being a tab stop.
aria-current="page"on the current destination and the last trail level.- Tabs with their roles and roving tab stop; a palette that announces the number of results.
- Panes' focus moves between the list and the detail while one shows at a time.
Changelog
- 1.0.0
The page is on the figure template.