/ src / routes / components / ViewModeSelector.svelte
ViewModeSelector.svelte
  1  <script lang="ts">
  2    import { createEventDispatcher } from 'svelte';
  3    import { viewColumns, viewPreferences } from '$lib/stores';
  4    import { get } from 'svelte/store';
  5    import { onMount } from 'svelte';
  6    import { page } from '$app/stores';
  7    import { browser } from '$app/environment';
  8    
  9    // Necesario para que TypeScript reconozca $page correctamente
 10    export {};
 11    
 12    const dispatch = createEventDispatcher();
 13    
 14    // Prop para el tipo de búsqueda (opcional)
 15    export let searchType: string = '';
 16    
 17    // Extraer URL para uso reactivo
 18    $: pageUrl = $page.url;
 19    $: typeParam = pageUrl.searchParams.get('type');
 20    
 21    // Obtener el tipo de búsqueda desde la URL si no se proporciona
 22    $: actualSearchType = searchType || typeParam || 'web';
 23    
 24    // Obtener el valor actual de columnas para este tipo de búsqueda
 25    $: columns = getColumnsForCurrentType();
 26    
 27    function getColumnsForCurrentType(): number {
 28      // Obtener valor de localStorage si existe
 29      if (typeof window !== 'undefined') {
 30        const storedPref = localStorage.getItem(`viewColumns_${actualSearchType}`);
 31        if (storedPref) {
 32          const value = parseInt(storedPref, 10);
 33          if (!isNaN(value)) return value;
 34        }
 35      }
 36      
 37      // Obtener preferencias para el tipo actual
 38      const preferences = get(viewPreferences);
 39      if (preferences && preferences[actualSearchType]) {
 40        return preferences[actualSearchType].columns;
 41      }
 42      
 43      // Si no hay preferencia específica, usar el valor global
 44      return get(viewColumns);
 45    }
 46    
 47    // Variable para almacenar el tamaño de la pantalla
 48    let windowWidth = typeof window !== 'undefined' ? window.innerWidth : 1024;
 49    
 50    // Mantener actualizada la lista de opciones disponibles según el tamaño de pantalla
 51    $: availableColumns = getAvailableColumns(windowWidth);
 52    
 53    // Determinar las opciones de columnas disponibles según el ancho
 54    function getAvailableColumns(width: number) {
 55      // Para dispositivos móviles solo mostramos vista de lista y 2 columnas
 56      if (width < 480) {
 57        return [
 58          { value: 1, label: 'Lista', icon: 'list' },
 59          { value: 2, label: '2 columnas', icon: 'grid-2' }
 60        ];
 61      }
 62      
 63      // Para tablets mostramos hasta 3 columnas
 64      if (width < 768) {
 65        return [
 66          { value: 1, label: 'Lista', icon: 'list' },
 67          { value: 2, label: '2 columnas', icon: 'grid-2' },
 68          { value: 3, label: '3 columnas', icon: 'grid-3' }
 69        ];
 70      }
 71      
 72      // Para laptops mostramos hasta 4 columnas
 73      if (width < 1200) {
 74        return [
 75          { value: 1, label: 'Lista', icon: 'list' },
 76          { value: 2, label: '2 columnas', icon: 'grid-2' },
 77          { value: 3, label: '3 columnas', icon: 'grid-3' },
 78          { value: 4, label: '4 columnas', icon: 'grid-4' }
 79        ];
 80      }
 81      
 82      // Para pantallas grandes mostramos todas las opciones
 83      return [
 84        { value: 1, label: 'Lista', icon: 'list' },
 85        { value: 2, label: '2 columnas', icon: 'grid-2' },
 86        { value: 3, label: '3 columnas', icon: 'grid-3' },
 87        { value: 4, label: '4 columnas', icon: 'grid-4' },
 88        { value: 6, label: '6 columnas', icon: 'grid-6' }
 89      ];
 90    }
 91    
 92    // Actualizar cuando cambia el tamaño de la ventana
 93    function handleResize() {
 94      windowWidth = typeof window !== 'undefined' ? window.innerWidth : 1024;
 95      
 96      // Si la selección actual no está disponible en el nuevo tamaño, ajustar a un valor válido
 97      if (!availableColumns.some(option => option.value === columns)) {
 98        // Encontrar el valor máximo disponible que sea menor o igual que el valor actual
 99        const maxAvailable = Math.max(...availableColumns.map(option => option.value));
100        updateColumns(Math.min(columns, maxAvailable));
101      }
102    }
103    
104    // Configurar event listener para resize
105    onMount(() => {
106      if (typeof window !== 'undefined') {
107        window.addEventListener('resize', handleResize);
108      }
109      return () => {
110        if (typeof window !== 'undefined') {
111          window.removeEventListener('resize', handleResize);
112        }
113      };
114    });
115  
116    function updateColumns(newValue: number) {
117      columns = newValue;
118      
119      // Guardar para el tipo de búsqueda actual
120      viewPreferences.update(prefs => {
121        const updatedPrefs = { ...prefs };
122        if (!updatedPrefs[actualSearchType]) {
123          updatedPrefs[actualSearchType] = {};
124        }
125        updatedPrefs[actualSearchType].columns = newValue;
126        return updatedPrefs;
127      });
128      
129      // También actualizar el store global para compatibilidad
130      viewColumns.set(columns);
131      
132      // Guardar en localStorage para persistencia
133      if (typeof window !== 'undefined') {
134        localStorage.setItem(`viewColumns_${actualSearchType}`, newValue.toString());
135      }
136      
137      // Notificar cambio
138      dispatch('change', { columns, searchType: actualSearchType });
139    }
140    
141    // Iconos para cada tipo de vista
142    const icons = {
143      list: `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>`,
144      'grid-2': `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="9"></rect><rect x="3" y="14" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect></svg>`,
145      'grid-3': `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="5" height="9"></rect><rect x="10" y="3" width="5" height="9"></rect><rect x="17" y="3" width="4" height="9"></rect><rect x="3" y="14" width="5" height="7"></rect><rect x="10" y="14" width="5" height="7"></rect><rect x="17" y="14" width="4" height="7"></rect></svg>`,
146      'grid-4': `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="4" height="7"></rect><rect x="9" y="3" width="4" height="7"></rect><rect x="15" y="3" width="4" height="7"></rect><rect x="21" y="3" width="0" height="7"></rect><rect x="3" y="12" width="4" height="7"></rect><rect x="9" y="12" width="4" height="7"></rect><rect x="15" y="12" width="4" height="7"></rect><rect x="21" y="12" width="0" height="7"></rect></svg>`,
147      'grid-6': `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="3" height="7"></rect><rect x="8" y="3" width="3" height="7"></rect><rect x="13" y="3" width="3" height="7"></rect><rect x="18" y="3" width="3" height="7"></rect><rect x="3" y="12" width="3" height="7"></rect><rect x="8" y="12" width="3" height="7"></rect><rect x="13" y="12" width="3" height="7"></rect><rect x="18" y="12" width="3" height="7"></rect></svg>`,
148    };
149  </script>
150  
151  <div class="view-mode-selector">
152    <div class="view-mode-label">
153      <span>Vista:</span>
154    </div>
155    
156    <div class="view-mode-buttons">
157      {#each availableColumns as option}
158        <button 
159          class="view-mode-button {columns === option.value ? 'active' : ''}" 
160          on:click={() => updateColumns(option.value)}
161          aria-label="Cambiar a vista {option.label}"
162          title="{option.label}"
163        >
164          {@html icons[option.icon]}
165        </button>
166      {/each}
167    </div>
168  </div>
169  
170  <style>
171    .view-mode-selector {
172      display: flex;
173      align-items: center;
174      gap: var(--space-sm);
175      color: var(--text-secondary);
176      padding: var(--space-sm) var(--space-md);
177      background: var(--bg-card-primary);
178      border: 1px solid var(--primary);
179      border-radius: var(--border-radius);
180      transition: all 0.3s ease;
181      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
182    }
183    
184    /* Ajustes para tema oscuro */
185    :global(body.dark) .view-mode-selector {
186      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
187    }
188    
189    /* Añadir efecto de hover para hacer más visible el selector */
190    .view-mode-selector:hover {
191      transform: translateY(-2px);
192      box-shadow: 0 4px 12px rgba(var(--primary-rgb), 0.3);
193    }
194    
195    .view-mode-label {
196      font-size: 0.9rem;
197      font-weight: var(--font-weight-medium);
198      white-space: nowrap;
199    }
200    
201    .view-mode-buttons {
202      display: flex;
203      align-items: center;
204      gap: var(--space-xs);
205    }
206    
207    .view-mode-button {
208      background: transparent;
209      border: 1px solid transparent;
210      color: var(--text-secondary);
211      width: 38px;
212      height: 38px;
213      padding: 0;
214      border-radius: var(--border-radius-sm);
215      display: flex;
216      align-items: center;
217      justify-content: center;
218      cursor: pointer;
219      transition: all var(--transition-speed);
220      position: relative;
221      overflow: hidden;
222    }
223    
224    .view-mode-button:hover {
225      background-color: var(--bg-secondary);
226      color: var(--primary);
227      border-color: var(--border-color);
228      transform: scale(1.05);
229    }
230    
231    .view-mode-button.active {
232      background-color: var(--primary);
233      color: white;
234      border-color: var(--primary);
235      box-shadow: 0 2px 4px rgba(var(--primary-rgb), 0.3);
236    }
237    
238    .view-mode-button.active:hover {
239      transform: scale(1.05);
240      box-shadow: 0 2px 8px rgba(var(--primary-rgb), 0.5);
241    }
242    
243    /* Añadir efecto al hacer clic */
244    .view-mode-button:active {
245      transform: scale(0.95);
246    }
247    
248    @media (max-width: 768px) {
249      .view-mode-selector {
250        padding: var(--space-xs) var(--space-sm);
251      }
252      
253      .view-mode-button {
254        width: 30px;
255        height: 30px;
256      }
257    }
258    
259    @media (max-width: 480px) {
260      .view-mode-selector {
261        width: 100%;
262        justify-content: space-between;
263      }
264    }
265  </style>