# 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 ``` ### 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 ``` ### With Back Navigation ```vue ``` ### Selection Mode ```vue ``` ### 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 ``` ### Custom Items ```vue ``` ### 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 ``` ### With Logout Callback ```vue ``` ### 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 `` 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 ``: - 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 ``` ### 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 ``` ### 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 ``` ### Context-Aware Actions (Receipt Edit Example) ```vue ``` ### Hiding During Overlays Hide the action bar when BottomSheet or other overlays are open: ```vue ``` ### 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 ``: - 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 ``` ### Single Action ```vue ``` ### 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 ``: - 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 ``` ### Prevent Close on Overlay ```vue ``` ### 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 ``` ### With Auto-Play ```vue ``` ### Programmatic Navigation ```vue ``` ### 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 ``` ### 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 ``` ### 6. Combine Components for Complex Views ```vue ``` --- ## 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 `` 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 `` 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