From 3353f4546c0c3a8c1baad630e4fbed3ec1edca8c Mon Sep 17 00:00:00 2001 From: Kawa Date: Wed, 15 Apr 2026 11:08:52 +0200 Subject: [PATCH] feat(11-03): Alpine.js theme + i18n stores in base.html with top-right controls - Add Alpine.store('theme') for Light/Dark/System cycling with localStorage persistence - Add Alpine.store('i18n') with FR/EN translations for all static UI strings - Add topbar with theme toggle button (:aria-label, @click cycle) and lang button - Wrap main content in .main-wrapper + .topbar for layout structure - Add .main-wrapper, .topbar, .topbar-controls styles to app.css - Add test_theme_toggle_present integration test - Fix test_dashboard_shows_recent_printers assertion (string now in i18n JS too) --- imptune/static/app.css | 30 ++++++++ imptune/templates/base.html | 142 ++++++++++++++++++++++++++++++++++-- tests/test_static.py | 22 +++++- 3 files changed, 185 insertions(+), 9 deletions(-) diff --git a/imptune/static/app.css b/imptune/static/app.css index 442c870..5b3cebc 100644 --- a/imptune/static/app.css +++ b/imptune/static/app.css @@ -91,6 +91,36 @@ nav.sidebar li { border-left-color: var(--pico-primary, #1a73e8); } +/* Main wrapper: fills remaining space beside sidebar, stacks topbar + content */ +.main-wrapper { + flex: 1 1 auto; + min-width: 0; + display: flex; + flex-direction: column; +} + +/* Topbar: holds top-right controls */ +.topbar { + display: flex; + justify-content: flex-end; + align-items: center; + padding: 0.5rem 1rem; + border-bottom: 1px solid var(--pico-muted-border-color, #e0e0e0); + gap: 0.5rem; +} + +.topbar-controls { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.topbar-controls button { + padding: 0.3rem 0.6rem; + font-size: 0.85rem; + min-width: 2.2rem; +} + /* Main content */ .main-content { flex: 1 1 auto; diff --git a/imptune/templates/base.html b/imptune/templates/base.html index d8d56b9..08c13ea 100644 --- a/imptune/templates/base.html +++ b/imptune/templates/base.html @@ -6,6 +6,110 @@ ImpTune + @@ -16,16 +120,38 @@ ImpTune -
- {% block content %}{% endblock %} -
+
+
+
+ + + + +
+
+
+ {% block content %}{% endblock %} +
+
diff --git a/tests/test_static.py b/tests/test_static.py index d3fb753..5e22e44 100644 --- a/tests/test_static.py +++ b/tests/test_static.py @@ -43,6 +43,16 @@ def test_packages_returns_200(client): assert response.status_code == 200 + +def test_theme_toggle_present(client): + """GET / contains a theme toggle button (data-theme cycling control).""" + response = client.get("/") + assert response.status_code == 200 + # The button's @click should reference $store.theme.cycle + assert "theme" in response.text + assert "cycle" in response.text or "store.theme" in response.text + + def test_dashboard_shows_recent_printers(client): """Dashboard renders names of recently-created printers from DB.""" from imptune.db.models import Printer @@ -62,7 +72,17 @@ def test_dashboard_shows_recent_printers(client): assert response.status_code == 200 assert "TestPrinter-Alpha" in response.text assert "TestPrinter-Beta" in response.text - assert "No printers configured yet" not in response.text + # Use class-specific check: the string also appears in the i18n store JS + assert 'class="empty-state">No printers configured yet' not in response.text + + +def test_theme_toggle_present(client): + """GET / contains a theme toggle button (data-theme cycling control).""" + response = client.get("/") + assert response.status_code == 200 + # The button's @click should reference $store.theme.cycle + assert "theme" in response.text + assert "cycle" in response.text or "store.theme" in response.text def test_dashboard_shows_recent_packages(client):