From c3985fe742381b6ea6cfa7da00e22a2365dfe57b Mon Sep 17 00:00:00 2001 From: ookami125 Date: Mon, 22 Jun 2026 00:26:58 -0400 Subject: [PATCH] webui: float the tags dropdown over content (fixed-position) The checkbox menu now uses position:fixed anchored under the button, overlaying the rest of the dialog instead of expanding it. Closes on outside click; repositions on scroll/resize. Co-Authored-By: Claude Opus 4.8 --- public/css/styles.css | 5 +++-- public/js/app.js | 30 ++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/public/css/styles.css b/public/css/styles.css index d50e709..34feef4 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -417,8 +417,9 @@ code.inline { background: var(--bg-3); border: 1px solid var(--line); border-rad .cbdrop-btn:hover { border-color: var(--accent); } .cbdrop-btn > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; } .cbdrop-caret { color: var(--txt-faint); flex: none; } -.cbdrop-menu { margin-top: 6px; background: var(--bg-2); border: 1px solid var(--line); border-radius: 8px; - padding: 6px; max-height: 200px; overflow: auto; } +.cbdrop-menu { position: fixed; z-index: 300; background: var(--bg-2); border: 1px solid var(--line); + border-radius: 8px; padding: 6px; max-height: 240px; overflow: auto; + box-shadow: 0 12px 40px rgba(0,0,0,.5); } .cbdrop-item { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: 5px; cursor: pointer; color: var(--txt); font-size: 13px; } .cbdrop-item:hover { background: var(--bg-3); } diff --git a/public/js/app.js b/public/js/app.js index 66eedf5..3c0c815 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -544,8 +544,9 @@ function openAddModal() { closeModal(); }, 'Failed to add torrent'), }]); - // tags checkbox dropdown: toggle, live label, inline new-tag creation + // tags checkbox dropdown: a fixed-position menu that floats over the dialog (() => { + const btn = document.getElementById('addTagsBtn'); const menu = document.getElementById('addTagsMenu'); const list = document.getElementById('addTagsList'); const label = document.getElementById('addTagsLabel'); @@ -571,8 +572,33 @@ function openAddModal() { cb.checked = true; input.value = ''; refresh(); + place(); }; - document.getElementById('addTagsBtn').addEventListener('click', () => { menu.hidden = !menu.hidden; }); + // Anchor the floating menu under the button and keep it on-screen. + const place = () => { + const r = btn.getBoundingClientRect(); + menu.style.left = r.left + 'px'; + menu.style.width = r.width + 'px'; + menu.style.top = (r.bottom + 4) + 'px'; + menu.style.maxHeight = Math.max(120, window.innerHeight - r.bottom - 16) + 'px'; + }; + let onOutside; let onReflow; + const close = () => { + menu.hidden = true; + document.removeEventListener('mousedown', onOutside, true); + document.removeEventListener('scroll', onReflow, true); + window.removeEventListener('resize', onReflow); + }; + const open = () => { + menu.hidden = false; + place(); + onOutside = (e) => { if (!menu.contains(e.target) && !btn.contains(e.target)) close(); }; + onReflow = () => place(); + document.addEventListener('mousedown', onOutside, true); + document.addEventListener('scroll', onReflow, true); // reposition on modal scroll + window.addEventListener('resize', onReflow); + }; + btn.addEventListener('click', () => (menu.hidden ? open() : close())); list.addEventListener('change', refresh); document.getElementById('addTagAdd').addEventListener('click', addTag); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); addTag(); } });