diff --git a/src/animaltrack/web/templates/base.py b/src/animaltrack/web/templates/base.py index 9cbddca..bd35017 100644 --- a/src/animaltrack/web/templates/base.py +++ b/src/animaltrack/web/templates/base.py @@ -6,6 +6,7 @@ from starlette.requests import Request from animaltrack.models.reference import UserRole from animaltrack.web.templates.nav import BottomNav, BottomNavStyles +from animaltrack.web.templates.shared_scripts import slide_over_script from animaltrack.web.templates.sidebar import ( MenuDrawer, Sidebar, @@ -79,29 +80,13 @@ def EventSlideOverStyles(): # noqa: N802 def EventSlideOverScript(): # noqa: N802 """JavaScript for event slide-over panel open/close behavior.""" - return Script(""" - function openEventPanel() { - document.getElementById('event-slide-over').classList.add('open'); - document.getElementById('event-backdrop').classList.add('open'); - document.body.style.overflow = 'hidden'; - // Focus the panel for keyboard events - document.getElementById('event-slide-over').focus(); - } - - function closeEventPanel() { - document.getElementById('event-slide-over').classList.remove('open'); - document.getElementById('event-backdrop').classList.remove('open'); - document.body.style.overflow = ''; - } - - // HTMX event: after loading event content, open the panel - document.body.addEventListener('htmx:afterSwap', function(evt) { - if (evt.detail.target.id === 'event-slide-over' || - evt.detail.target.id === 'event-panel-content') { - openEventPanel(); - } - }); - """) + return slide_over_script( + panel_id="event-slide-over", + backdrop_id="event-backdrop", + open_fn_name="openEventPanel", + close_fn_name="closeEventPanel", + htmx_auto_open_targets=["event-slide-over", "event-panel-content"], + ) def CsrfHeaderScript(): # noqa: N802 diff --git a/src/animaltrack/web/templates/shared_scripts.py b/src/animaltrack/web/templates/shared_scripts.py new file mode 100644 index 0000000..855cba1 --- /dev/null +++ b/src/animaltrack/web/templates/shared_scripts.py @@ -0,0 +1,58 @@ +# ABOUTME: Shared JavaScript script generators for AnimalTrack templates. +# ABOUTME: Provides reusable script components to reduce code duplication. + +from fasthtml.common import Script + + +def slide_over_script( + panel_id: str, + backdrop_id: str, + open_fn_name: str, + close_fn_name: str, + htmx_auto_open_targets: list[str] | None = None, +) -> Script: + """Generate JavaScript for slide-over panel open/close behavior. + + Creates global functions for opening and closing a slide-over panel with + backdrop. Optionally auto-opens when HTMX swaps content into specified targets. + + Args: + panel_id: DOM ID of the slide-over panel element. + backdrop_id: DOM ID of the backdrop overlay element. + open_fn_name: Name of the global function to open the panel. + close_fn_name: Name of the global function to close the panel. + htmx_auto_open_targets: List of target element IDs that trigger auto-open + when HTMX swaps content into them. + + Returns: + Script element containing the JavaScript code. + """ + # Build HTMX auto-open listener if targets specified + htmx_listener = "" + if htmx_auto_open_targets: + conditions = " ||\n ".join( + f"evt.detail.target.id === '{target}'" for target in htmx_auto_open_targets + ) + htmx_listener = f""" + // HTMX event: after loading content, open the panel + document.body.addEventListener('htmx:afterSwap', function(evt) {{ + if ({conditions}) {{ + {open_fn_name}(); + }} + }});""" + + return Script(f""" + function {open_fn_name}() {{ + document.getElementById('{panel_id}').classList.add('open'); + document.getElementById('{backdrop_id}').classList.add('open'); + document.body.style.overflow = 'hidden'; + // Focus the panel for keyboard events + document.getElementById('{panel_id}').focus(); + }} + + function {close_fn_name}() {{ + document.getElementById('{panel_id}').classList.remove('open'); + document.getElementById('{backdrop_id}').classList.remove('open'); + document.body.style.overflow = ''; + }}{htmx_listener} + """) diff --git a/src/animaltrack/web/templates/sidebar.py b/src/animaltrack/web/templates/sidebar.py index 9f094df..78aadf6 100644 --- a/src/animaltrack/web/templates/sidebar.py +++ b/src/animaltrack/web/templates/sidebar.py @@ -1,12 +1,13 @@ # ABOUTME: Responsive sidebar and menu drawer components for AnimalTrack. # ABOUTME: Desktop shows persistent sidebar, mobile shows slide-out drawer. -from fasthtml.common import A, Button, Div, Nav, Script, Span, Style +from fasthtml.common import A, Button, Div, Nav, Span, Style from fasthtml.svg import Path, Svg from animaltrack.build_info import get_build_info from animaltrack.models.reference import UserRole from animaltrack.web.templates.icons import EggIcon, FeedIcon, MoveIcon +from animaltrack.web.templates.shared_scripts import slide_over_script def SidebarStyles(): # noqa: N802 @@ -73,21 +74,12 @@ def SidebarStyles(): # noqa: N802 def SidebarScript(): # noqa: N802 """JavaScript for menu drawer open/close behavior.""" - return Script(""" - function openMenuDrawer() { - document.getElementById('menu-drawer').classList.add('open'); - document.getElementById('menu-backdrop').classList.add('open'); - document.body.style.overflow = 'hidden'; - // Focus the drawer for keyboard events - document.getElementById('menu-drawer').focus(); - } - - function closeMenuDrawer() { - document.getElementById('menu-drawer').classList.remove('open'); - document.getElementById('menu-backdrop').classList.remove('open'); - document.body.style.overflow = ''; - } - """) + return slide_over_script( + panel_id="menu-drawer", + backdrop_id="menu-backdrop", + open_fn_name="openMenuDrawer", + close_fn_name="closeMenuDrawer", + ) def _primary_nav_item(label: str, href: str, icon_fn, is_active: bool):