fixed toolbar menu
This commit is contained in:
31
resources/js/components/Topbar.jsx
Normal file
31
resources/js/components/Topbar.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function Topbar() {
|
||||
return (
|
||||
<header className="fixed top-0 left-0 right-0 h-16 bg-neutral-900 border-b border-neutral-800 z-50">
|
||||
<div className="h-full px-5 flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<button aria-label="Toggle menu" className="md:hidden text-neutral-200 hover:text-sky-400">
|
||||
<i className="fas fa-bars text-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<a href="/" className="text-sky-400 font-semibold text-xl">Skinbase</a>
|
||||
</div>
|
||||
|
||||
<div className="hidden md:block flex-1 max-w-xl">
|
||||
<form action="/search" method="get" className="relative">
|
||||
<input name="q" aria-label="Search" placeholder="Search tags, artworks, artists…"
|
||||
className="w-full bg-neutral-800 border border-neutral-700 rounded-lg py-2.5 pl-3.5 pr-10 text-white outline-none focus:border-sky-400" />
|
||||
<i className="fas fa-search absolute right-3.5 top-1/2 -translate-y-1/2 text-neutral-400" aria-hidden="true"></i>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 sm:gap-5">
|
||||
<a href="/forum" className="hidden sm:inline text-sm hover:text-sky-400">Forum</a>
|
||||
<button aria-label="User menu" className="text-neutral-200 hover:text-sky-400">
|
||||
<i className="fas fa-user" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
23
resources/js/entry-topbar.jsx
Normal file
23
resources/js/entry-topbar.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import Topbar from './components/Topbar'
|
||||
|
||||
function mount() {
|
||||
const container = document.getElementById('topbar-root')
|
||||
if (!container) return
|
||||
|
||||
const root = createRoot(container)
|
||||
root.render(<Topbar />)
|
||||
|
||||
// hide legacy header if present
|
||||
const legacy = document.getElementById('legacy-topbar')
|
||||
const topbar = document.getElementById('topbar')
|
||||
if (legacy) legacy.style.display = 'none'
|
||||
if (topbar) topbar.style.display = 'none'
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', mount)
|
||||
} else {
|
||||
mount()
|
||||
}
|
||||
@@ -101,13 +101,47 @@
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var dropdownToggle = closest(e.target, '[data-dropdown-toggle]');
|
||||
// legacy shorthand toggles: data-dd="name" -> menu id = dd-name
|
||||
var legacyToggle = closest(e.target, '[data-dd]');
|
||||
if (dropdownToggle) {
|
||||
// On pointer/hover-capable devices prefer hover; ignore mouse clicks
|
||||
if (canHover() && e.detail > 0) {
|
||||
// allow keyboard activation (e.detail === 0) to fall through
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
var dropdown = closest(dropdownToggle, '[data-dropdown]');
|
||||
if (dropdown) toggleDropdown(dropdown);
|
||||
return;
|
||||
}
|
||||
|
||||
if (legacyToggle) {
|
||||
// On pointer/hover-capable devices prefer hover; ignore mouse clicks
|
||||
if (canHover() && e.detail > 0) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
var ddName = legacyToggle.getAttribute('data-dd');
|
||||
if (!ddName) return;
|
||||
var menu = document.getElementById('dd-' + ddName);
|
||||
if (!menu) return;
|
||||
|
||||
// treat this pair (toggle + menu) similarly to our dropdown API
|
||||
var isOpen = !menu.classList.contains('hidden');
|
||||
// close other dropdowns
|
||||
closeAllDropdowns();
|
||||
|
||||
if (isOpen) {
|
||||
menu.classList.add('hidden');
|
||||
setExpanded(legacyToggle, false);
|
||||
} else {
|
||||
menu.classList.remove('hidden');
|
||||
setExpanded(legacyToggle, true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var mobileToggle = closest(e.target, '[data-mobile-toggle]');
|
||||
if (mobileToggle) {
|
||||
e.preventDefault();
|
||||
@@ -171,11 +205,50 @@
|
||||
hoverCloseTimers.set(
|
||||
dropdown,
|
||||
window.setTimeout(function () {
|
||||
closeDropdown(dropdown);
|
||||
closeMenuElement(dropdown);
|
||||
}, 140)
|
||||
);
|
||||
}
|
||||
|
||||
// Close a menu element or its parent dropdown wrapper.
|
||||
function closeMenuElement(el) {
|
||||
if (!el) return;
|
||||
|
||||
// If this is a dropdown wrapper, find the menu inside
|
||||
if (el.hasAttribute && el.hasAttribute('data-dropdown')) {
|
||||
var menu = el.querySelector('[data-dropdown-menu]');
|
||||
var toggle = el.querySelector('[data-dropdown-toggle]');
|
||||
if (menu) menu.classList.add('hidden');
|
||||
setExpanded(toggle, false);
|
||||
// also close submenus inside
|
||||
el.querySelectorAll('[data-submenu-menu]').forEach(function (sm) {
|
||||
sm.classList.add('hidden');
|
||||
});
|
||||
el.querySelectorAll('[data-submenu-toggle]').forEach(function (st) {
|
||||
setExpanded(st, false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's a menu element (e.g., legacy id=dd-name) hide it and try to find its toggle
|
||||
var menuEl = el;
|
||||
if (!menuEl.id && el.getAttribute && el.getAttribute('data-dropdown-menu')) {
|
||||
// explicit menu element
|
||||
}
|
||||
|
||||
// hide the element if possible
|
||||
try { menuEl.classList.add('hidden'); } catch (e) {}
|
||||
|
||||
// Try to map back to a toggle: id like dd-name -> data-dd="name"
|
||||
if (menuEl.id && menuEl.id.indexOf('dd-') === 0) {
|
||||
var name = menuEl.id.slice(3);
|
||||
var toggle = document.querySelector('[data-dd="' + name + '"]');
|
||||
if (toggle) setExpanded(toggle, false);
|
||||
} else {
|
||||
// fallback: if menu is inside a [data-dropdown], handled above; nothing more to do
|
||||
}
|
||||
}
|
||||
|
||||
function bindHoverHandlers() {
|
||||
if (!canHover()) return;
|
||||
document.querySelectorAll('[data-dropdown]').forEach(function (dropdown) {
|
||||
@@ -187,6 +260,30 @@
|
||||
scheduleClose(dropdown);
|
||||
});
|
||||
});
|
||||
|
||||
// legacy hover binding for shorthand toggles (data-dd)
|
||||
document.querySelectorAll('[data-dd]').forEach(function (el) {
|
||||
var ddName = el.getAttribute('data-dd');
|
||||
if (!ddName) return;
|
||||
var menu = document.getElementById('dd-' + ddName);
|
||||
if (!menu) return;
|
||||
|
||||
// when pointer enters either toggle or menu, open
|
||||
function enter() {
|
||||
clearHoverTimer(menu);
|
||||
menu.classList.remove('hidden');
|
||||
setExpanded(el, true);
|
||||
}
|
||||
|
||||
function leave() {
|
||||
scheduleClose(menu);
|
||||
}
|
||||
|
||||
el.addEventListener('mouseenter', enter);
|
||||
el.addEventListener('mouseleave', leave);
|
||||
menu.addEventListener('mouseenter', enter);
|
||||
menu.addEventListener('mouseleave', leave);
|
||||
});
|
||||
}
|
||||
|
||||
bindHoverHandlers();
|
||||
|
||||
Reference in New Issue
Block a user