diff --git a/src/components/SearchScript.astro b/src/components/SearchScript.astro index d70ac21..6ab8cfc 100644 --- a/src/components/SearchScript.astro +++ b/src/components/SearchScript.astro @@ -12,12 +12,36 @@ // Register the styleControls component Alpine.data('styleControls', () => ({ iconSize: 'medium', + iconSizeValue: 2, // Slider value: 1=small, 2=medium, 3=large viewMode: 'grid', displayMode: 'both', init() { // Load preferences from localStorage this.loadPreferences(); + + // Set initial iconSizeValue based on iconSize + this.updateSliderFromIconSize(); + + // Listen for events from searchServices component + window.addEventListener('styleControls:updateIconSize', (e) => { + if (e.detail && e.detail.size) { + this.iconSize = e.detail.size; + this.updateSliderFromIconSize(); + } + }); + + window.addEventListener('styleControls:updateViewMode', (e) => { + if (e.detail && e.detail.mode) { + this.viewMode = e.detail.mode; + } + }); + + window.addEventListener('styleControls:updateDisplayMode', (e) => { + if (e.detail && e.detail.mode) { + this.displayMode = e.detail.mode; + } + }); }, loadPreferences() { @@ -26,7 +50,7 @@ // Load icon size const savedIconSize = localStorage.getItem('services-icon-size'); if (savedIconSize) { - this.setIconSize(savedIconSize); + this.setIconSize(savedIconSize, false); // Don't update slider yet } // Load view mode @@ -40,6 +64,9 @@ if (savedDisplayMode) { this.displayMode = savedDisplayMode; } + + // Update slider value based on loaded icon size + this.updateSliderFromIconSize(); } catch (e) { console.error('Error loading preferences:', e); } @@ -58,19 +85,158 @@ } }, - setIconSize(size) { + // Convert between iconSize string and iconSizeValue number + updateSliderFromIconSize() { + if (this.iconSize === 'small') { + this.iconSizeValue = 1; + } else if (this.iconSize === 'medium') { + this.iconSizeValue = 2; + } else if (this.iconSize === 'large') { + this.iconSizeValue = 3; + } + }, + + // Update iconSize based on slider value + updateIconSizeFromSlider() { + console.log('Slider value changed:', this.iconSizeValue); + const value = parseInt(this.iconSizeValue); + let size; + + if (value === 1) { + size = 'small'; + } else if (value === 2) { + size = 'medium'; + } else if (value === 3) { + size = 'large'; + } else { + size = 'medium'; // Default fallback + } + + console.log('Setting icon size to:', size); + this.setIconSize(size); + + // Apply the size directly to app-list if it exists + this.applyIconSizeDirectly(size); + }, + + // Apply icon size directly to elements + applyIconSizeDirectly(size) { + // Try to find app-list element + const appList = document.getElementById('app-list'); + if (appList) { + console.log('Found app-list, applying size:', size); + // Remove existing size classes + appList.classList.remove('icon-size-small', 'icon-size-medium', 'icon-size-large'); + // Add the new size class + appList.classList.add(`icon-size-${size}`); + } else { + console.log('app-list element not found'); + + // Try to apply to all service cards directly + const cards = document.querySelectorAll('.app-card, .service-card'); + if (cards.length > 0) { + console.log('Found', cards.length, 'cards, applying size:', size); + cards.forEach(card => { + // Remove existing size classes + card.classList.remove('icon-size-small', 'icon-size-medium', 'icon-size-large'); + // Add the new size class + card.classList.add(`icon-size-${size}`); + }); + } else { + console.log('No service cards found'); + } + } + + // Dispatch event to notify searchServices component + window.dispatchEvent(new CustomEvent('searchServices:setIconSize', { + detail: { size } + })); + }, + + setIconSize(size, updateSlider = true) { + console.log('setIconSize called with:', size); this.iconSize = size; + + // Update slider value if requested + if (updateSlider) { + this.updateSliderFromIconSize(); + } + this.savePreferences(); + + // Apply the size directly + this.applyIconSizeDirectly(size); }, toggleViewMode() { this.viewMode = this.viewMode === 'grid' ? 'list' : 'grid'; this.savePreferences(); + + // Apply view mode directly + this.applyViewModeDirectly(this.viewMode); + + // Dispatch event to notify searchServices component + window.dispatchEvent(new CustomEvent('searchServices:setViewMode', { + detail: { mode: this.viewMode } + })); + }, + + applyViewModeDirectly(mode) { + console.log('Applying view mode directly:', mode); + const appList = document.getElementById('app-list'); + if (appList) { + // Remove existing view mode classes + appList.classList.remove('view-mode-grid', 'view-mode-list'); + + // Add the new view mode class + appList.classList.add(`view-mode-${mode}`); + + // Update all category sections + document.querySelectorAll('.category-section').forEach(section => { + const gridContainer = section.querySelector('.grid'); + if (gridContainer) { + // Update grid classes based on view mode + if (mode === 'grid') { + gridContainer.classList.remove('grid-cols-1'); + gridContainer.classList.add('grid-cols-2', 'sm:grid-cols-3', 'lg:grid-cols-4'); + } else { + gridContainer.classList.remove('grid-cols-2', 'sm:grid-cols-3', 'lg:grid-cols-4'); + gridContainer.classList.add('grid-cols-1'); + } + } + }); + } }, setDisplayMode(mode) { this.displayMode = mode; this.savePreferences(); + + // Apply display mode directly + this.applyDisplayModeDirectly(mode); + + // Dispatch event to notify searchServices component + window.dispatchEvent(new CustomEvent('searchServices:setDisplayMode', { + detail: { mode } + })); + }, + + applyDisplayModeDirectly(mode) { + console.log('Applying display mode directly:', mode); + const appList = document.getElementById('app-list'); + if (appList) { + // Remove existing display mode classes + appList.classList.remove('display-both', 'display-image-only', 'display-name-only'); + + // Add the new display mode class + if (mode === 'image') { + appList.classList.add('display-image-only'); + } else if (mode === 'name') { + appList.classList.add('display-name-only'); + } else { + appList.classList.add('display-both'); + } + } } })); @@ -106,12 +272,57 @@ // Listen for window resize events to optimize layout this.setupResizeListener(); + // Listen for events from styleControls component + this.setupStyleControlsListeners(); + // Set loading to false after initialization setTimeout(() => { this.loading = false; }, 300); }, + // Setup listeners for styleControls events + setupStyleControlsListeners() { + // Listen for icon size changes + window.addEventListener('searchServices:setIconSize', (e) => { + if (e.detail && e.detail.size) { + console.log('searchServices received icon size event:', e.detail.size); + this.setIconSize(e.detail.size); + + // Notify styleControls component of the change + window.dispatchEvent(new CustomEvent('styleControls:updateIconSize', { + detail: { size: e.detail.size } + })); + } + }); + + // Listen for view mode changes + window.addEventListener('searchServices:setViewMode', (e) => { + if (e.detail && e.detail.mode) { + console.log('searchServices received view mode event:', e.detail.mode); + this.setViewMode(e.detail.mode); + + // Notify styleControls component of the change + window.dispatchEvent(new CustomEvent('styleControls:updateViewMode', { + detail: { mode: e.detail.mode } + })); + } + }); + + // Listen for display mode changes + window.addEventListener('searchServices:setDisplayMode', (e) => { + if (e.detail && e.detail.mode) { + console.log('searchServices received display mode event:', e.detail.mode); + this.setDisplayMode(e.detail.mode); + + // Notify styleControls component of the change + window.dispatchEvent(new CustomEvent('styleControls:updateDisplayMode', { + detail: { mode: e.detail.mode } + })); + } + }); + }, + setupWatchers() { this.$watch('searchQuery', (query) => { // Debounce search for better performance diff --git a/src/components/common/StyleControls.astro b/src/components/common/StyleControls.astro index 4d3badb..a3c4afd 100644 --- a/src/components/common/StyleControls.astro +++ b/src/components/common/StyleControls.astro @@ -16,40 +16,18 @@ const { {showSizeSelector && (
-
- - - +
+
)} @@ -163,4 +141,85 @@ const { color: var(--color-zag-light); } } + + /* Slider styles */ + .size-slider { + -webkit-appearance: none; + appearance: none; + height: 6px; + border-radius: 3px; + background: var(--color-zag-light-muted); + outline: none; + cursor: pointer; + transition: all 0.2s; + + :where(.dark, .dark *) & { + background: var(--color-zag-dark-muted); + } + } + + /* Slider thumb */ + .size-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--color-zag-dark); + cursor: pointer; + border: 2px solid var(--color-zag-light); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + transition: all 0.2s; + + :where(.dark, .dark *) & { + background: var(--color-zag-light); + border: 2px solid var(--color-zag-dark); + } + } + + .size-slider::-moz-range-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--color-zag-dark); + cursor: pointer; + border: 2px solid var(--color-zag-light); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + transition: all 0.2s; + + :where(.dark, .dark *) & { + background: var(--color-zag-light); + border: 2px solid var(--color-zag-dark); + } + } + + /* Hover state */ + .size-slider:hover::-webkit-slider-thumb { + transform: scale(1.1); + } + + .size-slider:hover::-moz-range-thumb { + transform: scale(1.1); + } + + /* Focus state */ + .size-slider:focus { + outline: none; + } + + .size-slider:focus::-webkit-slider-thumb { + box-shadow: 0 0 0 3px var(--color-zag-light), 0 0 0 5px var(--color-zag-dark-muted); + + :where(.dark, .dark *) & { + box-shadow: 0 0 0 3px var(--color-zag-dark), 0 0 0 5px var(--color-zag-light-muted); + } + } + + .size-slider:focus::-moz-range-thumb { + box-shadow: 0 0 0 3px var(--color-zag-light), 0 0 0 5px var(--color-zag-dark-muted); + + :where(.dark, .dark *) & { + box-shadow: 0 0 0 3px var(--color-zag-dark), 0 0 0 5px var(--color-zag-light-muted); + } + } diff --git a/src/pages/homelab/index.astro b/src/pages/homelab/index.astro index 349923a..7d49dd3 100644 --- a/src/pages/homelab/index.astro +++ b/src/pages/homelab/index.astro @@ -146,7 +146,7 @@ const webpageData = {
-
+
diff --git a/src/styles/global.css b/src/styles/global.css index 2d014ef..6ff7db6 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -217,6 +217,28 @@ } @layer components { + /* Icon size classes for service cards */ + .icon-size-small .app-card .app-icon, + .icon-size-small .service-card .service-icon { + width: 32px; + height: 32px; + transition: width 0.3s ease, height 0.3s ease; + } + + .icon-size-medium .app-card .app-icon, + .icon-size-medium .service-card .service-icon { + width: 48px; + height: 48px; + transition: width 0.3s ease, height 0.3s ease; + } + + .icon-size-large .app-card .app-icon, + .icon-size-large .service-card .service-icon { + width: 64px; + height: 64px; + transition: width 0.3s ease, height 0.3s ease; + } + /* Interactive element base transitions */ .zag-interactive { position: relative;