feat: improve catalog responsive design, cart layout and card component
- Redesign Cart component with improved alignments, gaps, and responsive padding - Refactor Card component with mobile-first layout and consistent spacing - Update catalog layout to 60/40 split for products/cart on desktop (lg) - Add PaginationControls component (previously untracked) - Clean up obsolete CSS styles from catalog page
This commit is contained in:
269
src/components/catalog/PaginationControls.vue
Normal file
269
src/components/catalog/PaginationControls.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<v-row class="pagination-container my-3" align="center">
|
||||
<!-- Fila 1: Navegador de páginas (centrado, ancho completo) -->
|
||||
<v-col cols="12" class="d-flex justify-center mb-2">
|
||||
<v-pagination
|
||||
:model-value="currentPage"
|
||||
@update:model-value="$emit('page-change', $event)"
|
||||
:length="totalPages"
|
||||
:total-visible="computedTotalVisible"
|
||||
:show-first-last-page="showFirstLastButtons"
|
||||
rounded="circle"
|
||||
color="primary"
|
||||
:size="paginationSize"
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
|
||||
<!-- Fila 2: Info + Selector (en línea) - Solo Desktop -->
|
||||
<v-col
|
||||
cols="12"
|
||||
class="d-none d-md-flex justify-center align-center pagination-info-row"
|
||||
>
|
||||
<!-- Información de resultados -->
|
||||
<span class="pagination-info-text">
|
||||
Mostrando {{ paginationInfo.start }}-{{ paginationInfo.end }}
|
||||
de {{ paginationInfo.total }} productos
|
||||
</span>
|
||||
|
||||
<!-- Separador visual -->
|
||||
<v-divider vertical class="mx-4" style="height: 24px;"></v-divider>
|
||||
|
||||
<!-- Selector de items por página -->
|
||||
<span class="mr-2 text-body-1 font-weight-medium">
|
||||
Productos por página:
|
||||
</span>
|
||||
<v-select
|
||||
:model-value="itemsPerPage"
|
||||
@update:model-value="$emit('items-per-page-change', $event)"
|
||||
:items="itemsPerPageOptions"
|
||||
density="comfortable"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
class="items-per-page-selector"
|
||||
></v-select>
|
||||
</v-col>
|
||||
|
||||
<!-- Fila 2 Mobile: Info de resultados - Solo Mobile -->
|
||||
<v-col cols="12" class="d-flex d-md-none justify-center">
|
||||
<span class="pagination-info-text">
|
||||
Mostrando {{ paginationInfo.start }}-{{ paginationInfo.end }}
|
||||
de {{ paginationInfo.total }} productos
|
||||
</span>
|
||||
</v-col>
|
||||
|
||||
<!-- Fila 3 Mobile: Selector - Solo Mobile -->
|
||||
<v-col
|
||||
cols="12"
|
||||
class="d-flex d-md-none justify-center align-center"
|
||||
>
|
||||
<span class="mr-2 text-body-1 font-weight-medium">
|
||||
Productos por página:
|
||||
</span>
|
||||
<v-select
|
||||
:model-value="itemsPerPage"
|
||||
@update:model-value="$emit('items-per-page-change', $event)"
|
||||
:items="itemsPerPageOptions"
|
||||
density="comfortable"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
class="items-per-page-selector"
|
||||
></v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'PaginationControls',
|
||||
props: {
|
||||
currentPage: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
totalPages: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
itemsPerPage: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
itemsPerPageOptions: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
paginationInfo: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
position: {
|
||||
type: String,
|
||||
default: 'top',
|
||||
validator: (value) => ['top', 'bottom'].includes(value)
|
||||
},
|
||||
totalVisiblePages: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
emits: ['page-change', 'items-per-page-change'],
|
||||
setup(props) {
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
|
||||
// Actualizar ancho de ventana en resize
|
||||
const updateWidth = () => {
|
||||
windowWidth.value = window.innerWidth;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', updateWidth);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', updateWidth);
|
||||
});
|
||||
|
||||
const isMobile = computed(() => windowWidth.value < 680);
|
||||
|
||||
// Computed para tamaño de paginación: más pequeño en tablet para ahorrar espacio
|
||||
const paginationSize = computed(() => {
|
||||
const width = windowWidth.value;
|
||||
|
||||
// En pantallas pequeñas/medianas, usar tamaño default para ahorrar espacio
|
||||
if (width < 960) {
|
||||
return 'default';
|
||||
}
|
||||
|
||||
// En desktop, usar tamaño large
|
||||
return 'large';
|
||||
});
|
||||
|
||||
// Computed para mostrar botones first/last: solo en pantallas >= 680px
|
||||
const showFirstLastButtons = computed(() => {
|
||||
const width = windowWidth.value;
|
||||
|
||||
// Mostrar first/last solo en tablet y desktop (>= 680px)
|
||||
// En mobile (<680px), solo prev/next para ahorrar espacio
|
||||
return width >= 680;
|
||||
});
|
||||
|
||||
// Computed property para total-visible: prioriza prop recibida, sino calcula localmente
|
||||
const computedTotalVisible = computed(() => {
|
||||
// Si se recibe totalVisiblePages desde el padre, usarlo (SINCRONIZACIÓN)
|
||||
if (props.totalVisiblePages !== null) {
|
||||
return props.totalVisiblePages;
|
||||
}
|
||||
|
||||
// Fallback: cálculo local (por compatibilidad)
|
||||
const width = windowWidth.value;
|
||||
const totalPages = props.totalPages;
|
||||
|
||||
// Si hay pocas páginas, mostrarlas todas
|
||||
if (totalPages <= 7) {
|
||||
return totalPages;
|
||||
}
|
||||
|
||||
// Breakpoints responsivos
|
||||
if (width < 400) {
|
||||
return 3;
|
||||
} else if (width < 680) {
|
||||
return 5;
|
||||
} else if (width < 960) {
|
||||
return 7;
|
||||
} else if (width < 1280) {
|
||||
return 9;
|
||||
} else {
|
||||
return 11;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
isMobile,
|
||||
computedTotalVisible,
|
||||
paginationSize,
|
||||
showFirstLastButtons
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination-container {
|
||||
padding: 12px 0;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pagination-info-text {
|
||||
font-size: 1.05rem;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Nueva clase para la fila de info en desktop */
|
||||
.pagination-info-row {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.items-per-page-selector {
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 680px) {
|
||||
.pagination-container {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.items-per-page-selector {
|
||||
max-width: 80px;
|
||||
}
|
||||
|
||||
.text-body-1 {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ajustes visuales */
|
||||
.v-pagination {
|
||||
margin: 0 auto;
|
||||
min-width: 400px; /* Garantizar espacio mínimo para iconos + páginas */
|
||||
}
|
||||
|
||||
/* En móviles muy pequeños, reducir min-width */
|
||||
@media (max-width: 480px) {
|
||||
.v-pagination {
|
||||
min-width: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
/* En pantallas medianas problemáticas (680-960px), asegurar espacio suficiente */
|
||||
@media (min-width: 680px) and (max-width: 960px) {
|
||||
.v-pagination {
|
||||
min-width: 450px; /* Más espacio para evitar que desaparezcan los iconos */
|
||||
}
|
||||
}
|
||||
|
||||
/* Separador vertical */
|
||||
.v-divider--vertical {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Aumentar tamaño del icono del dropdown en v-select */
|
||||
.items-per-page-selector :deep(.v-icon) {
|
||||
font-size: 1.5rem !important;
|
||||
}
|
||||
|
||||
/* Aumentar tamaño del texto dentro del select */
|
||||
.items-per-page-selector :deep(.v-field__input) {
|
||||
font-size: 1rem !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Aumentar tamaño de los items del menú dropdown */
|
||||
.items-per-page-selector :deep(.v-list-item-title) {
|
||||
font-size: 1rem !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user