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

42 KiB
Raw Blame History

ROA2WEB Mobile Patterns Library

Version: 2.0.0 Last Updated: 2026-01-12 Status: Complete


Table of Contents

  1. Quick Start
  2. Mobile Layout Overview
  3. Navigation Architecture
  4. MobileTopBar
  5. MobileBottomNav
  6. MobileDrawerMenu (NEW)
  7. MobileActionBar (NEW)
  8. MobileSelectionFooter
  9. BottomSheet
  10. SwipeableCards
  11. Design Tokens for Mobile
  12. Best Practices
  13. 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

┌─────────────────────────────────────────┐
│         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)

// 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
]
// 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' }
]

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

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

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:

[
  { 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

<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)
.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

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 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

<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:

  1. Manual: [data-theme="dark"] on <html>
  2. System: @media (prefers-color-scheme: dark)

Check that data-theme attribute is set correctly.

Problem: When selection footer appears, it covers list items.

Solution: Use dynamic padding:

.content {
  padding-bottom: v-bind(isSelectionMode ? '96px' : '56px');
}


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