feat: responsive mobile nav with hamburger menu
Some checks failed
Build and Push / Build and Push Docker Image (push) Has been cancelled
Some checks failed
Build and Push / Build and Push Docker Image (push) Has been cancelled
This commit is contained in:
parent
ffbaa6230f
commit
e8ff087807
1 changed files with 183 additions and 3 deletions
|
|
@ -40,8 +40,8 @@ defmodule MicrowavepropWeb.Layouts do
|
|||
~H"""
|
||||
<header class="navbar px-4 sm:px-6 lg:px-8 border-b border-base-200">
|
||||
<div class="flex-1 gap-4">
|
||||
<a href="/" class="font-semibold tracking-tight">NTMS Microwave Propagation</a>
|
||||
<nav class="flex gap-1">
|
||||
<a href="/" class="font-semibold tracking-tight shrink-0">NTMS Microwave Propagation</a>
|
||||
<nav class="hidden lg:flex gap-1">
|
||||
<.link navigate="/map" class="btn btn-ghost btn-sm">Map</.link>
|
||||
<.link navigate="/path" class="btn btn-ghost btn-sm">Path</.link>
|
||||
<.link navigate="/eme" class="btn btn-ghost btn-sm">EME</.link>
|
||||
|
|
@ -98,11 +98,191 @@ defmodule MicrowavepropWeb.Layouts do
|
|||
<% end %>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex-none flex items-center gap-3">
|
||||
<div class="flex-none flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
id="mobile-menu-btn"
|
||||
class="lg:hidden btn btn-ghost btn-sm"
|
||||
aria-label="Toggle menu"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span id="menu-icon-bars"><.icon name="hero-bars-3" class="size-5" /></span>
|
||||
<span id="menu-icon-x" class="hidden"><.icon name="hero-x-mark" class="size-5" /></span>
|
||||
</button>
|
||||
<.theme_toggle />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div
|
||||
id="mobile-menu"
|
||||
phx-hook=".MobileMenu"
|
||||
class="lg:hidden overflow-hidden transition-all duration-300 ease-in-out max-h-0 opacity-0 bg-base-100 border-b border-base-200 shadow-lg"
|
||||
>
|
||||
<nav class="px-4 py-3 space-y-0.5">
|
||||
<.link
|
||||
navigate="/map"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Map</.link>
|
||||
<.link
|
||||
navigate="/path"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Path</.link>
|
||||
<.link
|
||||
navigate="/eme"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>EME</.link>
|
||||
<div class="pt-2">
|
||||
<div class="px-3 py-1 text-xs font-semibold uppercase tracking-wider text-base-content/40">
|
||||
Rover
|
||||
</div>
|
||||
<.link
|
||||
navigate="/rover"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Rover Planner</.link>
|
||||
<.link
|
||||
navigate="/rover-locations"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Rover Locations</.link>
|
||||
<.link
|
||||
navigate="/rover-planning"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Rover Planning</.link>
|
||||
</div>
|
||||
<.link
|
||||
navigate="/beacons"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Beacons</.link>
|
||||
<.link
|
||||
navigate="/submit"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Submit</.link>
|
||||
<.link
|
||||
navigate="/contacts"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Contacts</.link>
|
||||
<.link
|
||||
navigate="/contacts/map"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Contact Map</.link>
|
||||
<.link
|
||||
navigate="/about"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>About</.link>
|
||||
<div :if={@current_scope && @current_scope.user && @current_scope.user.is_admin} class="pt-2">
|
||||
<div class="px-3 py-1 text-xs font-semibold uppercase tracking-wider text-base-content/40">
|
||||
Admin
|
||||
</div>
|
||||
<.link
|
||||
navigate="/users"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Users</.link>
|
||||
<.link
|
||||
navigate="/admin/beacon-monitors"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Beacon monitors</.link>
|
||||
<.link
|
||||
navigate="/admin/contact-edits"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Contact edits</.link>
|
||||
<.link
|
||||
navigate="/status"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Status</.link>
|
||||
<.link
|
||||
href="/admin/oban"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Oban</.link>
|
||||
</div>
|
||||
<div class="border-t border-base-200 my-2"></div>
|
||||
<%= if @current_scope && @current_scope.user do %>
|
||||
<.link
|
||||
navigate={~p"/u/#{@current_scope.user.callsign}"}
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>{@current_scope.user.callsign}</.link>
|
||||
<.link
|
||||
href={~p"/users/settings"}
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Settings</.link>
|
||||
<.link
|
||||
href={~p"/users/log-out"}
|
||||
method="delete"
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Log out</.link>
|
||||
<% else %>
|
||||
<.link
|
||||
href={~p"/users/register"}
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Register</.link>
|
||||
<.link
|
||||
href={~p"/users/log-in"}
|
||||
class="block px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors text-sm font-medium"
|
||||
>Log in</.link>
|
||||
<% end %>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<script :type={Phoenix.LiveView.ColocatedHook} name=".MobileMenu">
|
||||
export default {
|
||||
mounted() {
|
||||
this.panel = this.el;
|
||||
this.btn = document.getElementById("mobile-menu-btn");
|
||||
this.barsIcon = document.getElementById("menu-icon-bars");
|
||||
this.xIcon = document.getElementById("menu-icon-x");
|
||||
this.open = false;
|
||||
|
||||
this.onBtnClick = () => this.toggle();
|
||||
this.btn.addEventListener("click", this.onBtnClick);
|
||||
|
||||
this.onOutsideClick = (e) => {
|
||||
if (!this.open) return;
|
||||
if (!this.panel.contains(e.target) && !this.btn.contains(e.target)) {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
document.addEventListener("click", this.onOutsideClick);
|
||||
|
||||
this.onLinkClick = () => this.close();
|
||||
this.panel.querySelectorAll("a").forEach((link) => {
|
||||
link.addEventListener("click", this.onLinkClick);
|
||||
});
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
if (this.btn) {
|
||||
this.btn.removeEventListener("click", this.onBtnClick);
|
||||
}
|
||||
document.removeEventListener("click", this.onOutsideClick);
|
||||
if (this.panel) {
|
||||
this.panel.querySelectorAll("a").forEach((link) => {
|
||||
link.removeEventListener("click", this.onLinkClick);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
toggle() {
|
||||
this.open ? this.close() : this.open_();
|
||||
},
|
||||
|
||||
open_() {
|
||||
this.open = true;
|
||||
this.panel.classList.remove("max-h-0", "opacity-0");
|
||||
this.panel.classList.add("max-h-[40rem]", "opacity-100");
|
||||
if (this.barsIcon) this.barsIcon.classList.add("hidden");
|
||||
if (this.xIcon) this.xIcon.classList.remove("hidden");
|
||||
this.btn.setAttribute("aria-expanded", "true");
|
||||
},
|
||||
|
||||
close() {
|
||||
this.open = false;
|
||||
this.panel.classList.add("max-h-0", "opacity-0");
|
||||
this.panel.classList.remove("max-h-[40rem]", "opacity-100");
|
||||
if (this.barsIcon) this.barsIcon.classList.remove("hidden");
|
||||
if (this.xIcon) this.xIcon.classList.add("hidden");
|
||||
this.btn.setAttribute("aria-expanded", "false");
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<main class="px-4 py-20 sm:px-6 lg:px-8">
|
||||
<div class={["mx-auto space-y-4", @max_width]}>
|
||||
{render_slot(@inner_block)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue