Files
analiticaNucleo/nuxt4-app/app/composables/useSidebarState.ts
josedario87 a10d39a179
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 49s
Refactor: Implementación impecable de la sidebar con estado unificado
Soluciona todos los problemas identificados en la arquitectura anterior:

Cambios principales:
- Nuevo composable useSidebarState() que centraliza todo el estado
- Elimina múltiples fuentes de verdad que causaban desincronización
- Remueve watchers en cascada y hooks indirectos
- Elimina workarounds manuales de DOM y aria-hidden
- Implementa persistencia consistente en cookies
- Manejo responsive automático (mobile vs desktop)

Archivos modificados:
- app/composables/useSidebarState.ts (nuevo): Composable singleton
- app/components/app/AppSidebar.vue: Usa el nuevo composable
- app/layouts/dashboard.vue: Simplificado, sin refs locales ni workarounds
- docs/SIDEBAR_ARCHITECTURE.md (nuevo): Documentación completa

Beneficios:
✓ Estado consistente en toda la aplicación
✓ No más flickering o comportamientos anómalos
✓ Código más simple y mantenible
✓ Mejor performance (menos re-renders)
✓ Auto-close en mobile al navegar

Referencias:
- app/composables/useSidebarState.ts:1
- app/components/app/AppSidebar.vue:232
- app/layouts/dashboard.vue:40
2025-10-30 11:16:15 -06:00

130 lines
2.9 KiB
TypeScript

/**
* Composable unificado para manejar el estado de la sidebar
*
* Centraliza todo el estado relacionado con la sidebar para evitar
* inconsistencias entre múltiples refs y watchers en cascada.
*
* Características:
* - Estado persistente en cookies
* - Sincronización automática entre open/collapsed
* - Manejo de responsive (mobile vs desktop)
* - Cierre automático en navegación (solo mobile)
*/
import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
// Storage key para las cookies
const STORAGE_KEY = 'analytics-dashboard-sidebar'
// Tipos
interface SidebarState {
open: boolean
collapsed: boolean
size: number
}
// Estado global singleton
const sidebarState = ref<SidebarState | null>(null)
export function useSidebarState() {
const route = useRoute()
// Inicializar estado solo una vez (singleton)
if (!sidebarState.value) {
// Leer de cookie si existe
const savedState = useCookie<SidebarState>(STORAGE_KEY, {
default: () => ({
open: true,
collapsed: false,
size: 28 // defaultSize
})
})
sidebarState.value = savedState.value
}
// Referencias reactivas al estado
const open = computed({
get: () => sidebarState.value?.open ?? true,
set: (value: boolean) => {
if (sidebarState.value) {
sidebarState.value.open = value
// Persistir en cookie
const cookie = useCookie<SidebarState>(STORAGE_KEY)
cookie.value = sidebarState.value
}
}
})
const collapsed = computed({
get: () => sidebarState.value?.collapsed ?? false,
set: (value: boolean) => {
if (sidebarState.value) {
sidebarState.value.collapsed = value
// Persistir en cookie
const cookie = useCookie<SidebarState>(STORAGE_KEY)
cookie.value = sidebarState.value
}
}
})
const size = computed({
get: () => sidebarState.value?.size ?? 28,
set: (value: number) => {
if (sidebarState.value) {
sidebarState.value.size = value
// Persistir en cookie
const cookie = useCookie<SidebarState>(STORAGE_KEY)
cookie.value = sidebarState.value
}
}
})
// Funciones de control
function toggle() {
open.value = !open.value
}
function toggleCollapse() {
collapsed.value = !collapsed.value
}
function setOpen(value: boolean) {
open.value = value
}
function setCollapsed(value: boolean) {
collapsed.value = value
}
// Detectar si estamos en mobile
const isMobile = computed(() => {
if (import.meta.server) return false
return window.innerWidth < 1024 // lg breakpoint
})
// Auto-cerrar en navegación solo en mobile
if (import.meta.client) {
watch(() => route.fullPath, () => {
if (isMobile.value) {
open.value = false
}
})
}
return {
// Estado
open,
collapsed,
size,
isMobile,
// Acciones
toggle,
toggleCollapse,
setOpen,
setCollapsed
}
}