Files
roa2web-service-auto/docs/MOBILE_PATTERNS.md
Claude Agent b7f442ed33 feat(mobile-navigation-improvements): Complete US-214 - Actualizare Documentație MOBILE_PATTERNS.md
Implemented by Ralph autonomous loop.
Iteration: 14

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

1376 lines
42 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:** 2.0.0
**Last Updated:** 2026-01-12
**Status:** ✅ Complete
---
## 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)
6. [MobileDrawerMenu](#mobiledrawermenu) *(NEW)*
7. [MobileActionBar](#mobileactionbar) *(NEW)*
8. [MobileSelectionFooter](#mobileselectionfooter)
9. [BottomSheet](#bottomsheet)
10. [SwipeableCards](#swipeablecards)
11. [Design Tokens for Mobile](#design-tokens-for-mobile)
12. [Best Practices](#best-practices)
13. [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
```
┌─────────────────────────────────────────┐
│ MobileTopBar (56px) │
│ [≡] Page Title [🔍] [⋮] │
├─────────────────────────────────────────┤
│ │
│ │
│ MAIN CONTENT │
│ │
│ (scrollable area) │
│ │
│ padding-top: 56px │
│ padding-bottom: 56px │
│ │
│ │
├─────────────────────────────────────────┤
│ MobileBottomNav (56px) │
│ 📋 ☁️ 📊 ⚙️ │
│ Bonuri Upload Rapoarte Setări │
└─────────────────────────────────────────┘
```
### ASCII Diagram: Drawer Menu Open
```
┌──────────────────┬──────────────────────┐
│ MobileDrawer │ ░░░░░░░░░░░░░░░░░░░░ │
│ Menu (280px) │ ░░░░░░░░░░░░░░░░░░░░ │
├──────────────────┤ ░░░ OVERLAY ░░░░░░░░ │
│ 🏢 ROA2WEB │ ░░ (tap to close) ░░ │
├──────────────────┤ ░░░░░░░░░░░░░░░░░░░░ │
│ 🏠 Dashboard │ ░░░░░░░░░░░░░░░░░░░░ │
│ 📋 Bonuri │ ░░░░░░░░░░░░░░░░░░░░ │
│ 📄 Facturi │ ░░░░░░░░░░░░░░░░░░░░ │
│ 🔢 Balanță │ ░░░░░░░░░░░░░░░░░░░░ │
│ 💰 Trezorerie │ ░░░░░░░░░░░░░░░░░░░░ │
│ ⚙️ Setări │ ░░░░░░░░░░░░░░░░░░░░ │
├──────────────────┤ ░░░░░░░░░░░░░░░░░░░░ │
│ 👤 Username │ ░░░░░░░░░░░░░░░░░░░░ │
│ 🚪 Deconectare │ ░░░░░░░░░░░░░░░░░░░░ │
└──────────────────┴──────────────────────┘
```
### 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)
└─────────────────────────────────────────┘
```
---
## Navigation Architecture
### Route Map
```
┌─────────────────────────────────────┐
│ / (root) │
│ Redirects to /reports/dashboard │
└───────────────┬─────────────────────┘
┌───────────────────────────┼───────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────────┐ ┌───────────────────────┐ ┌─────────────────┐
│ /data-entry │ │ /reports │ │ /settings │
│ │ │ │ │ │
│ 📋 Bonuri List │ │ 📊 dashboard │ │ ⚙️ Settings Hub │
│ 📝 Bon Create │ │ 📄 invoices │ │ (centralized │
│ ✏️ Bon Edit │ │ 💰 bank-cash │ │ settings) │
│ 📊 OCR Metrics │ │ 🔢 trial-balance │ │ │
│ │ │ 📉 maturity-analysis │ │ Links to: │
│ │ │ 📋 detailed-invoices │ │ - OCR Settings │
│ │ │ 📈 cache-stats │ │ - Cache Stats │
│ │ │ 📝 server-logs │ │ - Server Logs │
│ │ │ 📱 telegram │ │ - Telegram │
└───────────────────┘ └───────────────────────┘ └─────────────────┘
```
### 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 (Updated)
```javascript
// Default navigation 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
]
```
### MobileDrawerMenu Navigation Links
```javascript
// Drawer menu navigation items
[
{ to: '/reports/dashboard', icon: 'pi pi-home', label: 'Dashboard' },
{ to: '/data-entry', icon: 'pi pi-receipt', label: 'Bonuri' },
{ to: '/reports/invoices', icon: 'pi pi-file', label: 'Facturi' },
{ to: '/reports/trial-balance', icon: 'pi pi-calculator', label: 'Balanță' },
{ to: '/reports/bank-cash', icon: 'pi pi-money-bill', label: 'Trezorerie' },
{ to: '/settings', icon: 'pi pi-cog', label: 'Setări' }
]
```
### Dashboard Mobile Quick Links
On mobile, Dashboard shows KPI cards plus quick-link cards:
```
┌─────────────────────────────────────────┐
│ MobileTopBar: Dashboard │
├─────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────┐ │
│ │ SwipeableCards (KPIs) │ │
│ │ ← Swipe → │ │
│ └─────────────────────────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 📉 Analiză │ │ 📋 Facturi │ │
│ │ Scadențe → │ │ Detaliate → │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ (Quick links to dedicated pages) │
│ │
├─────────────────────────────────────────┤
│ MobileBottomNav │
└─────────────────────────────────────────┘
```
---
## 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
Material Design 3 inspired navigation drawer that slides in from the left. Replaces the desktop sidebar on mobile devices.
### 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 (Built-in)
The drawer contains the following navigation links:
```javascript
[
{ to: '/reports/dashboard', icon: 'pi pi-home', label: 'Dashboard' },
{ to: '/data-entry', icon: 'pi pi-receipt', label: 'Bonuri' },
{ to: '/reports/invoices', icon: 'pi pi-file', label: 'Facturi' },
{ to: '/reports/trial-balance', icon: 'pi pi-calculator', label: 'Balanță' },
{ to: '/reports/bank-cash', icon: 'pi pi-money-bill', label: 'Trezorerie' },
{ to: '/settings', icon: 'pi pi-cog', label: 'Setări' }
]
```
### 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) |
| `.drawer-menu` | Main drawer container (280px, max 85vw) |
| `.drawer-header` | Header with logo |
| `.drawer-section` | Navigation links container |
| `.drawer-link` | Individual navigation link (48px min-height) |
| `.drawer-link.active` | Active route styling (blue background) |
| `.drawer-profile` | Profile section at bottom |
| `.logout-link` | Red logout button styling |
### Animation
Uses Vue `<Transition name="drawer">`:
- Overlay fades in/out
- Drawer slides from left (`translateX(-100%)``translateX(0)`)
- Duration: `var(--transition-normal)` (250ms)
---
## 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 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:** 2.0.0
**Maintained By:** Frontend Team