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"""
|
~H"""
|
||||||
<header class="navbar px-4 sm:px-6 lg:px-8 border-b border-base-200">
|
<header class="navbar px-4 sm:px-6 lg:px-8 border-b border-base-200">
|
||||||
<div class="flex-1 gap-4">
|
<div class="flex-1 gap-4">
|
||||||
<a href="/" class="font-semibold tracking-tight">NTMS Microwave Propagation</a>
|
<a href="/" class="font-semibold tracking-tight shrink-0">NTMS Microwave Propagation</a>
|
||||||
<nav class="flex gap-1">
|
<nav class="hidden lg:flex gap-1">
|
||||||
<.link navigate="/map" class="btn btn-ghost btn-sm">Map</.link>
|
<.link navigate="/map" class="btn btn-ghost btn-sm">Map</.link>
|
||||||
<.link navigate="/path" class="btn btn-ghost btn-sm">Path</.link>
|
<.link navigate="/path" class="btn btn-ghost btn-sm">Path</.link>
|
||||||
<.link navigate="/eme" class="btn btn-ghost btn-sm">EME</.link>
|
<.link navigate="/eme" class="btn btn-ghost btn-sm">EME</.link>
|
||||||
|
|
@ -98,11 +98,191 @@ defmodule MicrowavepropWeb.Layouts do
|
||||||
<% end %>
|
<% end %>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</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 />
|
<.theme_toggle />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</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">
|
<main class="px-4 py-20 sm:px-6 lg:px-8">
|
||||||
<div class={["mx-auto space-y-4", @max_width]}>
|
<div class={["mx-auto space-y-4", @max_width]}>
|
||||||
{render_slot(@inner_block)}
|
{render_slot(@inner_block)}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue