Implemented by Ralph autonomous loop. Iteration: 3 Co-Authored-By: Claude <noreply@anthropic.com>
167 lines
4.3 KiB
Vue
167 lines
4.3 KiB
Vue
<template>
|
|
<Transition name="slide-up">
|
|
<footer v-if="visible" class="mobile-selection-footer">
|
|
<div class="selection-actions">
|
|
<Button
|
|
v-for="(action, index) in actions"
|
|
:key="index"
|
|
:label="action.label"
|
|
:icon="action.icon"
|
|
:severity="action.severity || 'secondary'"
|
|
class="selection-action-btn"
|
|
@click="handleAction(action)"
|
|
/>
|
|
</div>
|
|
</footer>
|
|
</Transition>
|
|
</template>
|
|
|
|
<script setup>
|
|
import Button from 'primevue/button'
|
|
|
|
/**
|
|
* MobileSelectionFooter - Material Design 3 inspired bottom action bar for selection mode
|
|
*
|
|
* Props:
|
|
* - visible: Whether the footer is visible (controls slide-up animation)
|
|
* - actions: Array of action buttons to display
|
|
* Each action: { label: string, icon: string, severity?: string, handler: Function }
|
|
*
|
|
* Behavior:
|
|
* - Slides up from the bottom when visible becomes true
|
|
* - Displays action buttons horizontally centered
|
|
* - Calls action.handler() when button is clicked
|
|
*
|
|
* Usage:
|
|
* <MobileSelectionFooter
|
|
* :visible="selectedItems.length > 0"
|
|
* :actions="[
|
|
* { label: 'Șterge', icon: 'pi pi-trash', severity: 'danger', handler: handleDelete },
|
|
* { label: 'Export', icon: 'pi pi-download', severity: 'secondary', handler: handleExport }
|
|
* ]"
|
|
* />
|
|
*/
|
|
|
|
const props = defineProps({
|
|
/**
|
|
* Controls visibility of the footer (triggers slide-up animation)
|
|
*/
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
/**
|
|
* Array of action buttons to display
|
|
* Each action should have: { label: string, icon: string, severity?: string, handler: Function }
|
|
*/
|
|
actions: {
|
|
type: Array,
|
|
default: () => [],
|
|
validator: (actions) => {
|
|
return Array.isArray(actions) && actions.every(
|
|
action => typeof action.label === 'string' &&
|
|
typeof action.icon === 'string' &&
|
|
typeof action.handler === 'function'
|
|
)
|
|
}
|
|
}
|
|
})
|
|
|
|
/**
|
|
* Handle action button click
|
|
* Calls the action's handler function if defined
|
|
*/
|
|
const handleAction = (action) => {
|
|
if (action.handler && typeof action.handler === 'function') {
|
|
action.handler()
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* ================================================
|
|
MobileSelectionFooter Component Styles
|
|
Material Design 3 inspired selection action bar
|
|
================================================ */
|
|
|
|
.mobile-selection-footer {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
background: var(--surface-card);
|
|
border-top: 1px solid var(--surface-border);
|
|
padding: var(--space-md);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: var(--z-fixed);
|
|
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
/* Container for action buttons */
|
|
.selection-actions {
|
|
display: flex;
|
|
gap: var(--space-sm);
|
|
width: 100%;
|
|
max-width: 400px;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* Individual action buttons */
|
|
.selection-action-btn {
|
|
flex: 1;
|
|
height: 48px;
|
|
font-size: var(--text-base);
|
|
font-weight: var(--font-semibold);
|
|
/* Ensure minimum touch target size */
|
|
min-height: 48px;
|
|
}
|
|
|
|
/* Single action takes full width */
|
|
.selection-actions:has(.selection-action-btn:only-child) .selection-action-btn {
|
|
max-width: none;
|
|
}
|
|
|
|
/* ================================================
|
|
Slide-up Animation (Vue Transition)
|
|
================================================ */
|
|
|
|
.slide-up-enter-active,
|
|
.slide-up-leave-active {
|
|
transition: transform var(--transition-normal), opacity var(--transition-normal);
|
|
}
|
|
|
|
.slide-up-enter-from,
|
|
.slide-up-leave-to {
|
|
transform: translateY(100%);
|
|
opacity: 0;
|
|
}
|
|
|
|
.slide-up-enter-to,
|
|
.slide-up-leave-from {
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
}
|
|
|
|
/* ================================================
|
|
Dark Mode Support
|
|
================================================ */
|
|
|
|
/* Manual dark mode via data-theme attribute */
|
|
[data-theme="dark"] .mobile-selection-footer {
|
|
background: var(--surface-card);
|
|
border-top-color: var(--surface-border);
|
|
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
/* Auto dark mode (when no manual theme is set) */
|
|
@media (prefers-color-scheme: dark) {
|
|
:root:not([data-theme]) .mobile-selection-footer {
|
|
background: var(--surface-card);
|
|
border-top-color: var(--surface-border);
|
|
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3);
|
|
}
|
|
}
|
|
</style>
|