Skip to content
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ This repository keeps user-facing and implementation notes inside the `docs/` fo

- `docs/components/Accessibility.md` — Accessibility testing, a11y helpers, and issue #383 notes
- `docs/components/ReputationPage.md` — Reputation page implementation and rendering states
- `docs/components/Navbar.md` — Navigation component examples, props, and accessibility patterns
- `docs/data-model.md` — Data model and persistence guide
- `docs/persistence.md` — Persistence API and local storage patterns
- `docs/preferences.md` — Preferences provider and currency/locale helpers
Expand Down
166 changes: 124 additions & 42 deletions docs/components/Navbar.md
Original file line number Diff line number Diff line change
@@ -1,61 +1,143 @@
# Navbar
# Navigation components

Accessible primary global navigation for the TalentTrust application.
This guide covers the current navigation primitives used throughout TalentTrust:

## Overview
- `Navbar` for the primary global links
- `Breadcrumbs` for page-level navigation context
- `RouteAnnouncer` for screen-reader announcements after client-side route changes

`Navbar` renders persistent links to the three core application routes:
`/contracts`, `/milestones`, and `/reputation`. It is mounted inside the
sticky header in `src/app/layout.tsx` and uses `next/link` with
`usePathname` for client-side navigation and active-route highlighting.
## Quick start

## File Location
A typical app-shell layout mounts these components once near the top of the tree:

- Component: `src/components/Navbar.tsx`
- Tests: `src/components/__tests__/Navbar.test.tsx`
```tsx
import Navbar from '@/components/Navbar';
import Breadcrumbs from '@/components/Breadcrumbs';
import RouteAnnouncer from '@/components/RouteAnnouncer';

export default function AppShell({ children }: { children: React.ReactNode }) {
return (
<>
<header className="sticky top-0 z-40 border-b bg-[var(--background)]">
<Navbar />
</header>

<main tabIndex={-1} className="mx-auto max-w-6xl p-6">
<Breadcrumbs
items={[
{ label: 'Dashboard', href: '/' },
{ label: 'Contracts', href: '/contracts' },
{ label: 'Contract #42' },
]}
/>
{children}
</main>

<RouteAnnouncer />
</>
);
}
```

## Navbar

### Overview

`Navbar` renders the primary application links for `/contracts`, `/milestones`, and `/reputation`. It uses `next/link` and `usePathname` from `next/navigation` to highlight the current route with `aria-current="page"`.

### Props

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| None | — | — | `Navbar` accepts no props. The route list is internal and fixed. |

### Example

```tsx
<header>
<Navbar />
</header>
```

### Accessibility and behavior

- Wrapping element: `<nav aria-label="Primary">`
- Active item: `aria-current="page"`
- Focus style: `focus:outline-none focus:ring-2 focus:ring-[var(--ring)] focus:ring-offset-1`
- Layout: links wrap naturally via `flex-wrap`, so the navigation stays usable on narrow screens without a hamburger menu

## Breadcrumbs

## Props
### Overview

`Breadcrumbs` renders an accessible breadcrumb trail. Ancestor items are links and the final item is plain text marked with `aria-current="page"`.

### Props

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `items` | `BreadcrumbItem[]` | Yes | Ordered list of crumbs from root to current page |

### `BreadcrumbItem`

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| — | — | — | Navbar accepts no props. Route data is internal. |
| `label` | `string` | Yes | Visible text shown for the crumb |
| `href` | `string` | No | Link target for ancestor crumbs. Omit this on the final crumb so it renders as the current page |

### Example

```tsx
<Breadcrumbs
items={[
{ label: 'Dashboard', href: '/' },
{ label: 'Contracts', href: '/contracts' },
{ label: 'Contract #42' },
]}
/>
```

## Accessibility
### Accessibility and behavior

- **`aria-current="page"`** — applied to the link whose `href` matches
the current pathname. Screen readers announce "current page" so users
know which section they are in.
- **Focus management** — All links are natively focusable. Focus rings
use `focus:ring-2 focus:ring-[var(--ring)]` consistent with
`WalletConnectButton` and `not-found` quick links.
- **Landmark** — Wrapped in `&lt;nav aria-label="Primary"&gt;` for easy
screen-reader landmark navigation.
- **Keyboard order** — Links appear in DOM order after the brand and
before the wallet button, maintaining a logical tab sequence.
- Root landmark: `<nav aria-label="Breadcrumb">`
- Separator: `/` is rendered with `aria-hidden="true"`
- Final crumb: rendered as text with `aria-current="page"`
- Fallback link target: omitted `href` values on ancestor items fall back to `/`

## Mobile Behavior
## RouteAnnouncer

On viewports narrower than the header's content width, the navigation
links wrap below the brand/wallet row via `flex-wrap`. No hamburger
menu is used—this avoids hidden-focus management and keeps the
implementation lightweight while remaining fully accessible.
### Overview

## Styling
`RouteAnnouncer` is a client component that improves screen-reader feedback after client-side navigation. When the pathname changes, it focuses the first `<main>` landmark (if present) and announces the new page title from the first `<h1>` on the page, or falls back to `Page: <pathname>`.

| State | Tailwind Classes |
|-------|-----------------|
| Active route | `text-[var(--primary)] bg-[var(--primary)]/10` |
| Inactive route | `text-[var(--muted-foreground)] hover:text-[var(--foreground)] hover:bg-[var(--muted)]` |
| Focus | `focus:outline-none focus:ring-2 focus:ring-[var(--ring)] focus:ring-offset-1` |
### Props

## Dependencies
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| None | — | — | `RouteAnnouncer` accepts no props. |

### Example

```tsx
<RouteAnnouncer />
```

- `next/link` — client-side navigation
- `next/navigation` — `usePathname` hook
- No additional packages required
### Accessibility and behavior

## Related
- Announcements are emitted through a visually hidden `role="status"` region
- The component intentionally does nothing on the initial mount to avoid announcing the page before navigation has occurred
- It expects the page to provide a `<main>` element with `tabIndex={-1}` so focus can move predictably

- `src/app/layout.tsx` — mounting point
- `src/app/not-found.tsx` — contains the same three routes as recovery links
- `src/components/RouteAnnouncer.tsx` — announces route changes to AT
## Common patterns

- Mount `Navbar` once inside the shared header and keep the same route list everywhere.
- Use `Breadcrumbs` on page-level views such as contract detail pages, where the trail is specific to the current context.
- Mount `RouteAnnouncer` once near the root layout so route changes are announced consistently across the app.
- Ensure the page has a single `<h1>` and a focusable `<main>` landmark for the best screen-reader experience.

## Related files

- Component: `src/components/Navbar.tsx`
- Component: `src/components/Breadcrumbs.tsx`
- Component: `src/components/RouteAnnouncer.tsx`
- Tests: `src/components/__tests__/Navbar.test.tsx`, `src/components/__tests__/Breadcrumbs.test.tsx`, `src/components/__tests__/RouteAnnouncer.test.tsx`
Loading