Extract slide-over script to shared component

Creates slide_over_script() in shared_scripts.py that generates JavaScript
for slide-over panels with open/close functions. EventSlideOverScript and
SidebarScript now use this shared function, reducing duplicated JS logic.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-09 12:20:18 +00:00
parent fc4c2a8e40
commit 4e78b79745
3 changed files with 74 additions and 39 deletions

View File

@@ -6,6 +6,7 @@ from starlette.requests import Request
from animaltrack.models.reference import UserRole from animaltrack.models.reference import UserRole
from animaltrack.web.templates.nav import BottomNav, BottomNavStyles from animaltrack.web.templates.nav import BottomNav, BottomNavStyles
from animaltrack.web.templates.shared_scripts import slide_over_script
from animaltrack.web.templates.sidebar import ( from animaltrack.web.templates.sidebar import (
MenuDrawer, MenuDrawer,
Sidebar, Sidebar,
@@ -79,29 +80,13 @@ def EventSlideOverStyles(): # noqa: N802
def EventSlideOverScript(): # noqa: N802 def EventSlideOverScript(): # noqa: N802
"""JavaScript for event slide-over panel open/close behavior.""" """JavaScript for event slide-over panel open/close behavior."""
return Script(""" return slide_over_script(
function openEventPanel() { panel_id="event-slide-over",
document.getElementById('event-slide-over').classList.add('open'); backdrop_id="event-backdrop",
document.getElementById('event-backdrop').classList.add('open'); open_fn_name="openEventPanel",
document.body.style.overflow = 'hidden'; close_fn_name="closeEventPanel",
// Focus the panel for keyboard events htmx_auto_open_targets=["event-slide-over", "event-panel-content"],
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();
}
});
""")
def CsrfHeaderScript(): # noqa: N802 def CsrfHeaderScript(): # noqa: N802

View File

@@ -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}
""")

View File

@@ -1,12 +1,13 @@
# ABOUTME: Responsive sidebar and menu drawer components for AnimalTrack. # ABOUTME: Responsive sidebar and menu drawer components for AnimalTrack.
# ABOUTME: Desktop shows persistent sidebar, mobile shows slide-out drawer. # 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 fasthtml.svg import Path, Svg
from animaltrack.build_info import get_build_info from animaltrack.build_info import get_build_info
from animaltrack.models.reference import UserRole from animaltrack.models.reference import UserRole
from animaltrack.web.templates.icons import EggIcon, FeedIcon, MoveIcon from animaltrack.web.templates.icons import EggIcon, FeedIcon, MoveIcon
from animaltrack.web.templates.shared_scripts import slide_over_script
def SidebarStyles(): # noqa: N802 def SidebarStyles(): # noqa: N802
@@ -73,21 +74,12 @@ def SidebarStyles(): # noqa: N802
def SidebarScript(): # noqa: N802 def SidebarScript(): # noqa: N802
"""JavaScript for menu drawer open/close behavior.""" """JavaScript for menu drawer open/close behavior."""
return Script(""" return slide_over_script(
function openMenuDrawer() { panel_id="menu-drawer",
document.getElementById('menu-drawer').classList.add('open'); backdrop_id="menu-backdrop",
document.getElementById('menu-backdrop').classList.add('open'); open_fn_name="openMenuDrawer",
document.body.style.overflow = 'hidden'; close_fn_name="closeMenuDrawer",
// 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 = '';
}
""")
def _primary_nav_item(label: str, href: str, icon_fn, is_active: bool): def _primary_nav_item(label: str, href: str, icon_fn, is_active: bool):