diff --git a/static/css/components/alphabet-bar.css b/static/css/components/alphabet-bar.css index 3601560e..b3f6c3ea 100644 --- a/static/css/components/alphabet-bar.css +++ b/static/css/components/alphabet-bar.css @@ -1,24 +1,102 @@ /* Alphabet Bar Component */ +.alphabet-bar-container { + position: fixed; + left: 0; + top: 50%; + transform: translateY(-50%); + z-index: 100; + display: flex; + transition: transform 0.3s ease; +} + +.alphabet-bar-container.collapsed { + transform: translateY(-50%) translateX(-90%); +} + +/* New visual indicator for when a letter is active and bar is collapsed */ +.alphabet-bar-container.collapsed .toggle-alphabet-bar.has-active-letter { + border-color: var(--lora-accent); + background: oklch(var(--lora-accent) / 0.15); +} + +.alphabet-bar-container.collapsed .toggle-alphabet-bar.has-active-letter::after { + content: ''; + position: absolute; + top: 7px; + right: 7px; + width: 8px; + height: 8px; + background-color: var(--lora-accent); + border-radius: 50%; + animation: pulse-active 2s infinite; +} + +@keyframes pulse-active { + 0% { transform: scale(0.8); opacity: 0.7; } + 50% { transform: scale(1.1); opacity: 1; } + 100% { transform: scale(0.8); opacity: 0.7; } +} + .alphabet-bar { background: var(--card-bg); border: 1px solid var(--border-color); - border-radius: var(--border-radius-xs); - padding: 4px; - margin: 8px 0; + border-radius: 0 var(--border-radius-xs) var(--border-radius-xs) 0; + padding: 8px 4px; + display: flex; + flex-direction: column; + gap: 6px; + align-items: center; + box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1); + max-height: 80vh; + overflow-y: auto; + scrollbar-width: thin; +} + +.alphabet-bar::-webkit-scrollbar { + width: 4px; +} + +.alphabet-bar::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 4px; +} + +.toggle-alphabet-bar { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-left: none; + border-radius: 0 var(--border-radius-xs) var(--border-radius-xs) 0; + padding: 8px 4px; + cursor: pointer; display: flex; - gap: 4px; - flex-wrap: wrap; align-items: center; justify-content: center; + color: var(--text-color); + width: 20px; + height: 40px; + align-self: center; + box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1); +} + +.toggle-alphabet-bar:hover { + background: var(--bg-hover); +} + +.toggle-alphabet-bar i { + transition: transform 0.3s ease; +} + +.alphabet-bar-container.collapsed .toggle-alphabet-bar i { + transform: rotate(180deg); } .letter-chip { - padding: 4px 6px; + padding: 4px 2px; border-radius: var(--border-radius-xs); background: var(--bg-color); color: var(--text-color); cursor: pointer; - min-width: 20px; + min-width: 24px; text-align: center; font-size: 0.85em; transition: all 0.2s ease; @@ -28,7 +106,7 @@ .letter-chip:hover { background: var(--lora-accent); color: white; - transform: translateY(-1px); + transform: scale(1.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } @@ -44,35 +122,33 @@ cursor: default; } +/* Hide the count by default, only show in tooltip */ .letter-chip .count { - font-size: 0.75em; - margin-left: 2px; - opacity: 0.7; -} - -.alphabet-bar-section { - margin: 6px 0; - padding: 0 2px; - position: relative; + display: none; } .alphabet-bar-title { font-size: 0.75em; color: var(--text-color); opacity: 0.7; - margin-right: 8px; + margin-bottom: 6px; + writing-mode: vertical-lr; + transform: rotate(180deg); white-space: nowrap; } @media (max-width: 768px) { - .alphabet-bar { - padding: 3px; - gap: 3px; + .alphabet-bar-container { + transform: translateY(-50%) translateX(-90%); + } + + .alphabet-bar-container.active { + transform: translateY(-50%) translateX(0); } .letter-chip { - padding: 3px 5px; - min-width: 16px; + padding: 3px 1px; + min-width: 20px; font-size: 0.75em; } } @@ -80,7 +156,7 @@ /* Keyframe animations for the active letter */ @keyframes pulse { 0% { transform: scale(1); } - 50% { transform: scale(1.05); } + 50% { transform: scale(1.1); } 100% { transform: scale(1); } } diff --git a/static/js/components/alphabet/AlphabetBar.js b/static/js/components/alphabet/AlphabetBar.js index 69ee9369..be1fa0f3 100644 --- a/static/js/components/alphabet/AlphabetBar.js +++ b/static/js/components/alphabet/AlphabetBar.js @@ -33,6 +33,12 @@ export class AlphabetBar { // Restore the active letter filter from storage if available this.restoreActiveLetterFilter(); + + // Restore collapse state from storage + this.restoreCollapseState(); + + // Update the toggle button indicator if there's an active letter filter + this.updateToggleIndicator(); } /** @@ -67,20 +73,21 @@ export class AlphabetBar { letterChips.forEach(chip => { const letter = chip.dataset.letter; - const countSpan = chip.querySelector('.count'); const count = this.letterCounts[letter] || 0; - // Update the count display + // Update the title attribute for tooltip display + if (count > 0) { + chip.title = `${letter}: ${count} LoRAs`; + chip.classList.remove('disabled'); + } else { + chip.title = `${letter}: No LoRAs`; + chip.classList.add('disabled'); + } + + // Keep the count span for backward compatibility + const countSpan = chip.querySelector('.count'); if (countSpan) { - if (count > 0) { - countSpan.textContent = ` (${count})`; - chip.title = `${letter}: ${count} LoRAs`; - chip.classList.remove('disabled'); - } else { - countSpan.textContent = ''; - chip.title = `${letter}: No LoRAs`; - chip.classList.add('disabled'); - } + countSpan.textContent = ` (${count})`; } }); } @@ -90,6 +97,8 @@ export class AlphabetBar { */ initEventListeners() { const alphabetBar = document.querySelector('.alphabet-bar'); + const toggleButton = document.querySelector('.toggle-alphabet-bar'); + const alphabetBarContainer = document.querySelector('.alphabet-bar-container'); if (alphabetBar) { // Use event delegation for letter chips @@ -101,6 +110,25 @@ export class AlphabetBar { } }); + // Add toggle button listener + if (toggleButton && alphabetBarContainer) { + toggleButton.addEventListener('click', () => { + alphabetBarContainer.classList.toggle('collapsed'); + + // If expanding and there's an active letter, scroll it into view + if (!alphabetBarContainer.classList.contains('collapsed')) { + this.scrollActiveLetterIntoView(); + } + + // Save collapse state to storage + setStorageItem(`${this.pageType}_alphabetBarCollapsed`, + alphabetBarContainer.classList.contains('collapsed')); + + // Update toggle indicator + this.updateToggleIndicator(); + }); + } + // Add keyboard shortcut listeners document.addEventListener('keydown', (e) => { // Alt + letter shortcuts @@ -147,6 +175,26 @@ export class AlphabetBar { } } + /** + * Restore the collapse state from storage + */ + restoreCollapseState() { + const alphabetBarContainer = document.querySelector('.alphabet-bar-container'); + + if (alphabetBarContainer) { + const isCollapsed = getStorageItem(`${this.pageType}_alphabetBarCollapsed`); + + // If there's a stored preference, apply it + if (isCollapsed !== null) { + if (isCollapsed) { + alphabetBarContainer.classList.add('collapsed'); + } else { + alphabetBarContainer.classList.remove('collapsed'); + } + } + } + } + /** * Handle letter chip click * @param {HTMLElement} letterChip - The letter chip that was clicked @@ -175,6 +223,9 @@ export class AlphabetBar { setStorageItem(`${this.pageType}_activeLetterFilter`, null); } + // Update visual indicator on toggle button + this.updateToggleIndicator(); + // Trigger a reload with the new filter resetAndReload(true); } @@ -191,6 +242,9 @@ export class AlphabetBar { if (letterChip && !letterChip.classList.contains('disabled')) { letterChip.classList.add('active'); this.pageState.activeLetterFilter = activeLetterFilter; + + // Scroll the active letter into view if the alphabet bar is expanded + this.scrollActiveLetterIntoView(); } } } @@ -209,6 +263,9 @@ export class AlphabetBar { // Remove from storage setStorageItem(`${this.pageType}_activeLetterFilter`, null); + + // Update the toggle button indicator + this.updateToggleIndicator(); } /** @@ -219,4 +276,44 @@ export class AlphabetBar { this.letterCounts = { ...newCounts }; this.updateLetterCountsDisplay(); } + + /** + * Update the toggle button visual indicator based on active filter + */ + updateToggleIndicator() { + const toggleButton = document.querySelector('.toggle-alphabet-bar'); + const hasActiveFilter = this.pageState.activeLetterFilter !== null; + + if (toggleButton) { + if (hasActiveFilter) { + toggleButton.classList.add('has-active-letter'); + } else { + toggleButton.classList.remove('has-active-letter'); + } + } + } + + /** + * Scroll the active letter into view if the alphabet bar is expanded + */ + scrollActiveLetterIntoView() { + if (!this.pageState.activeLetterFilter) return; + + + const alphabetBarContainer = document.querySelector('.alphabet-bar-container'); + if (alphabetBarContainer) { + const activeLetterChip = document.querySelector(`.letter-chip.active`); + + if (activeLetterChip) { + // Use a small timeout to ensure the alphabet bar is fully expanded + setTimeout(() => { + activeLetterChip.scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + }, 300); + } + } + } } \ No newline at end of file diff --git a/static/js/components/controls/LorasControls.js b/static/js/components/controls/LorasControls.js index dd021d5b..0fd26af0 100644 --- a/static/js/components/controls/LorasControls.js +++ b/static/js/components/controls/LorasControls.js @@ -157,16 +157,4 @@ export class LorasControls extends PageControls { // Expose the alphabet bar to the global scope for debugging window.alphabetBar = this.alphabetBar; } - - /** - * Override resetAndReload to update letter counts - */ - async resetAndReload(updateFolders = false) { - await super.resetAndReload(updateFolders); - - // Update letter counts after reload if alphabet bar exists - if (this.alphabetBar) { - this.alphabetBar.fetchLetterCounts(); - } - } } \ No newline at end of file diff --git a/templates/components/alphabet_bar.html b/templates/components/alphabet_bar.html index 9ef5e5fd..f085b360 100644 --- a/templates/components/alphabet_bar.html +++ b/templates/components/alphabet_bar.html @@ -1,6 +1,6 @@ -
\ No newline at end of file