Make size changes a slider
This commit is contained in:
parent
750fe5c629
commit
f51fa741cc
@ -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
|
||||
|
@ -16,40 +16,18 @@ const {
|
||||
{showSizeSelector && (
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm zag-text-muted hidden sm:inline">Size:</span>
|
||||
<div class="size-selector flex items-center gap-1 border-2 border-solid zag-border-b rounded-lg p-1 zag-bg zag-transition">
|
||||
<button
|
||||
@click="setIconSize('small')"
|
||||
:class="iconSize === 'small' ? 'active-size' : 'inactive-size'"
|
||||
class="p-1.5 transition-all rounded-md focus:outline-none focus:ring-2 focus:ring-current"
|
||||
aria-label="Small icons"
|
||||
title="Small icons (Alt+1)"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
@click="setIconSize('medium')"
|
||||
:class="iconSize === 'medium' ? 'active-size' : 'inactive-size'"
|
||||
class="p-1.5 transition-all rounded-md focus:outline-none focus:ring-2 focus:ring-current"
|
||||
aria-label="Medium icons"
|
||||
title="Medium icons (Alt+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="18" height="18" rx="2" ry="2"></rect>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
@click="setIconSize('large')"
|
||||
:class="iconSize === 'large' ? 'active-size' : 'inactive-size'"
|
||||
class="p-1.5 transition-all rounded-md focus:outline-none focus:ring-2 focus:ring-current"
|
||||
aria-label="Large icons"
|
||||
title="Large icons (Alt+3)"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="size-selector flex flex-col items-center border-2 border-solid zag-border-b rounded-lg p-2 zag-bg zag-transition">
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="3"
|
||||
step="1"
|
||||
x-model="iconSizeValue"
|
||||
@input="updateIconSizeFromSlider()"
|
||||
class="size-slider w-full"
|
||||
aria-label="Icon size slider"
|
||||
title="Adjust icon size (Alt+1: Small, Alt+2: Medium, Alt+3: Large)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -146,7 +146,7 @@ const webpageData = {
|
||||
<!-- Search and controls container -->
|
||||
<div class="mb-4 pt-0">
|
||||
<!-- Style controls in a centered row above search -->
|
||||
<div class="w-full flex justify-center mb-4">
|
||||
<div class="w-full flex justify-center mb-4" x-data="styleControls">
|
||||
<StyleControls />
|
||||
</div>
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user