Files
roa2web-service-auto/docs/MOBILE_PATTERNS.md
Claude Agent ac2966c629 feat(mobile-fixes-phase3): Complete US-311 - Actualizare Documentație MOBILE_PATTERNS.md
Implemented by Ralph autonomous loop.
Iteration: 9

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-12 16:59:01 +00:00

1909 lines
59 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ROA2WEB Mobile Patterns Library
**Version:** 3.0.0
**Last Updated:** 2026-01-12
**Status:** ✅ Complete (Phase 3 - Mobile Navigation Overhaul)
---
## Table of Contents
1. [Quick Start](#quick-start)
2. [Mobile Layout Overview](#mobile-layout-overview)
3. [Navigation Architecture](#navigation-architecture)
4. [MobileTopBar](#mobiletopbar)
5. [MobileBottomNav](#mobilebottomnav) *(UPDATED - Phase 3)*
6. [MobileDrawerMenu](#mobiledrawermenu) *(UPDATED - Phase 3)*
7. [Mobile Tab Pattern](#mobile-tab-pattern) *(NEW - Phase 3)*
8. [FAB Pattern](#fab-pattern) *(NEW - Phase 3)*
9. [MobileActionBar](#mobileactionbar)
10. [MobileSelectionFooter](#mobileselectionfooter)
11. [BottomSheet](#bottomsheet)
12. [SwipeableCards](#swipeablecards)
13. [Design Tokens for Mobile](#design-tokens-for-mobile)
14. [Best Practices](#best-practices)
15. [Troubleshooting](#troubleshooting)
---
## Quick Start
### For New Developers
Get a mobile view running in **5 minutes**:
```vue
<template>
<div class="mobile-view">
<!-- 1. Top Bar -->
<MobileTopBar
title="My View"
:showMenu="true"
:actions="topBarActions"
@menu-click="showDrawer = true"
@action-click="handleAction"
/>
<!-- 2. Drawer Menu (hamburger navigation) -->
<MobileDrawerMenu
v-model="showDrawer"
:user="currentUser"
@logout="handleLogout"
/>
<!-- 3. Main Content (with padding for fixed bars) -->
<main class="mobile-content">
<!-- Your content here -->
</main>
<!-- 4. Bottom Navigation -->
<MobileBottomNav />
</div>
</template>
<script setup>
import { ref } from 'vue'
import MobileTopBar from '@shared/components/mobile/MobileTopBar.vue'
import MobileBottomNav from '@shared/components/mobile/MobileBottomNav.vue'
import MobileDrawerMenu from '@shared/components/mobile/MobileDrawerMenu.vue'
const showDrawer = ref(false)
const currentUser = { username: 'John Doe' }
const topBarActions = [
{ icon: 'pi pi-filter', label: 'Filter', tooltip: 'Filtrează' }
]
const handleLogout = () => {
// Handle logout
}
</script>
<style scoped>
.mobile-content {
padding-top: 56px; /* MobileTopBar height */
padding-bottom: 56px; /* MobileBottomNav height */
min-height: 100vh;
}
</style>
```
### Import Paths
All mobile components are located in `src/shared/components/mobile/`:
```javascript
import MobileTopBar from '@shared/components/mobile/MobileTopBar.vue'
import MobileBottomNav from '@shared/components/mobile/MobileBottomNav.vue'
import MobileDrawerMenu from '@shared/components/mobile/MobileDrawerMenu.vue'
import MobileActionBar from '@shared/components/mobile/MobileActionBar.vue'
import MobileSelectionFooter from '@shared/components/mobile/MobileSelectionFooter.vue'
import BottomSheet from '@shared/components/mobile/BottomSheet.vue'
import SwipeableCards from '@shared/components/mobile/SwipeableCards.vue'
```
---
## Mobile Layout Overview
### ASCII Diagram: Standard Mobile Layout (Phase 3)
```
┌─────────────────────────────────────────┐
│ MobileTopBar (56px) │
│ [≡] Page Title [📥] [🔍] │
├─────────────────────────────────────────┤
│ │
│ │
│ MAIN CONTENT │
│ │
│ (scrollable area) │
│ │
│ padding-top: 56px │
│ padding-bottom: 56px │
│ [+] │ ← FAB (contextual)
│ │
├─────────────────────────────────────────┤
│ MobileBottomNav (56px) │
│ 🏠 📋 📄 ⚙️ │
│ Dashboard Bonuri Facturi Setări │
└─────────────────────────────────────────┘
```
> **Phase 3 Changes:**
> - Footer Nav: 4 direct links (Dashboard, Bonuri, Facturi, Setări)
> - Upload moved to FAB on Bonuri page
> - Rapoarte removed from footer (accessible via Hamburger Menu)
### ASCII Diagram: Drawer Menu Open (Phase 3 - Grouped Categories)
```
┌──────────────────┬──────────────────────┐
│ MobileDrawer │ ░░░░░░░░░░░░░░░░░░░░ │
│ Menu (280px) │ ░░░░░░░░░░░░░░░░░░░░ │
├──────────────────┤ ░░░ OVERLAY ░░░░░░░░ │
│ 🏢 ROA2WEB │ ░░ (tap to close) ░░ │
├──────────────────┤ ░░░░░░░░░░░░░░░░░░░░ │
│ PRINCIPALE │ ░░░░░░░░░░░░░░░░░░░░ │
│ 🏠 Dashboard │ ░░░░░░░░░░░░░░░░░░░░ │
│ 📋 Bonuri │ ░░░░░░░░░░░░░░░░░░░░ │
│ ────────────── │ ░░░░░░░░░░░░░░░░░░░░ │
│ RAPOARTE │ ░░░░░░░░░░░░░░░░░░░░ │
│ 📄 Facturi │ ░░░░░░░░░░░░░░░░░░░░ │
│ 🔢 Balanță │ ░░░░░░░░░░░░░░░░░░░░ │
│ 💰 Casa și Banca│ ░░░░░░░░░░░░░░░░░░░░ │
│ ────────────── │ ░░░░░░░░░░░░░░░░░░░░ │
│ ANALIZE │ ░░░░░░░░░░░░░░░░░░░░ │
│ ⏰ Scadențe │ ░░░░░░░░░░░░░░░░░░░░ │
│ 📋 Facturi Det. │ ░░░░░░░░░░░░░░░░░░░░ │
│ ────────────── │ ░░░░░░░░░░░░░░░░░░░░ │
│ ADMINISTRARE │ ░░░░░░░░░░░░░░░░░░░░ │
│ ⚙️ Setări │ ░░░░░░░░░░░░░░░░░░░░ │
├──────────────────┤ ░░░░░░░░░░░░░░░░░░░░ │
│ 👤 Username │ ░░░░░░░░░░░░░░░░░░░░ │
│ 🚪 Deconectare │ ░░░░░░░░░░░░░░░░░░░░ │
└──────────────────┴──────────────────────┘
```
> **Phase 3 Changes:**
> - Navigation organized into 4 category sections with visual separators
> - PRINCIPALE: Dashboard, Bonuri
> - RAPOARTE: Facturi, Balanță, Casa și Banca
> - ANALIZE: Scadențe, Facturi Detaliate
> - ADMINISTRARE: Setări
### ASCII Diagram: Selection Mode Layout
```
┌─────────────────────────────────────────┐
│ MobileTopBar (selection-active) │
│ [←] "3 selectate" [☑] [✕] │
├─────────────────────────────────────────┤
│ │
│ ☑ Item 1 │
│ ☐ Item 2 │
│ ☑ Item 3 │
│ ☑ Item 4 │
│ ☐ Item 5 │
│ │
│ │
├─────────────────────────────────────────┤
│ MobileSelectionFooter │
│ ┌──────────┐ ┌──────────┐ │
│ │ 🗑 Șterge │ │ 📤 Export │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────┘
```
### ASCII Diagram: Action Bar Layout (Edit/Create Views)
```
┌─────────────────────────────────────────┐
│ MobileTopBar (56px) │
│ [←] Editare Bon [⋮] │
├─────────────────────────────────────────┤
│ │
│ Form Fields... │
│ │
│ ┌─────────────────────────────┐ │
│ │ Furnizor: LIDL │ │
│ │ Total: 125.50 RON │ │
│ │ Data: 2026-01-12 │ │
│ └─────────────────────────────┘ │
│ │
├─────────────────────────────────────────┤
│ MobileActionBar (context-aware) │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ Salvează │ │ Trimite aprobare │ │
│ └──────────┘ └──────────────────┘ │
├─────────────────────────────────────────┤
│ MobileBottomNav (56px) │
└─────────────────────────────────────────┘
```
### ASCII Diagram: BottomSheet Open
```
┌─────────────────────────────────────────┐
│ MobileTopBar (56px) │
├─────────────────────────────────────────┤
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
│ ░░░░░░░░░░░░ OVERLAY ░░░░░░░░░░░░░░░░░░ │
│ ░░░░░░░░░░ (tap to close) ░░░░░░░░░░░░░ │
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
├─────────────────────────────────────────┤ ←─ BottomSheet
│ ─────────── │ slides up
│ (drag handle) │
│ │
│ Filter Options: │
│ ☐ Option A │
│ ☑ Option B │
│ ☐ Option C │
│ │
│ [ Apply Filters ] │
│ │
└─────────────────────────────────────────┘
```
### ASCII Diagram: SwipeableCards
```
┌─────────────────────────────────────────┐
│ │
│ ┌─────────────────────────────────────┐ │
│ │ │ │
│ │ KPI Card Content │ │ ← Swipe left/right
│ │ │ │ to navigate
│ │ $125,430 │ │
│ │ Total Sales │ │
│ │ │ │
│ └─────────────────────────────────────┘ │
│ │
│ ●━━━━●───● │ ← Dots indicator
│ │ (active = expanded)
└─────────────────────────────────────────┘
```
### ASCII Diagram: Mobile Tab Pattern (Phase 3)
```
┌─────────────────────────────────────────┐
│ MobileTopBar (56px) │
│ [≡] Facturi [📥] [🔍] │
├─────────────────────────────────────────┤
│┌──────────────────┬────────────────────┐│
││ Clienți │ Furnizori ││ ← Full-width tabs
││ ═══════════ │ ││ (Material Design 3)
│└──────────────────┴────────────────────┘│
├─────────────────────────────────────────┤
│ │
│ TAB CONTENT │
│ (changes based on tab) │
│ │
│ padding-top: 104px (56+48) │
│ │
├─────────────────────────────────────────┤
│ MobileBottomNav (56px) │
│ 🏠 📋 📄 ⚙️ │
│ Dashboard Bonuri Facturi Setări │
└─────────────────────────────────────────┘
```
> **Used in:** Facturi (`/reports/invoices`), Scadențe (`/reports/maturity-analysis`)
### ASCII Diagram: FAB Pattern (Phase 3)
```
┌─────────────────────────────────────────┐
│ MobileTopBar (56px) │
│ [≡] Bonuri [📥] [🔍] │
├─────────────────────────────────────────┤
│ │
│ MAIN CONTENT │
│ │
│ ┌─────────────┐ │
│ │ Upload │ │ ← FAB Popup Menu
│ │ Bulk │ │ (appears on tap)
│ ├─────────────┤ │
│ │ Bon Nou │ │
│ └─────────────┘ │
│ [+] │ ← FAB (56x56px)
│ │ bottom: 72px
├─────────────────────────────────────────┤
│ MobileBottomNav (56px) │
│ 🏠 📋 📄 ⚙️ │
│ Dashboard Bonuri Facturi Setări │
└─────────────────────────────────────────┘
```
> **Used in:** Bonuri page (`/data-entry`) - Upload moved from footer to FAB
---
## Navigation Architecture
### Route Map (Phase 3 - Updated)
```
┌─────────────────────────────────────┐
│ / (root) │
│ Redirects to /dashboard │
└───────────────┬─────────────────────┘
┌───────────────────────────┼───────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────────┐ ┌───────────────────────┐ ┌─────────────────┐
│ /data-entry │ │ /reports │ │ /settings │
│ │ │ │ │ │
│ 📋 Bonuri List │ │ 📄 invoices [TABS] │ │ ⚙️ Settings Hub │
│ 📝 Bon Create │ │ 💰 bank-cash │ │ (centralized │
│ ✏️ Bon Edit │ │ 🔢 trial-balance │ │ settings) │
│ 📊 OCR Metrics │ │ 📉 maturity [TABS] │ │ │
│ ☁️ Bulk Upload │ │ 📋 detailed-invoices │ │ Links to: │
│ │ │ 📈 cache-stats │ │ - OCR Settings │
│ [FAB: +] │ │ 📝 server-logs │ │ - Cache Stats │
│ - Bon Nou │ │ 📱 telegram │ │ - Server Logs │
│ - Upload Bulk │ │ │ │ - Telegram │
└───────────────────┘ └───────────────────────┘ └─────────────────┘
/dashboard
┌─────────────────────┐
│ 🏠 Dashboard │ ← Primary entry point (Footer Nav)
│ SwipeableCards KPIs│
│ (No quick-links │
│ on mobile) │
└─────────────────────┘
```
> **Phase 3 Navigation Changes:**
> - `/dashboard` is now a direct route in Footer Nav (not under `/reports`)
> - Invoices and Maturity Analysis have Clienți/Furnizori tabs
> - FAB on Bonuri page provides Bon Nou + Upload Bulk actions
### Navigation Flow: Settings Hub Pattern
The Settings Hub (`/settings`) is the centralized entry point for all administrative pages:
```
MobileBottomNav "Setări" button
┌───────────────────────────────────┐
│ Settings Hub │
│ /settings │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ OCR │ │ Cache │ │
│ │ Setări │ │ Stats │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ ┌────┴────┐ ┌────┴────┐ │
│ │ Server │ │Telegram │ │
│ │ Logs │ │ Bot │ │
│ └─────────┘ └─────────┘ │
└───────────────────────────────────┘
▼ (click on card)
┌───────────────────────────────────┐
│ /data-entry/ocr-metrics │
│ /reports/cache-stats │
│ /reports/server-logs │
│ /reports/telegram │
└───────────────────────────────────┘
```
### MobileBottomNav Default Items (Phase 3 - 4 Direct Links)
```javascript
// Default navigation items - Phase 3 (4 links, no action button)
[
{ to: '/dashboard', icon: 'pi pi-home', label: 'Dashboard' },
{ to: '/data-entry', icon: 'pi pi-receipt', label: 'Bonuri' },
{ to: '/reports/invoices', icon: 'pi pi-file-edit', label: 'Facturi' },
{ to: '/settings', icon: 'pi pi-cog', label: 'Setări' }
]
```
> **Phase 3 Changes:**
> - Upload button removed from footer (moved to FAB on Bonuri page)
> - Dashboard added as direct link
> - Facturi added as direct link (with Clienți/Furnizori tabs)
> - All 4 items are now router links (no action buttons)
### MobileDrawerMenu Navigation Links (Phase 3 - Grouped Categories)
```javascript
// PRINCIPALE: Primary navigation
const principaleItems = [
{ to: '/dashboard', icon: 'pi pi-home', label: 'Dashboard', exactMatch: true },
{ to: '/data-entry', icon: 'pi pi-receipt', label: 'Bonuri', exactMatch: false }
]
// RAPOARTE: Reports section
const rapoarteItems = [
{ to: '/reports/invoices', icon: 'pi pi-file', label: 'Facturi', exactMatch: true },
{ to: '/reports/trial-balance', icon: 'pi pi-calculator', label: 'Balanță', exactMatch: true },
{ to: '/reports/bank-cash', icon: 'pi pi-money-bill', label: 'Casa și Banca', exactMatch: true }
]
// ANALIZE: Analysis section
const analizeItems = [
{ to: '/reports/maturity-analysis', icon: 'pi pi-clock', label: 'Scadențe', exactMatch: true },
{ to: '/reports/detailed-invoices', icon: 'pi pi-list', label: 'Facturi Detaliate', exactMatch: true }
]
// ADMINISTRARE: Admin section
const administrareItems = [
{ to: '/settings', icon: 'pi pi-cog', label: 'Setări', exactMatch: false }
]
```
> **Phase 3 Changes:**
> - Navigation items organized into 4 visual sections with headers
> - Visual dividers between sections (`.drawer-divider`)
> - Section headers styled as uppercase labels (PRINCIPALE, RAPOARTE, ANALIZE, ADMINISTRARE)
> - `exactMatch` flag for precise active state highlighting
### Dashboard Mobile Layout (Phase 3 - KPIs Only)
On mobile, Dashboard shows ONLY KPI cards (quick-links removed in Phase 3):
```
┌─────────────────────────────────────────┐
│ MobileTopBar: Dashboard │
│ [≡] Dashboard [📥] [🔍] │
├─────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────┐ │
│ │ SwipeableCards (KPIs) │ │
│ │ ← Swipe → │ │
│ │ │ │
│ │ Total Facturi: 125,430 RON │ │
│ │ Scadențe: 5 restante │ │
│ │ ... │ │
│ │ │ │
│ └─────────────────────────────────┘ │
│ │
│ ●━━━━●───● │
│ │
│ (No quick-links on mobile - use │
│ Footer Nav or Hamburger Menu) │
│ │
├─────────────────────────────────────────┤
│ MobileBottomNav (56px) │
│ 🏠 📋 📄 ⚙️ │
│ Dashboard Bonuri Facturi Setări │
└─────────────────────────────────────────┘
```
> **Phase 3 Changes:**
> - Quick-link cards removed from mobile Dashboard
> - Desktop Dashboard keeps quick-links (unchanged)
> - Navigation to other pages via Footer Nav or Hamburger Menu
---
## MobileTopBar
Material Design 3 inspired top navigation bar for mobile views.
### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `title` | String | `''` | Center title text |
| `showBack` | Boolean | `false` | Show back arrow button on left |
| `showMenu` | Boolean | `false` | Show hamburger menu on left (ignored if showBack is true) |
| `actions` | Array | `[]` | Right-side action buttons |
| `selectionActive` | Boolean | `false` | Enable selection mode styling |
### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `menu-click` | - | Hamburger menu clicked |
| `back-click` | - | Back arrow clicked |
| `action-click` | `action` | Action button clicked |
### Action Object Structure
```typescript
interface TopBarAction {
icon: string // PrimeIcons class (e.g., 'pi pi-filter')
label?: string // Accessibility label
tooltip?: string // Tooltip text
active?: boolean // Highlight when active
}
```
### Basic Usage
```vue
<template>
<MobileTopBar
title="Bonuri Fiscale"
:showMenu="true"
:actions="topBarActions"
@menu-click="showSidebar = true"
@action-click="handleAction"
/>
</template>
<script setup>
const topBarActions = [
{ icon: 'pi pi-filter', label: 'Filtrează', tooltip: 'Deschide filtre' },
{ icon: 'pi pi-search', label: 'Caută', tooltip: 'Căutare' }
]
const handleAction = (action) => {
if (action.icon === 'pi pi-filter') {
openFilterSheet()
}
}
</script>
```
### With Back Navigation
```vue
<MobileTopBar
title="Detalii Bon"
:showBack="true"
@back-click="router.back()"
/>
```
### Selection Mode
```vue
<MobileTopBar
:title="selectedCount > 0 ? `${selectedCount} selectate` : 'Bonuri'"
:showBack="selectedCount > 0"
:selectionActive="selectedCount > 0"
:actions="selectionActions"
@back-click="clearSelection"
/>
```
### CSS Classes
| Class | Description |
|-------|-------------|
| `.mobile-top-bar` | Base container (fixed, 56px height) |
| `.selection-active` | Blue background for selection mode |
| `.top-bar-left` | Left button container |
| `.top-bar-right` | Right action buttons container |
| `.top-bar-title` | Center title (ellipsis on overflow) |
| `.top-bar-btn` | Individual button (48x48px touch target) |
| `.top-bar-btn.active` | Highlighted state |
---
## MobileBottomNav
Material Design 3 inspired bottom navigation bar with router integration.
### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `items` | Array | Default nav items | Navigation items array |
### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `item-click` | `item` | Non-router item clicked |
### Item Object Structure
```typescript
interface NavItem {
to?: string // Route path (uses router-link when provided)
icon: string // PrimeIcons class (e.g., 'pi pi-receipt')
label: string // Display text
active?: boolean // Force active state (for non-router items)
}
```
### Default Items
```javascript
[
{ to: '/data-entry', icon: 'pi pi-receipt', label: 'Bonuri' },
{ icon: 'pi pi-cloud-upload', label: 'Upload' }, // Action button
{ to: '/reports/dashboard', icon: 'pi pi-chart-bar', label: 'Rapoarte' },
{ to: '/settings', icon: 'pi pi-cog', label: 'Setări' } // → Settings Hub
]
```
### Basic Usage
```vue
<template>
<MobileBottomNav />
</template>
```
### Custom Items
```vue
<template>
<MobileBottomNav
:items="customItems"
@item-click="handleNavClick"
/>
</template>
<script setup>
const customItems = [
{ to: '/home', icon: 'pi pi-home', label: 'Acasă' },
{ to: '/reports', icon: 'pi pi-chart-bar', label: 'Rapoarte' },
{ icon: 'pi pi-plus', label: 'Adaugă' }, // Button, not route
{ to: '/settings', icon: 'pi pi-cog', label: 'Setări' }
]
const handleNavClick = (item) => {
if (item.label === 'Adaugă') {
showAddDialog()
}
}
</script>
```
### CSS Classes
| Class | Description |
|-------|-------------|
| `.mobile-bottom-nav` | Base container (fixed, 56px height) |
| `.bottom-nav-item` | Individual nav item (flex: 1) |
| `.bottom-nav-item.active` | Active state (primary color) |
| `.bottom-nav-item.router-link-active` | Auto-active via Vue Router |
---
## MobileDrawerMenu
*(UPDATED - Phase 3)*
Material Design 3 inspired navigation drawer that slides in from the left. Replaces the desktop sidebar on mobile devices. **Phase 3** reorganized navigation into grouped categories with visual separators.
### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `modelValue` | Boolean | `false` | Controls visibility (v-model support) |
| `user` | Object | `null` | User object for profile display `{ username: string }` |
| `onLogout` | Function | `null` | Optional logout callback; emits `logout` event if not provided |
### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `update:modelValue` | `boolean` | v-model update when visibility changes |
| `logout` | - | Emitted when logout is clicked (if no `onLogout` prop) |
### Navigation Items (Phase 3 - Grouped Categories)
The drawer navigation is organized into 4 sections with visual separators:
```javascript
// PRINCIPALE Section
const principaleItems = [
{ to: '/dashboard', icon: 'pi pi-home', label: 'Dashboard', exactMatch: true },
{ to: '/data-entry', icon: 'pi pi-receipt', label: 'Bonuri', exactMatch: false }
]
// RAPOARTE Section
const rapoarteItems = [
{ to: '/reports/invoices', icon: 'pi pi-file', label: 'Facturi', exactMatch: true },
{ to: '/reports/trial-balance', icon: 'pi pi-calculator', label: 'Balanță', exactMatch: true },
{ to: '/reports/bank-cash', icon: 'pi pi-money-bill', label: 'Casa și Banca', exactMatch: true }
]
// ANALIZE Section
const analizeItems = [
{ to: '/reports/maturity-analysis', icon: 'pi pi-clock', label: 'Scadențe', exactMatch: true },
{ to: '/reports/detailed-invoices', icon: 'pi pi-list', label: 'Facturi Detaliate', exactMatch: true }
]
// ADMINISTRARE Section
const administrareItems = [
{ to: '/settings', icon: 'pi pi-cog', label: 'Setări', exactMatch: false }
]
```
### Section Structure
```
┌──────────────────┐
│ 🏢 ROA2WEB │ ← Header with logo
├──────────────────┤
│ PRINCIPALE │ ← Section header (uppercase)
│ 🏠 Dashboard │
│ 📋 Bonuri │
│ ────────────── │ ← Divider
│ RAPOARTE │
│ 📄 Facturi │
│ 🔢 Balanță │
│ 💰 Casa și Banca│
│ ────────────── │
│ ANALIZE │
│ ⏰ Scadențe │
│ 📋 Facturi Det. │
│ ────────────── │
│ ADMINISTRARE │
│ ⚙️ Setări │
├──────────────────┤
│ 👤 Username │ ← Profile section (footer)
│ 🚪 Deconectare │
└──────────────────┘
```
### Basic Usage
```vue
<template>
<MobileTopBar
title="Dashboard"
:showMenu="true"
@menu-click="showDrawer = true"
/>
<MobileDrawerMenu
v-model="showDrawer"
:user="authStore.user"
@logout="handleLogout"
/>
</template>
<script setup>
import { ref } from 'vue'
import MobileDrawerMenu from '@shared/components/mobile/MobileDrawerMenu.vue'
import { useAuthStore } from '@/stores/auth'
const showDrawer = ref(false)
const authStore = useAuthStore()
const handleLogout = async () => {
await authStore.logout()
router.push('/login')
}
</script>
```
### With Logout Callback
```vue
<MobileDrawerMenu
v-model="showDrawer"
:user="{ username: 'John Doe' }"
:onLogout="async () => {
await api.logout()
router.push('/login')
}"
/>
```
### Features
| Feature | Description |
|---------|-------------|
| **Slide Animation** | Slides in from left with overlay fade |
| **Close on Tap Outside** | Tapping the overlay closes the drawer |
| **Close on Navigation** | Drawer closes automatically when a link is clicked |
| **Active State** | Current route is highlighted |
| **Profile Section** | Displays username and logout button at bottom |
| **Teleported** | Rendered to `<body>` to avoid z-index issues |
| **Dark Mode** | Full support for light/dark themes |
### CSS Classes
| Class | Description |
|-------|-------------|
| `.drawer-overlay` | Full-screen backdrop (50% black, 70% in dark mode) |
| `.drawer-menu` | Main drawer container (280px, max 85vw) |
| `.drawer-header` | Header with logo |
| `.drawer-sections` | Scrollable container for all navigation sections |
| `.drawer-section` | Individual section container |
| `.section-header` | Section header label (PRINCIPALE, RAPOARTE, etc.) - uppercase, small text |
| `.drawer-nav` | List container for navigation links |
| `.drawer-link` | Individual navigation link (48px min-height) |
| `.drawer-link.active` | Active route styling (blue background) |
| `.drawer-divider` | Visual separator between sections |
| `.drawer-profile` | Profile section at bottom |
| `.profile-header` | User avatar and name row |
| `.profile-avatar` | Circular avatar with user icon |
| `.logout-link` | Red logout button styling |
### Section Header Styling
```css
.section-header {
padding: var(--space-sm) var(--space-lg);
font-size: var(--text-xs);
font-weight: var(--font-semibold);
color: var(--text-color-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.drawer-divider {
height: 1px;
background: var(--surface-border);
margin: var(--space-xs) var(--space-md);
}
```
### Animation
Uses Vue `<Transition name="drawer">`:
- Overlay fades in/out
- Drawer slides from left (`translateX(-100%)``translateX(0)`)
- Duration: `var(--transition-normal)` (250ms)
---
## Mobile Tab Pattern
*(NEW - Phase 3)*
Material Design 3 inspired full-width tabs for switching between Clienți and Furnizori views. Used in pages that display partner-related data.
### When to Use
Use the Mobile Tab Pattern when:
- A page shows data that can be filtered by partner type (Clienți/Furnizori)
- The tab switch should be persistent via URL query parameter
- Both tabs share the same filters (except type)
### Implementation
```vue
<template>
<!-- US-304/US-305: Mobile Tabs for Clienți/Furnizori -->
<div v-if="isMobile" class="mobile-tabs-container">
<div class="mobile-tabs">
<button
class="mobile-tab"
:class="{ active: activeTab === 'clients' }"
@click="switchTab('clients')"
>
<span class="tab-label">Clienți</span>
</button>
<button
class="mobile-tab"
:class="{ active: activeTab === 'suppliers' }"
@click="switchTab('suppliers')"
>
<span class="tab-label">Furnizori</span>
</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
// Tab state synced with URL query param
const activeTab = ref(route.query.tab === 'suppliers' ? 'suppliers' : 'clients')
// Switch tab and update URL
const switchTab = (tab) => {
if (tab === activeTab.value) return
activeTab.value = tab
// Update URL query param without full navigation
router.replace({
query: { ...route.query, tab: tab === 'clients' ? undefined : tab }
})
// Trigger data reload with new type
loadData()
}
</script>
```
### CSS Classes
```css
/* Tab container - positioned below MobileTopBar */
.mobile-tabs-container {
position: fixed;
top: 56px; /* Below MobileTopBar */
left: 0;
right: 0;
z-index: 999;
background: var(--surface-card);
border-bottom: 1px solid var(--surface-border);
}
.mobile-tabs {
display: flex;
width: 100%;
}
.mobile-tab {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-md) var(--space-sm);
background: transparent;
border: none;
border-bottom: 2px solid transparent;
cursor: pointer;
transition: all var(--transition-fast);
color: var(--text-color-secondary);
min-height: 48px;
font-size: var(--text-base);
font-weight: var(--font-medium);
}
.mobile-tab:active {
background: var(--surface-hover);
}
.mobile-tab.active {
color: var(--color-primary);
border-bottom-color: var(--color-primary);
font-weight: var(--font-semibold);
}
```
### Content Padding
When using tabs, increase top padding to account for both bars:
```css
.mobile-content-with-tabs {
padding-top: 104px; /* 56px (TopBar) + 48px (Tabs) */
padding-bottom: 56px; /* BottomNav */
}
```
### Used In
| Page | Route | Description |
|------|-------|-------------|
| **Facturi** | `/reports/invoices` | Switch between client/supplier invoices |
| **Scadențe** | `/reports/maturity-analysis` | Switch between client/supplier maturity |
---
## FAB Pattern
*(NEW - Phase 3)*
Material Design 3 inspired Floating Action Button (FAB) for contextual actions. Positioned above MobileBottomNav with popup menu support.
### When to Use
Use the FAB Pattern when:
- A page has primary actions that benefit from prominent placement
- Multiple related actions can be grouped (popup menu)
- Actions were previously in footer/header but need better mobile UX
### Design Tokens
```javascript
// From PRD cssRules.mobileLayoutTokens
{
fabSize: '56px', // FAB diameter
fabBottomOffset: '72px', // 56px (nav) + 16px spacing
touchTargetMin: '48px' // Minimum touch target
}
```
### Implementation
```vue
<template>
<!-- FAB Button -->
<Transition name="fab-slide">
<button
v-if="isMobile && !selectionMode && fabVisible"
class="mobile-fab"
:class="{ 'fab-active': fabMenuOpen }"
@click="toggleFabMenu"
aria-label="Meniu acțiuni"
aria-haspopup="true"
:aria-expanded="fabMenuOpen"
>
<i class="pi pi-plus" :class="{ 'fab-icon-rotate': fabMenuOpen }"></i>
</button>
</Transition>
<!-- FAB Popup Menu -->
<Menu
ref="fabMenuRef"
:model="fabMenuItems"
:popup="true"
class="fab-popup-menu"
/>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import Menu from 'primevue/menu'
const router = useRouter()
const fabMenuRef = ref(null)
const fabMenuOpen = ref(false)
const fabVisible = ref(true)
// FAB Menu Items
const fabMenuItems = [
{
label: 'Bon Nou',
icon: 'pi pi-plus',
command: () => router.push('/data-entry/receipts/new')
},
{
label: 'Upload Bulk',
icon: 'pi pi-cloud-upload',
command: () => openBulkUpload()
}
]
// Toggle FAB Menu
const toggleFabMenu = (event) => {
fabMenuOpen.value = !fabMenuOpen.value
fabMenuRef.value?.toggle(event)
}
// Hide FAB during scroll, show when scroll stops
let lastScrollY = 0
const handleScroll = () => {
const currentScrollY = window.scrollY
const scrollDelta = currentScrollY - lastScrollY
if (scrollDelta > 10) {
fabVisible.value = false // Scrolling down - hide
} else if (scrollDelta < -10) {
fabVisible.value = true // Scrolling up - show
}
lastScrollY = currentScrollY
// Show after scroll stops (300ms)
clearTimeout(scrollTimeout)
scrollTimeout = setTimeout(() => {
fabVisible.value = true
}, 300)
}
</script>
```
### CSS Classes
```css
/* FAB Button */
.mobile-fab {
position: fixed;
bottom: 72px; /* 56px nav + 16px spacing */
right: var(--space-md);
width: 56px;
height: 56px;
border-radius: var(--radius-full);
background: var(--color-primary);
color: white;
border: none;
display: flex;
align-items: center;
justify-content: center;
box-shadow: var(--shadow-lg);
cursor: pointer;
z-index: 999;
transition: all var(--transition-fast);
}
.mobile-fab:active {
transform: scale(0.95);
box-shadow: var(--shadow-md);
}
.mobile-fab i {
font-size: var(--text-2xl);
transition: transform var(--transition-fast);
}
/* FAB active state when menu open */
.mobile-fab.fab-active {
background: var(--color-primary-dark);
}
.mobile-fab .fab-icon-rotate {
transform: rotate(45deg);
}
/* FAB Popup Menu positioning */
.fab-popup-menu {
position: fixed !important;
bottom: 140px !important; /* 72px + 56px + 12px */
right: var(--space-md) !important;
left: auto !important;
top: auto !important;
min-width: 180px;
z-index: 1000 !important;
}
/* FAB slide animation */
.fab-slide-enter-active,
.fab-slide-leave-active {
transition: transform var(--transition-normal), opacity var(--transition-normal);
}
.fab-slide-enter-from,
.fab-slide-leave-to {
transform: translateY(100px);
opacity: 0;
}
```
### Behavior
| Interaction | Result |
|-------------|--------|
| **Tap FAB** | Opens popup menu with actions |
| **Tap menu item** | Executes action, closes menu |
| **Tap outside** | Closes menu |
| **Scroll down** | FAB hides (slide down) |
| **Scroll up** | FAB shows (slide up) |
| **Scroll stops** | FAB reappears after 300ms |
| **Selection mode** | FAB hidden (replaced by selection footer) |
### Used In
| Page | Route | Actions |
|------|-------|---------|
| **Bonuri** | `/data-entry` | Bon Nou, Upload Bulk |
> **Phase 3 Note:** Upload was moved from Footer Nav to FAB to reduce footer clutter and provide better contextual placement.
---
## MobileActionBar
Context-aware bottom action bar for forms and edit views. Positions above MobileBottomNav and displays action buttons based on current state.
### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `visible` | Boolean | `false` | Controls visibility (triggers slide-up animation) |
| `actions` | Array | `[]` | Array of action buttons to display |
### Action Object Structure
```typescript
interface ActionBarAction {
label: string // Button text (e.g., 'Salvează')
icon: string // PrimeIcons class (e.g., 'pi pi-save')
severity?: string // PrimeVue severity ('primary', 'secondary', 'danger', etc.)
handler: () => void // Click handler function
disabled?: boolean // Disable the button
}
```
### Layout Behavior
| Actions Count | Layout |
|---------------|--------|
| 1 button | Full-width |
| 2 buttons | Side-by-side, equal width |
| 3+ buttons | Equal distribution |
### Basic Usage
```vue
<template>
<MobileActionBar
:visible="isMobile"
:actions="formActions"
/>
</template>
<script setup>
import MobileActionBar from '@shared/components/mobile/MobileActionBar.vue'
const formActions = [
{
label: 'Salvează',
icon: 'pi pi-save',
severity: 'primary',
handler: handleSave
},
{
label: 'Anulează',
icon: 'pi pi-times',
severity: 'secondary',
handler: handleCancel
}
]
</script>
```
### Context-Aware Actions (Receipt Edit Example)
```vue
<template>
<MobileActionBar
:visible="isMobile"
:actions="contextAwareActions"
/>
</template>
<script setup>
import { computed } from 'vue'
const receiptStatus = ref('draft') // 'draft' | 'pending' | 'approved' | 'rejected'
const contextAwareActions = computed(() => {
switch (receiptStatus.value) {
case 'draft':
return [
{ label: 'Salvează', icon: 'pi pi-save', severity: 'secondary', handler: handleSave },
{ label: 'Trimite', icon: 'pi pi-send', severity: 'primary', handler: handleSubmit }
]
case 'pending':
return [
{ label: 'Salvează', icon: 'pi pi-save', severity: 'secondary', handler: handleSave },
{ label: 'Aprobă', icon: 'pi pi-check', severity: 'success', handler: handleApprove },
{ label: 'Respinge', icon: 'pi pi-times', severity: 'danger', handler: handleReject }
]
case 'approved':
return [] // No actions for approved receipts
case 'rejected':
return [
{ label: 'Salvează', icon: 'pi pi-save', severity: 'secondary', handler: handleSave },
{ label: 'Re-trimite', icon: 'pi pi-refresh', severity: 'primary', handler: handleResubmit }
]
default:
return []
}
})
</script>
```
### Hiding During Overlays
Hide the action bar when BottomSheet or other overlays are open:
```vue
<template>
<MobileActionBar
:visible="isMobile && !isBottomSheetOpen"
:actions="formActions"
/>
<BottomSheet v-model="isBottomSheetOpen">
<!-- Filter content -->
</BottomSheet>
</template>
```
### CSS Classes
| Class | Description |
|-------|-------------|
| `.mobile-action-bar` | Base container (fixed, bottom: 56px) |
| `.action-bar-content` | Button container (max-width: 500px) |
| `.layout-single` | Single button layout (full-width) |
| `.layout-dual` | Two button layout (50% each) |
| `.layout-multi` | Three+ button layout (equal distribution) |
| `.action-bar-btn` | Individual button (48px min-height) |
### Animation
Uses Vue `<Transition name="slide-up">`:
- Slides up from below screen (`translateY(100%)``translateY(0)`)
- Duration: `var(--transition-normal)` (250ms)
- Fades in/out simultaneously
### Positioning
```css
.mobile-action-bar {
position: fixed;
bottom: 56px; /* Above MobileBottomNav */
left: 0;
right: 0;
/* Safe area support for iPhone X+ */
bottom: calc(56px + env(safe-area-inset-bottom));
}
```
### Content Padding Adjustment
When MobileActionBar is visible, increase bottom padding:
```css
.mobile-content {
padding-bottom: 120px; /* 56px (nav) + 64px (action bar) */
}
```
---
## MobileSelectionFooter
Slide-up action bar for batch operations on selected items.
### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `visible` | Boolean | `false` | Controls visibility (triggers animation) |
| `actions` | Array | `[]` | Action buttons to display |
### Action Object Structure
```typescript
interface SelectionAction {
label: string // Button text
icon: string // PrimeIcons class
severity?: string // PrimeVue severity ('secondary', 'danger', etc.)
handler: () => void // Click handler function
}
```
### Basic Usage
```vue
<template>
<MobileSelectionFooter
:visible="selectedItems.length > 0"
:actions="selectionActions"
/>
</template>
<script setup>
const selectedItems = ref([])
const selectionActions = computed(() => [
{
label: 'Șterge',
icon: 'pi pi-trash',
severity: 'danger',
handler: deleteSelected
},
{
label: 'Export',
icon: 'pi pi-download',
severity: 'secondary',
handler: exportSelected
}
])
const deleteSelected = () => {
// Delete logic
selectedItems.value = []
}
const exportSelected = () => {
// Export logic
}
</script>
```
### Single Action
```vue
<MobileSelectionFooter
:visible="hasSelection"
:actions="[
{ label: 'Confirmă Selectate', icon: 'pi pi-check', severity: 'success', handler: confirmAll }
]"
/>
```
### CSS Classes
| Class | Description |
|-------|-------------|
| `.mobile-selection-footer` | Base container (fixed, z-index 1030) |
| `.selection-actions` | Button container (max-width: 400px) |
| `.selection-action-btn` | Individual button (48px min-height) |
### Animation
Uses Vue `<Transition name="slide-up">`:
- Slides up from bottom when `visible` becomes `true`
- Slides down when `visible` becomes `false`
- Duration: `var(--transition-normal)` (250ms)
---
## BottomSheet
Modal-style sheet that slides up from the bottom, ideal for filters and forms.
### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `modelValue` | Boolean | `false` | v-model for visibility |
| `closeOnOverlay` | Boolean | `true` | Close when tapping overlay |
| `closeOnSwipeDown` | Boolean | `true` | Close when swiping down on handle |
### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `update:modelValue` | `boolean` | v-model update |
### Slots
| Slot | Description |
|------|-------------|
| `default` | Content inside the sheet |
### Basic Usage
```vue
<template>
<Button label="Open Filters" @click="showFilters = true" />
<BottomSheet v-model="showFilters">
<h3>Filtre</h3>
<div class="form-group">
<label class="form-label">Status</label>
<Dropdown v-model="filters.status" :options="statusOptions" />
</div>
<div class="form-group">
<label class="form-label">Data</label>
<Calendar v-model="filters.date" />
</div>
<div class="flex gap-sm mt-md">
<Button label="Resetează" severity="secondary" @click="resetFilters" />
<Button label="Aplică" @click="applyFilters" />
</div>
</BottomSheet>
</template>
<script setup>
const showFilters = ref(false)
const applyFilters = () => {
// Apply filter logic
showFilters.value = false
}
</script>
```
### Prevent Close on Overlay
```vue
<BottomSheet
v-model="showImportantForm"
:closeOnOverlay="false"
>
<!-- User must explicitly close this -->
</BottomSheet>
```
### CSS Classes
| Class | Description |
|-------|-------------|
| `.bottom-sheet-overlay` | Full-screen overlay (50% opacity black) |
| `.bottom-sheet` | Sheet container (max-height: 90vh) |
| `.bottom-sheet-handle` | Handle area at top (32px min-height) |
| `.handle-bar` | Visual drag indicator (40px × 4px) |
| `.bottom-sheet-content` | Scrollable content area |
### Touch Gestures
| Gesture | Action |
|---------|--------|
| Tap overlay | Close (if `closeOnOverlay: true`) |
| Tap handle | Close |
| Swipe down > 100px | Close (if `closeOnSwipeDown: true`) |
---
## SwipeableCards
Touch-swipeable carousel for KPI cards with dots indicator.
### Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `totalCards` | Number | **required** | Number of cards in carousel |
| `showDots` | Boolean | `true` | Show navigation dots |
| `autoPlay` | Boolean | `false` | Auto-advance cards |
| `autoPlayInterval` | Number | `5000` | Auto-play interval (ms) |
### Events
| Event | Payload | Description |
|-------|---------|-------------|
| `update:currentIndex` | `number` | Current card index changed |
### Slots
Named slots for each card: `card-0`, `card-1`, `card-2`, etc.
### Exposed Methods
| Method | Description |
|--------|-------------|
| `goToCard(index)` | Navigate to specific card |
| `nextCard()` | Go to next card |
| `prevCard()` | Go to previous card |
| `currentIndex` | Current card index (ref) |
### Basic Usage
```vue
<template>
<SwipeableCards :totalCards="3">
<template #card-0>
<div class="kpi-card">
<div class="kpi-value">$125,430</div>
<div class="kpi-label">Total Sales</div>
</div>
</template>
<template #card-1>
<div class="kpi-card">
<div class="kpi-value">1,234</div>
<div class="kpi-label">Orders</div>
</div>
</template>
<template #card-2>
<div class="kpi-card">
<div class="kpi-value">98.5%</div>
<div class="kpi-label">Success Rate</div>
</div>
</template>
</SwipeableCards>
</template>
```
### With Auto-Play
```vue
<SwipeableCards
:totalCards="4"
:autoPlay="true"
:autoPlayInterval="3000"
>
<!-- Cards -->
</SwipeableCards>
```
### Programmatic Navigation
```vue
<template>
<SwipeableCards ref="carousel" :totalCards="3">
<!-- Cards -->
</SwipeableCards>
<Button label="Next" @click="carousel.nextCard()" />
</template>
<script setup>
const carousel = ref(null)
</script>
```
### CSS Classes
| Class | Description |
|-------|-------------|
| `.swipeable-cards-container` | Base container with overflow hidden |
| `.cards-track` | Horizontal flex container (will-change: transform) |
| `.card-slide` | Individual card (flex: 0 0 100%) |
| `.dots-indicator` | Navigation dots container |
| `.dot` | Individual dot (8px, expands to 24px when active) |
| `.dot.active` | Active dot (primary color) |
### Touch Thresholds
| Threshold | Value | Description |
|-----------|-------|-------------|
| Swipe distance | 50px | Minimum swipe to change card |
| Velocity | 0.3 | Quick swipe detection |
| Angle | 30° | Max angle for horizontal detection |
---
## Design Tokens for Mobile
### Layout Tokens
| Token | Value | Use |
|-------|-------|-----|
| `--header-height` | 56px | MobileTopBar & MobileBottomNav height |
| `--z-fixed` | 1030 | Fixed elements z-index |
| `--z-modal-backdrop` | 1040 | BottomSheet overlay |
| `--z-modal` | 1050 | BottomSheet content |
### Touch Target Sizes
```css
/* Minimum touch target: 48x48px */
.touch-target {
min-width: 48px;
min-height: 48px;
}
/* Button in top bar */
.top-bar-btn {
width: 48px;
height: 48px;
}
```
### Spacing for Mobile
| Token | Value | Mobile Use |
|-------|-------|------------|
| `var(--space-xs)` | 4px | Icon gaps, tight spacing |
| `var(--space-sm)` | 8px | Between nav items, card gaps |
| `var(--space-md)` | 16px | Card padding, content margins |
| `var(--space-lg)` | 24px | Section spacing |
### Mobile Content Padding
```css
/* Standard mobile content area */
.mobile-content {
padding-top: 56px; /* MobileTopBar */
padding-bottom: 56px; /* MobileBottomNav */
padding-left: var(--space-md);
padding-right: var(--space-md);
}
/* When selection footer is visible */
.mobile-content-with-selection {
padding-bottom: 80px; /* Higher for selection footer */
}
```
---
## Best Practices
### 1. Always Use Design Tokens
```css
/* ✅ CORRECT */
.mobile-card {
padding: var(--space-md);
border-radius: var(--radius-md);
box-shadow: var(--shadow-sm);
}
/* ❌ WRONG */
.mobile-card {
padding: 16px;
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
```
### 2. Account for Fixed Headers/Footers
```vue
<template>
<div class="view-container">
<MobileTopBar title="My View" />
<main class="view-content">
<!-- Content that scrolls -->
</main>
<MobileBottomNav />
</div>
</template>
<style scoped>
.view-content {
padding-top: 56px; /* TopBar height */
padding-bottom: 56px; /* BottomNav height */
min-height: 100vh;
overflow-y: auto;
}
</style>
```
### 3. Test Both Light and Dark Mode
All mobile components support:
- Manual dark mode: `[data-theme="dark"]`
- System preference: `@media (prefers-color-scheme: dark)`
```javascript
// Toggle theme in DevTools:
document.documentElement.setAttribute('data-theme', 'dark')
```
### 4. Handle Safe Areas (Notch/Home Indicator)
```css
.mobile-bottom-nav {
padding-bottom: env(safe-area-inset-bottom);
}
.mobile-content {
padding-bottom: calc(56px + env(safe-area-inset-bottom));
}
```
### 5. Selection Mode Pattern
```vue
<script setup>
const selectedItems = ref([])
const isSelectionMode = computed(() => selectedItems.value.length > 0)
const toggleSelection = (item) => {
const index = selectedItems.value.findIndex(i => i.id === item.id)
if (index === -1) {
selectedItems.value.push(item)
} else {
selectedItems.value.splice(index, 1)
}
}
const clearSelection = () => {
selectedItems.value = []
}
</script>
```
### 6. Combine Components for Complex Views
```vue
<template>
<!-- Top Bar adapts to selection mode -->
<MobileTopBar
:title="isSelectionMode ? `${selectedCount} selectate` : 'Bonuri'"
:showBack="isSelectionMode"
:selectionActive="isSelectionMode"
:actions="currentActions"
@back-click="clearSelection"
@action-click="handleAction"
/>
<!-- Main Content -->
<main class="content">
<SwipeableCards :totalCards="kpis.length">
<template v-for="(kpi, i) in kpis" #[`card-${i}`]>
<KPICard :data="kpi" />
</template>
</SwipeableCards>
<ItemList :items="items" @select="toggleSelection" />
</main>
<!-- Bottom Nav (hidden in selection mode) -->
<MobileBottomNav v-if="!isSelectionMode" />
<!-- Selection Footer (shown in selection mode) -->
<MobileSelectionFooter
:visible="isSelectionMode"
:actions="selectionActions"
/>
<!-- Filter Sheet -->
<BottomSheet v-model="showFilters">
<FilterForm @apply="applyFilters" />
</BottomSheet>
</template>
```
---
## Troubleshooting
### Top/Bottom bars not fixed
**Problem**: Bars scroll with content.
**Solution**: Ensure `.mobile-top-bar` and `.mobile-bottom-nav` have `position: fixed`. Check for `overflow: hidden` on ancestors.
### Content hidden behind bars
**Problem**: Content is cut off at top/bottom.
**Solution**: Add padding to content area:
```css
.content {
padding-top: 56px;
padding-bottom: 56px;
}
```
### BottomSheet z-index issues
**Problem**: BottomSheet appears behind other elements.
**Solution**: BottomSheet uses `<Teleport to="body">` to avoid stacking context issues. Check for `z-index` wars in your CSS.
### Swipe conflicts with scrolling
**Problem**: SwipeableCards interferes with vertical scroll.
**Solution**: Component uses angle detection (30° threshold). Ensure cards have sufficient content area. Check `touch-action: pan-y` is set.
### Dark mode not working
**Problem**: Components don't respond to theme changes.
**Solution**: All components support both mechanisms:
1. Manual: `[data-theme="dark"]` on `<html>`
2. System: `@media (prefers-color-scheme: dark)`
Check that `data-theme` attribute is set correctly.
### Selection footer overlaps content
**Problem**: When selection footer appears, it covers list items.
**Solution**: Use dynamic padding:
```css
.content {
padding-bottom: v-bind(isSelectionMode ? '96px' : '56px');
}
```
---
## Related Documentation
- [Design Tokens](./DESIGN_TOKENS.md) - Color, spacing, typography tokens
- [CSS Patterns](./CSS_PATTERNS.md) - General UI patterns
- [Onboarding CSS](./ONBOARDING_CSS.md) - Quick start for CSS
---
## Changelog
### Version 3.0.0 (2026-01-12) - Phase 3: Mobile Navigation Overhaul
- **NEW**: Added `Mobile Tab Pattern` section for Clienți/Furnizori switching (US-304, US-305)
- **NEW**: Added `FAB Pattern` section for contextual actions (US-303)
- **UPDATED**: `MobileBottomNav` - 4 direct links: Dashboard, Bonuri, Facturi, Setări (US-307)
- **UPDATED**: `MobileDrawerMenu` - Grouped categories: PRINCIPALE, RAPOARTE, ANALIZE, ADMINISTRARE (US-308)
- **UPDATED**: Dashboard mobile layout - KPIs only, quick-links removed (US-309)
- **UPDATED**: ASCII diagrams with new navigation structure and FAB/Tab patterns
- **UPDATED**: Navigation Architecture with Phase 3 route map
- **REMOVED**: Upload button from footer nav (moved to FAB on Bonuri page)
### Version 2.0.0 (2026-01-12)
- **NEW**: Added `MobileDrawerMenu` component documentation
- **NEW**: Added `MobileActionBar` component documentation
- **NEW**: Added Navigation Architecture section with route maps
- **UPDATED**: MobileBottomNav default items now point to Settings Hub (`/settings`)
- **UPDATED**: ASCII diagrams with new routes and component layouts
- **UPDATED**: Quick Start section to include drawer menu integration
### Version 1.0.0 (2026-01-11)
- Initial release with MobileTopBar, MobileBottomNav, MobileSelectionFooter, BottomSheet, SwipeableCards
---
**Last Updated:** 2026-01-12
**Version:** 3.0.0
**Maintained By:** Frontend Team