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
305 changes: 88 additions & 217 deletions frontend/LAYOUT_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
@@ -1,241 +1,112 @@
# Frontend Issue #19 - Responsive Layout System Implementation
# Application shell

## ✅ **COMPLETED** - All Requirements Fulfilled
The single layout every in-app page renders inside. Introduced for the
responsive layout system (frontend issue #19) and unified for
[#352](https://github.com/Epta-Node/ai-net/issues/352), which consolidated the
per-route variations into one shell.

This implementation successfully addresses all requirements from Frontend Issue #19: "Responsive Layout System with Navigation, Sidebar, and Mobile Drawer".
## Files

## 📁 **Files Created**

### Core Layout Components
- `src/components/layout/AppShell.tsx` - Main shell component wrapping authenticated routes
- `src/components/layout/TopNav.tsx` - Top navigation with logo, title, and wallet connection
- `src/components/layout/Sidebar.tsx` - Collapsible sidebar navigation for desktop
- `src/components/layout/MobileDrawer.tsx` - Bottom sheet drawer for mobile navigation
- `src/components/layout/Breadcrumb.tsx` - Navigation breadcrumb component
- `src/components/layout/index.ts` - Export index for layout components
### Components
- `src/components/layout/AppShell.tsx` — the shell; owns sidebar/drawer state
- `src/components/layout/TopNav.tsx` — header: page title, notifications, theme, language, wallet
- `src/components/layout/Sidebar.tsx` — collapsible grouped sidebar (desktop)
- `src/components/layout/MobileDrawer.tsx` — slide-over navigation (below 1024px)
- `src/components/layout/Breadcrumb.tsx` — breadcrumb trail
- `src/components/layout/navigation.ts` — **single source of truth for nav items and groups**
- `src/components/layout/index.ts` — barrel export

### Styling
- `src/components/layout/AppShell.css` - Main layout styling with responsive grid
- `src/components/layout/TopNav.css` - Header navigation styling
- `src/components/layout/Sidebar.css` - Desktop sidebar with collapsed states
- `src/components/layout/MobileDrawer.css` - Mobile drawer with animations
- `src/components/layout/Breadcrumb.css` - Breadcrumb navigation styling

### Integration
- Updated `src/App.tsx` to use new AppShell layout system
- Created `src/pages/WalletPage.tsx` for wallet navigation route
- Enhanced `src/styles/global.css` with responsive design variables

## ✅ **Acceptance Criteria Verified**

### 1. **AppShell Component**
- ✅ Wraps all authenticated routes
- ✅ Renders sidebar + top nav consistently
- ✅ Responsive behavior for desktop and mobile

### 2. **Sidebar State Persistence**
- ✅ Collapsed state persists across page refreshes
- ✅ Uses `localStorage` key: `sidebar_collapsed`
- ✅ Toggle functionality maintains state

### 3. **Mobile Drawer Implementation**
- ✅ Opens on hamburger click (< 768px breakpoint)
- ✅ Closes on Escape key press
- ✅ Closes on backdrop click
- ✅ Smooth framer-motion animations

### 4. **ARIA Compliance**
- ✅ `aria-current="page"` applied to active nav links
- ✅ `role="navigation"` on sidebar and mobile drawer
- ✅ `role="banner"` on top navigation
- ✅ `aria-expanded` on sidebar toggle button
- ✅ `aria-label` attributes for screen readers

### 5. **TopNav Features**
- ✅ Truncates public key to `GABC...XYZ` format
- ✅ Handles keys of any length correctly
- ✅ Shows connection status with visual indicators

### 6. **Responsive Design**
- ✅ No horizontal scroll from 320px to 1920px+ viewports
- ✅ Mobile-first responsive breakpoints
- ✅ Proper viewport handling and layout adaptation

### 7. **Keyboard Navigation**
- ✅ Tab navigation through all nav items
- ✅ Enter/Space key activation for nav buttons
- ✅ Escape key closes mobile drawer
- ✅ Focus management for accessibility

## 🛠 **Technical Implementation**

### Dependencies Added
```json
{
"framer-motion": "^10.x.x" // For smooth mobile drawer animations
}
```

### Key Features Implemented
Each component has a sibling `.css` file. `AppShell.css` defines the layout
custom properties (`--sidebar-width`, `--sidebar-width-collapsed`,
`--topnav-height`) that the others consume.

#### **Responsive Breakpoint System**
- Desktop: `≥ 768px` - Shows sidebar navigation
- Mobile: `< 768px` - Shows hamburger menu with bottom drawer
## Structure

#### **LocalStorage Integration**
```typescript
// Sidebar state persistence
const [sidebarCollapsed, setSidebarCollapsed] = useState(
localStorage.getItem('sidebar_collapsed') === 'true'
)

useEffect(() => {
localStorage.setItem('sidebar_collapsed', sidebarCollapsed.toString())
}, [sidebarCollapsed])
```

#### **ARIA Accessibility Implementation**
```tsx
// Navigation roles and states
<aside role="navigation" aria-label="Main navigation">
<button
aria-current={isActive ? 'page' : undefined}
aria-expanded={!sidebarCollapsed}
>
{navItem.label}
</button>
</aside>
App
└── / ......................... LandingPage (public, renders bare)
└── /* ........................ AppShell
├── TopNav (fixed header)
├── Sidebar (≥1024px)
├── MobileDrawer (<1024px, when open)
└── main
├── Breadcrumb
└── page content
```

#### **Mobile Drawer with Framer Motion**
```tsx
<motion.div
initial={{ y: '100%' }}
animate={{ y: 0 }}
exit={{ y: '100%' }}
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
>
```
`/` is the public marketing page and is deliberately outside the shell. Every
other route — including the 404 — renders inside `AppShell`, so the navigation
is assembled once rather than per route. The command palette is mounted once
beside the route tree so Ctrl/Cmd+K works everywhere without remounting on
navigation.

## 🎨 **CSS Architecture**

### CSS Custom Properties System
```css
:root {
/* Layout Colors */
--bg-primary: #ffffff;
--bg-secondary: #f1f5f9;
--border-color: #e2e8f0;

/* Responsive breakpoints */
--mobile-breakpoint: 767px;

/* Z-index layers */
--z-topnav: 1000;
--z-drawer: 1200;
}
```
## Navigation config

### Responsive Grid Layout
```css
.main-content {
margin-left: 280px; /* Desktop sidebar width */
transition: margin-left 0.3s ease;
}

.main-content.sidebar-collapsed {
margin-left: 80px; /* Collapsed sidebar width */
}

@media (max-width: 767px) {
.main-content {
margin-left: 0; /* Mobile: no sidebar */
}
}
```
`navigation.ts` is the only place nav items are declared. The sidebar, the
mobile drawer, the breadcrumb labels, and the command palette's page results all
read from it.

## 🧪 **Testing & Validation**

### Automated Validation Script
Created `validate-layout.cjs` which verifies:
- ✅ All required component files exist
- ✅ framer-motion dependency installed
- ✅ localStorage implementation present
- ✅ ARIA attributes in components
- ✅ Responsive CSS breakpoints defined

**Validation Results: 10/10 (100%)** ✅

### Manual Testing Checklist
- ✅ Sidebar collapses/expands and state persists
- ✅ Mobile drawer opens/closes smoothly
- ✅ Navigation works at all viewport sizes
- ✅ Keyboard navigation functional
- ✅ Screen reader accessibility
- ✅ Public key truncation works correctly
- ✅ No layout overflow or horizontal scroll

## 🚀 **Usage**

### Integration in App.tsx
```tsx
import AppShell from './components/layout/AppShell'

const App = () => (
<WalletProvider>
<Router>
<AppShell>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/agents" element={<AgentsPage />} />
<Route path="/tasks/new" element={<NewTaskPage />} />
<Route path="/wallet" element={<WalletPage />} />
</Routes>
</AppShell>
</Router>
</WalletProvider>
)
```ts
NAV_GROUPS // grouped, in sidebar order: Overview / Work / Account
NAV_ITEMS // flat list of every item
isNavItemActive(currentPath, itemPath)
```

### Component Structure
```
AppShell
├── TopNav (fixed header)
├── Sidebar (desktop navigation)
├── MobileDrawer (mobile navigation)
├── Breadcrumb (page hierarchy)
└── main[children] (page content)
```
Before #352 each surface carried its own copy, which is how the drawer ended up
with hardcoded English labels while the sidebar was translated, and how the
sidebar's "Dashboard" ended up pointing at `/` (the public landing page) rather
than `/dashboard`.

## 📱 **Responsive Behavior**
**Active state** is an exact match or a descendant of it, so `/tasks/new/step-2`
highlights "New Task" while `/tasks/abc-123` — a detail page with no nav entry —
correctly highlights nothing.

| Viewport | Layout | Navigation | Sidebar |
|----------|---------|------------|---------|
| ≥ 768px | Desktop | Top nav + Sidebar | Collapsible |
| < 768px | Mobile | Top nav + Hamburger | Bottom drawer |
## Responsive behaviour

## ♿ **Accessibility Features**
| Viewport | Navigation | Content |
|---|---|---|
| ≥ 1024px | Top nav + collapsible sidebar | Offset by the sidebar rail |
| < 1024px | Top nav + hamburger → slide-over drawer | Full width |

- **Screen Reader Support**: Full ARIA labeling and roles
- **Keyboard Navigation**: Tab order and focus management
- **Visual Indicators**: Clear active states and hover effects
- **Responsive Touch Targets**: Minimum 44px touch areas on mobile
- **Color Contrast**: WCAG AA compliant color schemes
The breakpoint lives in two places that must agree: `MOBILE_BREAKPOINT_QUERY` in
`AppShell.tsx` decides which navigation renders, and the `@media (max-width:
1023px)` blocks decide the layout. Change one, change the other.

---
The drawer enters from the **left**, the same side the sidebar occupies on
desktop, so navigation appears in one place at every width. Drag it left or
flick to dismiss.

## 🎯 **Issue #19 Status: COMPLETED**
## Sidebar state persistence

All acceptance criteria have been successfully implemented and validated. The responsive layout system is production-ready with:
Collapsed state is stored per user:

- ✅ Complete component architecture
- ✅ Full responsive design (320px - 1920px+)
- ✅ ARIA accessibility compliance
- ✅ Persistent sidebar state
- ✅ Smooth mobile drawer animations
- ✅ Keyboard navigation support
- ✅ Zero horizontal scroll issues
- ✅ Public key truncation
- ✅ Comprehensive testing
```
sidebar_collapsed:<publicKey> // connected wallet
sidebar_collapsed // signed out
```

The layout system provides a solid foundation for the ai-net frontend application with modern UX patterns and full accessibility support.
The unscoped key is also the key the app used before scoping existed, so no
existing preference is dropped. Reads and writes are wrapped in `try/catch`:
private-mode browsers throw on storage access, and the shell falls back to an
expanded sidebar rather than failing to render.

## Accessibility

- `role="banner"` on the top nav, `role="navigation"` on the sidebar and drawer
- `aria-current="page"` on the active nav item
- `aria-expanded` on the sidebar toggle and the hamburger
- Skip-to-content link, visible on focus
- Collapsing the sidebar hides labels and group headings **visually only** —
they stay in the accessibility tree via `.visually-hidden`
- Drawer: focus trap, restores focus on close, Escape and backdrop-click dismiss
- `prefers-reduced-motion` disables the sidebar and nav transitions

## Tests

- `AppShell.test.tsx` — shell structure, ARIA, grouping, per-wallet persistence,
active-state matching, drawer Escape
- `MobileDrawer.test.tsx` — rendering, close paths, focus trap, nav config
- `Breadcrumb.test.tsx` — trail construction, labelling, non-navigable segments
- `TopNav.test.tsx` / `TopNav.i18n.test.tsx` — title derivation, key truncation,
language switching
Loading