Implemented by Ralph autonomous loop. Iteration: 9 Co-Authored-By: Claude <noreply@anthropic.com>
59 KiB
ROA2WEB Mobile Patterns Library
Version: 3.0.0 Last Updated: 2026-01-12 Status: ✅ Complete (Phase 3 - Mobile Navigation Overhaul)
Table of Contents
- Quick Start
- Mobile Layout Overview
- Navigation Architecture
- MobileTopBar
- MobileBottomNav (UPDATED - Phase 3)
- MobileDrawerMenu (UPDATED - Phase 3)
- Mobile Tab Pattern (NEW - Phase 3)
- FAB Pattern (NEW - Phase 3)
- MobileActionBar
- MobileSelectionFooter
- BottomSheet
- SwipeableCards
- Design Tokens for Mobile
- Best Practices
- Troubleshooting
Quick Start
For New Developers
Get a mobile view running in 5 minutes:
<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/:
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:
/dashboardis 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)
// 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)
// 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)
exactMatchflag 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
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
<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
<MobileTopBar
title="Detalii Bon"
:showBack="true"
@back-click="router.back()"
/>
Selection Mode
<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
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
[
{ 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
<template>
<MobileBottomNav />
</template>
Custom Items
<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:
// 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
<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
<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
.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
<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
/* 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:
.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
// From PRD cssRules.mobileLayoutTokens
{
fabSize: '56px', // FAB diameter
fabBottomOffset: '72px', // 56px (nav) + 16px spacing
touchTargetMin: '48px' // Minimum touch target
}
Implementation
<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
/* 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
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
<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)
<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:
<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
.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:
.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
interface SelectionAction {
label: string // Button text
icon: string // PrimeIcons class
severity?: string // PrimeVue severity ('secondary', 'danger', etc.)
handler: () => void // Click handler function
}
Basic Usage
<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
<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
visiblebecomestrue - Slides down when
visiblebecomesfalse - 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
<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
<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
<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
<SwipeableCards
:totalCards="4"
:autoPlay="true"
:autoPlayInterval="3000"
>
<!-- Cards -->
</SwipeableCards>
Programmatic Navigation
<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
/* 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
/* 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
/* ✅ 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
<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)
// Toggle theme in DevTools:
document.documentElement.setAttribute('data-theme', 'dark')
4. Handle Safe Areas (Notch/Home Indicator)
.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
<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
<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:
.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:
- Manual:
[data-theme="dark"]on<html> - 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:
.content {
padding-bottom: v-bind(isSelectionMode ? '96px' : '56px');
}
Related Documentation
- Design Tokens - Color, spacing, typography tokens
- CSS Patterns - General UI patterns
- Onboarding CSS - Quick start for CSS
Changelog
Version 3.0.0 (2026-01-12) - Phase 3: Mobile Navigation Overhaul
- NEW: Added
Mobile Tab Patternsection for Clienți/Furnizori switching (US-304, US-305) - NEW: Added
FAB Patternsection 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
MobileDrawerMenucomponent documentation - NEW: Added
MobileActionBarcomponent 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