diff --git a/stirling-pdf/src/main/resources/static/css/navbar.css b/stirling-pdf/src/main/resources/static/css/navbar.css index cf5bba667..f317f533b 100644 --- a/stirling-pdf/src/main/resources/static/css/navbar.css +++ b/stirling-pdf/src/main/resources/static/css/navbar.css @@ -290,6 +290,21 @@ span.icon-text::after { color: var(--md-sys-color-on-surface-variant); } +.nav-link { + display: flex; + align-items: center; + max-width: 95vw; +} + +.chevron-icon { + margin-left: auto; + transition: transform 0.3s ease; +} + +[aria-expanded="true"] > .chevron-icon { + transform: rotate(180deg); +} + .nav-item { position: relative; } @@ -484,27 +499,88 @@ html[dir="rtl"] .dropdown-menu { display: inline-flex; } -@media (min-width:992px) { +@media (max-width:1199.98px) { + .navbar-collapse .dropdown-menu { + width: 100%; + } + .navbar-collapse .dropdown-menu-wrapper { + width: 100%; + box-sizing: border-box; + } + .navbar-collapse .dropdown-mw-28 { + min-width: 0; + } +} + +@media (min-width:1200px) { + /* This CSS-based hover is disabled because it conflicts with Bootstrap's JavaScript. + Hover functionality is now handled in navbar.js and search.js */ + /* .dropdown:hover .dropdown-menu { display: block; margin-top: 0; } + */ - /* .icon-hide { - display: none; - } */ -} - -@media (max-width:1199px) { - .icon-hide { - display: inline-flex; - } -} - -@media (min-width:1200px) { .icon-hide { display: none; } + .chevron-icon { + display: none !important; + } +} + +@media (max-width: 1199.98px) { + .navbar-collapse .dropdown-menu { + width: 100vw !important; + max-width: 100vw !important; + left: 0 !important; + } + + .navbar-collapse .dropdown-mega .dropdown-menu { + max-height: 60vh; + overflow-y: auto; + } + .navbar-collapse .dropdown-mega .dropdown-menu-wrapper { + border-radius: 0; + } + + #favoritesDropdown, + #languageDropdown + .dropdown-menu .dropdown-menu-wrapper, + #searchDropdown + .dropdown-menu .dropdown-menu-wrapper { + padding: 1.5rem 1rem; + } + + #favoritesDropdown, #languageSelection, #searchResults { + width: 100%; + box-sizing: border-box; + } + + #languageSelection { + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + } + + .navbar-collapse .dropdown-menu-wrapper { + width: 95vw; + box-sizing: border-box; + margin-left: 0 !important; + padding: 0 !important; + } + .navbar-collapse .dropdown-mw-28 { + min-width: 0; + } + .icon-hide { + display: inline-flex; + } + + .navbar-collapse .dropdown-item { + margin-left: 0 !important; + } + + .container { + margin-left: 0px !important; + padding-left: 4px !important; + } } .go-pro-link { @@ -558,6 +634,12 @@ html[dir="rtl"] .dropdown-menu { box-sizing: border-box; } +@media (max-width: 768px) { + .feature-group { + min-width: 10rem; + } +} + .feature-rows { display: flex; flex-wrap: wrap; diff --git a/stirling-pdf/src/main/resources/static/js/navbar.js b/stirling-pdf/src/main/resources/static/js/navbar.js index 1d8c0dcce..c1e92e33f 100644 --- a/stirling-pdf/src/main/resources/static/js/navbar.js +++ b/stirling-pdf/src/main/resources/static/js/navbar.js @@ -42,6 +42,58 @@ function toolsManager() { }); } +function setupDropdownHovers() { + const dropdowns = document.querySelectorAll('.navbar-nav > .nav-item.dropdown'); + + dropdowns.forEach(dropdown => { + const toggle = dropdown.querySelector('[data-bs-toggle="dropdown"]'); + if (!toggle) return; + + // Skip search dropdown, it has its own logic + if (toggle.id === 'searchDropdown') { + return; + } + + let timeout; + const instance = bootstrap.Dropdown.getOrCreateInstance(toggle); + + dropdown.addEventListener('mouseenter', () => { + if (window.innerWidth >= 1200) { + clearTimeout(timeout); + if (!instance._isShown()) { + instance.show(); + } + } + }); + + dropdown.addEventListener('mouseleave', () => { + if (window.innerWidth >= 1200) { + timeout = setTimeout(() => { + if (instance._isShown()) { + instance.hide(); + } + }, 200); + } + }); + + toggle.addEventListener('click', (e) => { + if (window.innerWidth >= 1200) { + // On desktop, prevent Bootstrap's default click toggle + e.preventDefault(); + e.stopPropagation(); + + // Still allow navigation if it's a link + const href = toggle.getAttribute('href'); + if (href && href !== '#') { + window.location.href = href; + } + } + // On mobile (< 1200px), this listener does nothing, allowing default click behavior. + }); + }); +} + + window.tooltipSetup = () => { const tooltipElements = document.querySelectorAll('[title]'); @@ -56,23 +108,27 @@ window.tooltipSetup = () => { document.body.appendChild(customTooltip); element.addEventListener('mouseenter', (event) => { - customTooltip.style.display = 'block'; - customTooltip.style.left = `${event.pageX + 10}px`; // Position tooltip slightly away from the cursor - customTooltip.style.top = `${event.pageY + 10}px`; + if (window.innerWidth >= 1200) { + customTooltip.style.display = 'block'; + customTooltip.style.left = `${event.pageX + 10}px`; + customTooltip.style.top = `${event.pageY + 10}px`; + } }); - // Update the position of the tooltip as the user moves the mouse element.addEventListener('mousemove', (event) => { - customTooltip.style.left = `${event.pageX + 10}px`; - customTooltip.style.top = `${event.pageY + 10}px`; + if (window.innerWidth >= 1200) { + customTooltip.style.left = `${event.pageX + 10}px`; + customTooltip.style.top = `${event.pageY + 10}px`; + } }); - // Hide the tooltip when the mouse leaves element.addEventListener('mouseleave', () => { customTooltip.style.display = 'none'; }); }); }; + document.addEventListener('DOMContentLoaded', () => { tooltipSetup(); + setupDropdownHovers(); }); diff --git a/stirling-pdf/src/main/resources/static/js/search.js b/stirling-pdf/src/main/resources/static/js/search.js index c7932965c..df7c7121a 100644 --- a/stirling-pdf/src/main/resources/static/js/search.js +++ b/stirling-pdf/src/main/resources/static/js/search.js @@ -79,33 +79,67 @@ const searchDropdown = document.getElementById('searchDropdown'); const searchInput = document.getElementById('navbarSearchInput'); const dropdownMenu = searchDropdown.querySelector('.dropdown-menu'); -// Handle dropdown shown event -searchDropdown.addEventListener('shown.bs.dropdown', function () { - searchInput.focus(); +// Create a single dropdown instance +const dropdownInstance = new bootstrap.Dropdown(searchDropdown); + +// Function to handle showing the dropdown +function showSearchDropdown() { + if (!dropdownInstance._isShown()) { + dropdownInstance.show(); + } + setTimeout(() => searchInput.focus(), 150); // Focus after animation +} + +// Handle click for mobile +searchDropdown.addEventListener('click', function (e) { + if (window.innerWidth < 1200) { + // Let Bootstrap's default toggling handle it, but ensure focus + if (!dropdownInstance._isShown()) { + // Use a small delay to allow the dropdown to open before focusing + setTimeout(() => searchInput.focus(), 150); + } + } else { + // On desktop, hover opens the dropdown, so a click shouldn't toggle it. + e.preventDefault(); + } }); -// Handle hover opening +// Handle hover for desktop searchDropdown.addEventListener('mouseenter', function () { - const dropdownInstance = new bootstrap.Dropdown(searchDropdown); - dropdownInstance.show(); - - setTimeout(() => { - searchInput.focus(); - }, 100); -}); - -// Handle mouse leave -searchDropdown.addEventListener('mouseleave', function () { - // Check if current value is empty (including if user typed and then deleted) - if (searchInput.value.trim().length === 0) { - searchInput.blur(); - const dropdownInstance = new bootstrap.Dropdown(searchDropdown); - dropdownInstance.hide(); + if (window.innerWidth >= 1200) { + showSearchDropdown(); } }); -searchDropdown.addEventListener('hidden.bs.dropdown', function () { - if (searchInput.value.trim().length === 0) { - searchInput.blur(); +// Handle mouse leave for desktop +searchDropdown.addEventListener('mouseleave', function (e) { + if (window.innerWidth >= 1200) { + // A short delay to allow moving mouse from button to menu + setTimeout(() => { + const dropdownMenu = searchDropdown.querySelector('.dropdown-menu'); + if (!dropdownMenu) return; + + // Check if either the button or the menu is still hovered + const isHoveringButton = searchDropdown.matches(':hover'); + const isHoveringMenu = dropdownMenu.matches(':hover'); + + if (!isHoveringButton && !isHoveringMenu && searchInput.value.trim().length === 0) { + dropdownInstance.hide(); + } + }, 200); } }); + +// Hide dropdown if it's open and user clicks outside +document.addEventListener('click', function(e) { + if (!searchDropdown.contains(e.target) && dropdownInstance._isShown()) { + if (searchInput.value.trim().length === 0) { + dropdownInstance.hide(); + } + } +}); + +// Keep dropdown open if search input is clicked +searchInput.addEventListener('click', function (e) { + e.stopPropagation(); +}); diff --git a/stirling-pdf/src/main/resources/templates/fragments/common.html b/stirling-pdf/src/main/resources/templates/fragments/common.html index d873fd7a1..ea0ac1f1f 100644 --- a/stirling-pdf/src/main/resources/templates/fragments/common.html +++ b/stirling-pdf/src/main/resources/templates/fragments/common.html @@ -31,6 +31,10 @@ const isHighDPI = systemDPR > 1.4; function scaleNav() { + if (window.innerWidth < 1200) { + // Don't scale nav on mobile + return; + } const currentDPR = window.devicePixelRatio || 1; const browserZoom = currentDPR / systemDPR; diff --git a/stirling-pdf/src/main/resources/templates/fragments/navbar.html b/stirling-pdf/src/main/resources/templates/fragments/navbar.html index f6f707211..6c2198b25 100644 --- a/stirling-pdf/src/main/resources/templates/fragments/navbar.html +++ b/stirling-pdf/src/main/resources/templates/fragments/navbar.html @@ -45,9 +45,10 @@ apps +