diff --git a/frontend/app.js b/frontend/app.js index 7c5dcbd..ed9e767 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -160,6 +160,127 @@ menuDropdown.classList.remove("active"); } + // --------------------------------------------------------------------------- + // Custom Dropdowns + // --------------------------------------------------------------------------- + function initCustomDropdowns() { + document.querySelectorAll('.custom-dropdown').forEach(dropdown => { + const trigger = dropdown.querySelector('.custom-dropdown-trigger'); + const options = dropdown.querySelectorAll('.custom-dropdown-option'); + const hiddenInput = dropdown.querySelector('input[type="hidden"]'); + const selectedText = dropdown.querySelector('.custom-dropdown-selected'); + + if (!trigger) return; + + // Toggle dropdown + trigger.addEventListener('click', (e) => { + e.stopPropagation(); + const isOpen = dropdown.classList.contains('open'); + + // Close all other dropdowns + document.querySelectorAll('.custom-dropdown.open').forEach(d => { + if (d !== dropdown) d.classList.remove('open'); + }); + + dropdown.classList.toggle('open', !isOpen); + trigger.setAttribute('aria-expanded', !isOpen); + }); + + // Handle option selection + options.forEach(option => { + option.addEventListener('click', (e) => { + e.stopPropagation(); + const value = option.getAttribute('data-value'); + const text = option.textContent; + + // Update hidden input + if (hiddenInput) { + hiddenInput.value = value; + // Trigger change event + hiddenInput.dispatchEvent(new Event('change', { bubbles: true })); + } + + // Update selected text + if (selectedText) { + selectedText.textContent = text; + } + + // Update visual selection + options.forEach(opt => opt.classList.remove('selected')); + option.classList.add('selected'); + + // Close dropdown + dropdown.classList.remove('open'); + trigger.setAttribute('aria-expanded', 'false'); + }); + }); + }); + + // Close dropdowns when clicking outside + document.addEventListener('click', () => { + document.querySelectorAll('.custom-dropdown.open').forEach(dropdown => { + dropdown.classList.remove('open'); + const trigger = dropdown.querySelector('.custom-dropdown-trigger'); + if (trigger) trigger.setAttribute('aria-expanded', 'false'); + }); + }); + } + + // Helper to populate custom dropdown options + function populateCustomDropdown(dropdownId, optionsList, defaultValue) { + const dropdown = document.getElementById(dropdownId); + if (!dropdown) return; + + const optionsContainer = dropdown.querySelector('.custom-dropdown-menu'); + const hiddenInput = dropdown.querySelector('input[type="hidden"]'); + const selectedText = dropdown.querySelector('.custom-dropdown-selected'); + + if (!optionsContainer) return; + + // Clear existing options (keep the first one if it's the default) + optionsContainer.innerHTML = ''; + + // Add new options + optionsList.forEach(opt => { + const li = document.createElement('li'); + li.className = 'custom-dropdown-option'; + li.setAttribute('role', 'option'); + li.setAttribute('data-value', opt.value); + li.textContent = opt.text; + if (opt.value === defaultValue) { + li.classList.add('selected'); + if (selectedText) selectedText.textContent = opt.text; + if (hiddenInput) hiddenInput.value = opt.value; + } + optionsContainer.appendChild(li); + }); + + // Re-initialize click handlers + optionsContainer.querySelectorAll('.custom-dropdown-option').forEach(option => { + option.addEventListener('click', (e) => { + e.stopPropagation(); + const value = option.getAttribute('data-value'); + const text = option.textContent; + + if (hiddenInput) { + hiddenInput.value = value; + hiddenInput.dispatchEvent(new Event('change', { bubbles: true })); + } + + if (selectedText) { + selectedText.textContent = text; + } + + optionsContainer.querySelectorAll('.custom-dropdown-option').forEach(opt => opt.classList.remove('selected')); + option.classList.add('selected'); + + dropdown.classList.remove('open'); + const trigger = dropdown.querySelector('.custom-dropdown-trigger'); + if (trigger) trigger.setAttribute('aria-expanded', 'false'); + }); + }); + } + // --------------------------------------------------------------------------- // API helpers // --------------------------------------------------------------------------- @@ -307,11 +428,17 @@ const vaults = await api("/api/vaults"); allVaults = vaults; const container = document.getElementById("vault-tree"); - const filter = document.getElementById("vault-filter"); - const quickSelect = document.getElementById("vault-quick-select"); container.innerHTML = ""; - filter.innerHTML = ''; - quickSelect.innerHTML = ''; + + // Prepare dropdown options + const dropdownOptions = [ + { value: "all", text: "Tous les vaults" }, + ...vaults.map(v => ({ value: v.name, text: v.name })) + ]; + + // Populate custom dropdowns + populateCustomDropdown("vault-filter-dropdown", dropdownOptions, "all"); + populateCustomDropdown("vault-quick-select-dropdown", dropdownOptions, "all"); vaults.forEach((v) => { // Sidebar tree entry @@ -326,17 +453,6 @@ const childContainer = el("div", { class: "tree-children collapsed", id: `vault-children-${v.name}` }); container.appendChild(childContainer); - - // Vault filter dropdown - const opt = document.createElement("option"); - opt.value = v.name; - opt.textContent = v.name; - filter.appendChild(opt); - - const quickOpt = document.createElement("option"); - quickOpt.value = v.name; - quickOpt.textContent = v.name; - quickSelect.appendChild(quickOpt); }); syncVaultSelectors(); @@ -1329,6 +1445,7 @@ async function init() { initTheme(); initHeaderMenu(); + initCustomDropdowns(); document.getElementById("theme-toggle").addEventListener("click", toggleTheme); document.getElementById("header-logo").addEventListener("click", goHome); initSearch(); diff --git a/frontend/index.html b/frontend/index.html index 4585003..e40c914 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -95,9 +95,16 @@ Vault - +
+ + + +
+ +