feat(unified-mobile-material-design): Complete US-101b - Creare MobileBottomNav.vue component

Implemented by Ralph autonomous loop.
Iteration: 2

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-01-12 09:47:50 +00:00
parent dfbaca2477
commit b86aa6c5d2
3 changed files with 189 additions and 2 deletions

View File

@@ -57,8 +57,8 @@
"router-link pentru navigare",
"npm run build passes"
],
"passes": false,
"notes": ""
"passes": true,
"notes": "Completed in iteration 2"
},
{
"id": "US-101c",

View File

@@ -13,3 +13,9 @@ Mon Jan 12 09:44:54 AM UTC 2026
[2026-01-12 09:45:00] Working on story: US-101a
[2026-01-12 09:45:00] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_1_US-101a.log)
[2026-01-12 09:46:30] SUCCESS: Story US-101a passed!
[2026-01-12 09:46:31] Changes committed
[2026-01-12 09:46:31] Progress: 1/20 stories completed
[2026-01-12 09:46:33] === Iteration 2/100 ===
[2026-01-12 09:46:33] Working on story: US-101b
[2026-01-12 09:46:33] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_2_US-101b.log)
[2026-01-12 09:47:50] SUCCESS: Story US-101b passed!

View File

@@ -0,0 +1,181 @@
<template>
<nav class="mobile-bottom-nav">
<template v-for="(item, index) in items" :key="index">
<!-- Router link for navigation items -->
<router-link
v-if="item.to"
:to="item.to"
class="bottom-nav-item"
:class="{ 'active': item.active }"
>
<i :class="item.icon"></i>
<span>{{ item.label }}</span>
</router-link>
<!-- Button for action items (no navigation) -->
<button
v-else
type="button"
class="bottom-nav-item"
:class="{ 'active': item.active }"
@click="$emit('item-click', item)"
>
<i :class="item.icon"></i>
<span>{{ item.label }}</span>
</button>
</template>
</nav>
</template>
<script setup>
/**
* MobileBottomNav - Material Design 3 inspired bottom navigation bar for mobile views
*
* Props:
* - items: Array of navigation items with { to?, icon, label, active? }
* - to: Optional route path (uses router-link when provided)
* - icon: PrimeIcons class (e.g., 'pi pi-receipt')
* - label: Display text for the nav item
* - active: Optional boolean to force active state
*
* Events:
* - item-click: Emitted when a button item (without `to`) is clicked
*
* Default items (4 links):
* - Bonuri (/data-entry)
* - Upload (action button)
* - Rapoarte (/reports/dashboard)
* - Setări (/data-entry/ocr-metrics)
*/
defineProps({
/**
* Array of navigation items
* Each item should have: { to?: string, icon: string, label: string, active?: boolean }
*/
items: {
type: Array,
default: () => [
{ to: '/data-entry', icon: 'pi pi-receipt', label: 'Bonuri' },
{ icon: 'pi pi-cloud-upload', label: 'Upload' },
{ to: '/reports/dashboard', icon: 'pi pi-chart-bar', label: 'Rapoarte' },
{ to: '/data-entry/ocr-metrics', icon: 'pi pi-cog', label: 'Setări' }
],
validator: (items) => {
return Array.isArray(items) && items.every(
item => typeof item.icon === 'string' && typeof item.label === 'string'
)
}
}
})
defineEmits(['item-click'])
</script>
<style scoped>
/* ================================================
MobileBottomNav Component Styles
Material Design 3 inspired bottom navigation
================================================ */
.mobile-bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 56px;
background: var(--surface-card);
border-top: 1px solid var(--surface-border);
display: flex;
align-items: stretch;
justify-content: space-around;
z-index: 1000;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.08);
}
/* Navigation item styles */
.bottom-nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-xs);
color: var(--text-color-secondary);
text-decoration: none;
font-size: var(--text-xs);
font-weight: var(--font-medium);
background: none;
border: none;
cursor: pointer;
transition: color var(--transition-fast);
padding: var(--space-xs);
min-width: 48px;
}
.bottom-nav-item i {
font-size: var(--text-xl);
}
/* Touch feedback */
.bottom-nav-item:active {
background: var(--surface-hover);
}
/* Active state (manual or router-link-active) */
.bottom-nav-item.active,
.bottom-nav-item.router-link-active {
color: var(--color-primary);
}
.bottom-nav-item.active i,
.bottom-nav-item.router-link-active i {
color: var(--color-primary);
}
/* ================================================
Dark Mode Support
================================================ */
/* Manual dark mode via data-theme attribute */
[data-theme="dark"] .mobile-bottom-nav {
background: var(--surface-card);
border-top-color: var(--surface-border);
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .bottom-nav-item {
color: var(--text-color-secondary);
}
[data-theme="dark"] .bottom-nav-item:active {
background: var(--surface-hover);
}
[data-theme="dark"] .bottom-nav-item.active,
[data-theme="dark"] .bottom-nav-item.router-link-active {
color: var(--blue-400);
}
/* Auto dark mode (when no manual theme is set) */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .mobile-bottom-nav {
background: var(--surface-card);
border-top-color: var(--surface-border);
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3);
}
:root:not([data-theme]) .bottom-nav-item {
color: var(--text-color-secondary);
}
:root:not([data-theme]) .bottom-nav-item:active {
background: var(--surface-hover);
}
:root:not([data-theme]) .bottom-nav-item.active,
:root:not([data-theme]) .bottom-nav-item.router-link-active {
color: var(--blue-400);
}
}
</style>