Make size changes a slider
This commit is contained in:
parent
750fe5c629
commit
f51fa741cc
@ -12,12 +12,36 @@
|
|||||||
// Register the styleControls component
|
// Register the styleControls component
|
||||||
Alpine.data('styleControls', () => ({
|
Alpine.data('styleControls', () => ({
|
||||||
iconSize: 'medium',
|
iconSize: 'medium',
|
||||||
|
iconSizeValue: 2, // Slider value: 1=small, 2=medium, 3=large
|
||||||
viewMode: 'grid',
|
viewMode: 'grid',
|
||||||
displayMode: 'both',
|
displayMode: 'both',
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// Load preferences from localStorage
|
// Load preferences from localStorage
|
||||||
this.loadPreferences();
|
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() {
|
loadPreferences() {
|
||||||
@ -26,7 +50,7 @@
|
|||||||
// Load icon size
|
// Load icon size
|
||||||
const savedIconSize = localStorage.getItem('services-icon-size');
|
const savedIconSize = localStorage.getItem('services-icon-size');
|
||||||
if (savedIconSize) {
|
if (savedIconSize) {
|
||||||
this.setIconSize(savedIconSize);
|
this.setIconSize(savedIconSize, false); // Don't update slider yet
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load view mode
|
// Load view mode
|
||||||
@ -40,6 +64,9 @@
|
|||||||
if (savedDisplayMode) {
|
if (savedDisplayMode) {
|
||||||
this.displayMode = savedDisplayMode;
|
this.displayMode = savedDisplayMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update slider value based on loaded icon size
|
||||||
|
this.updateSliderFromIconSize();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error loading preferences:', 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;
|
this.iconSize = size;
|
||||||
|
|
||||||
|
// Update slider value if requested
|
||||||
|
if (updateSlider) {
|
||||||
|
this.updateSliderFromIconSize();
|
||||||
|
}
|
||||||
|
|
||||||
this.savePreferences();
|
this.savePreferences();
|
||||||
|
|
||||||
|
// Apply the size directly
|
||||||
|
this.applyIconSizeDirectly(size);
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleViewMode() {
|
toggleViewMode() {
|
||||||
this.viewMode = this.viewMode === 'grid' ? 'list' : 'grid';
|
this.viewMode = this.viewMode === 'grid' ? 'list' : 'grid';
|
||||||
this.savePreferences();
|
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) {
|
setDisplayMode(mode) {
|
||||||
this.displayMode = mode;
|
this.displayMode = mode;
|
||||||
this.savePreferences();
|
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
|
// Listen for window resize events to optimize layout
|
||||||
this.setupResizeListener();
|
this.setupResizeListener();
|
||||||
|
|
||||||
|
// Listen for events from styleControls component
|
||||||
|
this.setupStyleControlsListeners();
|
||||||
|
|
||||||
// Set loading to false after initialization
|
// Set loading to false after initialization
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}, 300);
|
}, 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() {
|
setupWatchers() {
|
||||||
this.$watch('searchQuery', (query) => {
|
this.$watch('searchQuery', (query) => {
|
||||||
// Debounce search for better performance
|
// Debounce search for better performance
|
||||||
|
@ -16,40 +16,18 @@ const {
|
|||||||
{showSizeSelector && (
|
{showSizeSelector && (
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-sm zag-text-muted hidden sm:inline">Size:</span>
|
<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">
|
<div class="size-selector flex flex-col items-center border-2 border-solid zag-border-b rounded-lg p-2 zag-bg zag-transition">
|
||||||
<button
|
<input
|
||||||
@click="setIconSize('small')"
|
type="range"
|
||||||
:class="iconSize === 'small' ? 'active-size' : 'inactive-size'"
|
min="1"
|
||||||
class="p-1.5 transition-all rounded-md focus:outline-none focus:ring-2 focus:ring-current"
|
max="3"
|
||||||
aria-label="Small icons"
|
step="1"
|
||||||
title="Small icons (Alt+1)"
|
x-model="iconSizeValue"
|
||||||
>
|
@input="updateIconSizeFromSlider()"
|
||||||
<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">
|
class="size-slider w-full"
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
aria-label="Icon size slider"
|
||||||
</svg>
|
title="Adjust icon size (Alt+1: Small, Alt+2: Medium, Alt+3: Large)"
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -163,4 +141,85 @@ const {
|
|||||||
color: var(--color-zag-light);
|
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>
|
</style>
|
||||||
|
@ -146,7 +146,7 @@ const webpageData = {
|
|||||||
<!-- Search and controls container -->
|
<!-- Search and controls container -->
|
||||||
<div class="mb-4 pt-0">
|
<div class="mb-4 pt-0">
|
||||||
<!-- Style controls in a centered row above search -->
|
<!-- 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 />
|
<StyleControls />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -217,6 +217,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
@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 */
|
/* Interactive element base transitions */
|
||||||
.zag-interactive {
|
.zag-interactive {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user