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:
@@ -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",
|
||||
|
||||
@@ -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!
|
||||
|
||||
181
src/shared/components/mobile/MobileBottomNav.vue
Normal file
181
src/shared/components/mobile/MobileBottomNav.vue
Normal 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>
|
||||
Reference in New Issue
Block a user