diff --git a/assets/css/app.css b/assets/css/app.css index f3827da..eb05043 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -34,6 +34,25 @@ main { height: 100vh !important; width: 100vw !important; z-index: 1 !important; + transition: right 0.3s ease-in-out !important; +} + +/* Desktop slideover behavior for map */ +@media (min-width: 1024px) { + #aprs-map.slideover-open { + right: 352px !important; + } + + #aprs-map.slideover-closed { + right: 0 !important; + } +} + +/* Mobile slideover behavior for map */ +@media (max-width: 1023px) { + #aprs-map { + right: 0 !important; + } } /* Ensure the map container div has proper dimensions */ @@ -273,3 +292,113 @@ body.home-page main > div { .historical-dot-marker { z-index: 15 !important; } + +/* Slideover toggle button styles */ +.slideover-toggle { + position: fixed !important; + right: 10px; + top: 10px; + z-index: 1001; + background: white; + border: 2px solid rgba(0, 0, 0, 0.2); + border-radius: 8px; + padding: 10px; + cursor: pointer; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.slideover-toggle:hover { + background: #f8fafc; + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2); + transform: translateY(-1px); +} + +.slideover-toggle:active { + transform: translateY(0); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); +} + +/* Hide toggle on desktop when slideover is open */ +@media (min-width: 1024px) { + .slideover-toggle.slideover-open { + display: none; + } +} + +/* Mobile toggle adjustments */ +@media (max-width: 1023px) { + .slideover-toggle { + right: 15px; + top: 15px; + padding: 12px; + border-radius: 50%; + width: 48px; + height: 48px; + } +} + +/* Slideover panel responsive styles */ +.slideover-panel { + position: fixed; + top: 0; + right: 0; + height: 100vh; + width: 352px; + background: white; + box-shadow: -4px 0 24px rgba(0, 0, 0, 0.15); + z-index: 1000; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + overflow-y: auto; +} + +/* Desktop slideover behavior */ +@media (min-width: 1024px) { + .slideover-panel.slideover-open { + transform: translateX(0); + } + + .slideover-panel.slideover-closed { + transform: translateX(100%); + } +} + +/* Mobile slideover behavior */ +@media (max-width: 1023px) { + .slideover-panel { + width: 100vw; + max-width: 400px; + box-shadow: -8px 0 32px rgba(0, 0, 0, 0.2); + } + + .slideover-panel.slideover-open { + transform: translateX(0); + } + + .slideover-panel.slideover-closed { + transform: translateX(100%); + } +} + +/* Mobile panel improvements */ +@media (max-width: 1023px) { + .slideover-panel { + display: flex; + flex-direction: column; + } +} + +/* Improve scrolling on mobile */ +@media (max-width: 1023px) { + .slideover-panel { + -webkit-overflow-scrolling: touch; + overscroll-behavior: contain; + } + + .slideover-panel .flex-1 { + min-height: 0; + } +} diff --git a/assets/js/app.js b/assets/js/app.js index 7960352..cb38335 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -27,9 +27,52 @@ let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute(" // Import minimal APRS map hook import MapAPRSMap from "./map"; +// Responsive Slideover Hook +let ResponsiveSlideoverHook = { + mounted() { + this.isInitialized = false; + + this.handleResize = () => { + const isDesktop = window.innerWidth >= 1024; + const isMobile = window.innerWidth < 1024; + + // Set initial state based on screen size + if (!this.isInitialized) { + this.isInitialized = true; + if (isDesktop) { + this.pushEvent("set_slideover_state", { open: true }); + } else { + this.pushEvent("set_slideover_state", { open: false }); + } + } + }; + + // Initial check after a brief delay to ensure LiveView is ready + setTimeout(() => { + this.handleResize(); + }, 100); + + // Listen for resize events with debouncing + let resizeTimer; + this.debouncedResize = () => { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(this.handleResize, 150); + }; + + window.addEventListener("resize", this.debouncedResize); + }, + + destroyed() { + if (this.debouncedResize) { + window.removeEventListener("resize", this.debouncedResize); + } + }, +}; + // APRS Map Hook let Hooks = {}; Hooks.APRSMap = MapAPRSMap; +Hooks.ResponsiveSlideoverHook = ResponsiveSlideoverHook; let liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, diff --git a/lib/aprs_web/live/map_live/index.ex b/lib/aprs_web/live/map_live/index.ex index 547adb6..670462b 100644 --- a/lib/aprs_web/live/map_live/index.ex +++ b/lib/aprs_web/live/map_live/index.ex @@ -56,7 +56,9 @@ defmodule AprsWeb.MapLive.Index do # Overlay controls overlay_callsign: "", trail_duration: "1", - historical_hours: "1" + historical_hours: "1", + # Slideover state - will be set based on screen size + slideover_open: true ) end @@ -248,6 +250,16 @@ defmodule AprsWeb.MapLive.Index do end end + @impl true + def handle_event("toggle_slideover", _params, socket) do + {:noreply, assign(socket, slideover_open: !socket.assigns.slideover_open)} + end + + @impl true + def handle_event("set_slideover_state", %{"open" => open}, socket) do + {:noreply, assign(socket, slideover_open: open)} + end + @spec handle_bounds_update(map(), Socket.t()) :: {:noreply, Socket.t()} defp handle_bounds_update(bounds, socket) do # Update the map bounds from the client @@ -474,10 +486,28 @@ defmodule AprsWeb.MapLive.Index do position: absolute; top: 0; left: 0; - right: 352px; bottom: 0; height: 100vh; z-index: 1; + transition: right 0.3s ease-in-out; + } + + /* Desktop styles */ + @media (min-width: 1024px) { + #aprs-map.slideover-open { + right: 352px; + } + + #aprs-map.slideover-closed { + right: 0; + } + } + + /* Mobile styles */ + @media (max-width: 1023px) { + #aprs-map { + right: 0 !important; + } } .locate-button { @@ -550,6 +580,7 @@ defmodule AprsWeb.MapLive.Index do
+ + + + + <%= if @slideover_open do %> +
+
+ <% end %> + -
+
-
+

APRS.me

+ + +
-
+
+ + +
+ <.link + navigate={~p"/packets"} + class="flex items-center space-x-2 text-sm text-blue-600 hover:text-blue-800 transition-colors" + > + + + + View All Packets + + <.link + navigate={~p"/badpackets"} + class="flex items-center space-x-2 text-sm text-blue-600 hover:text-blue-800 transition-colors" + > + + + + View Bad Packets + +
"""