diff options
| author | Ben Sima <ben@bsima.me> | 2025-11-12 15:26:43 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bsima.me> | 2025-11-12 15:26:43 -0500 |
| commit | c846966535ef2531eab581a8c4e77f6bebdd1b5b (patch) | |
| tree | f5aec7f3ed47ff8e39e6108f53714575fb5ab9c2 /Biz | |
| parent | 8c05cb04366b9b5d3c6867e432dd4a18714796cd (diff) | |
Refactor UI components and add dark mode to admin pages
- Create shared UI module (Biz/PodcastItLater/UI.py) with:
- create_bootstrap_styles() - create_auto_dark_mode_style() -
create_htmx_script() - create_bootstrap_js()
- Update Admin.py to use shared UI module and add dark mode support -
Update Web.py to use shared UI module - Admin Queue and User Management
pages now support automatic dark mode
Diffstat (limited to 'Biz')
| -rw-r--r-- | Biz/PodcastItLater/Admin.py | 39 | ||||
| -rw-r--r-- | Biz/PodcastItLater/UI.py | 69 | ||||
| -rw-r--r-- | Biz/PodcastItLater/Web.py | 37 |
3 files changed, 89 insertions, 56 deletions
diff --git a/Biz/PodcastItLater/Admin.py b/Biz/PodcastItLater/Admin.py index 7dd0c50..4920b39 100644 --- a/Biz/PodcastItLater/Admin.py +++ b/Biz/PodcastItLater/Admin.py @@ -12,6 +12,7 @@ Admin pages and functionality for managing users and queue items. # : dep pytest-asyncio # : dep pytest-mock import Biz.PodcastItLater.Core as Core +import Biz.PodcastItLater.UI as UI import ludic.catalog.layouts as layouts import ludic.html as html @@ -290,16 +291,6 @@ def create_table_header(columns: list[str]) -> html.thead: ) -def create_bootstrap_styles() -> html.style: - """Load Bootstrap CSS for admin pages.""" - return html.style( - "@import url('https://cdn.jsdelivr.net/npm/bootstrap@5.3.2" - "/dist/css/bootstrap.min.css');" - "@import url('https://cdn.jsdelivr.net/npm/bootstrap-icons" - "@1.11.3/font/bootstrap-icons.min.css');", - ) - - class AdminUsers(Component[AnyChildren, AdminUsersAttrs]): """Admin view for managing users.""" @@ -314,15 +305,17 @@ class AdminUsers(Component[AnyChildren, AdminUsersAttrs]): name="viewport", content="width=device-width, initial-scale=1", ), - html.title("PodcastItLater - User Management"), - html.script( - src="https://unpkg.com/htmx.org@1.9.10", - integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC", - crossorigin="anonymous", + html.meta( + name="color-scheme", + content="light dark", ), + html.title("PodcastItLater - User Management"), + UI.create_htmx_script(), ), html.body( - create_bootstrap_styles(), + UI.create_bootstrap_styles(), + # Auto dark mode CSS (must come after Bootstrap) + UI.create_auto_dark_mode_style(), html.div( html.h1( "PodcastItLater - User Management", @@ -390,15 +383,17 @@ class AdminView(Component[AnyChildren, AdminViewAttrs]): name="viewport", content="width=device-width, initial-scale=1", ), - html.title("PodcastItLater - Admin Queue Status"), - html.script( - src="https://unpkg.com/htmx.org@1.9.10", - integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC", - crossorigin="anonymous", + html.meta( + name="color-scheme", + content="light dark", ), + html.title("PodcastItLater - Admin Queue Status"), + UI.create_htmx_script(), ), html.body( - create_bootstrap_styles(), + UI.create_bootstrap_styles(), + # Auto dark mode CSS (must come after Bootstrap) + UI.create_auto_dark_mode_style(), html.div( AdminView._render_content( queue_items, diff --git a/Biz/PodcastItLater/UI.py b/Biz/PodcastItLater/UI.py new file mode 100644 index 0000000..b7ec76c --- /dev/null +++ b/Biz/PodcastItLater/UI.py @@ -0,0 +1,69 @@ +""" +PodcastItLater Shared UI Components. + +Common UI components and utilities shared across web pages. +""" + +# : out podcastitlater-ui +# : dep ludic +import ludic.html as html + + +def create_bootstrap_styles() -> html.style: + """Load Bootstrap CSS and icons.""" + return html.style( + "@import url('https://cdn.jsdelivr.net/npm/bootstrap@5.3.2" + "/dist/css/bootstrap.min.css');" + "@import url('https://cdn.jsdelivr.net/npm/bootstrap-icons" + "@1.11.3/font/bootstrap-icons.min.css');", + ) + + +def create_auto_dark_mode_style() -> html.style: + """Create CSS for automatic dark mode based on prefers-color-scheme.""" + return html.style( + """ + /* Auto dark mode - applies Bootstrap dark theme via media query */ + @media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + --bs-body-color: #dee2e6; + --bs-body-color-rgb: 222, 226, 230; + --bs-body-bg: #212529; + --bs-body-bg-rgb: 33, 37, 41; + --bs-emphasis-color: #fff; + --bs-emphasis-color-rgb: 255, 255, 255; + --bs-secondary-color: rgba(222, 226, 230, 0.75); + --bs-secondary-bg: #343a40; + --bs-tertiary-color: rgba(222, 226, 230, 0.5); + --bs-tertiary-bg: #2b3035; + --bs-heading-color: inherit; + --bs-link-color: #6ea8fe; + --bs-link-hover-color: #8bb9fe; + --bs-link-color-rgb: 110, 168, 254; + --bs-link-hover-color-rgb: 139, 185, 254; + --bs-code-color: #e685b5; + --bs-border-color: #495057; + --bs-border-color-translucent: rgba(255, 255, 255, 0.15); + } + } + """, + ) + + +def create_htmx_script() -> html.script: + """Load HTMX library.""" + return html.script( + src="https://unpkg.com/htmx.org@1.9.10", + integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC", + crossorigin="anonymous", + ) + + +def create_bootstrap_js() -> html.script: + """Load Bootstrap JavaScript bundle.""" + return html.script( + src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js", + integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL", + crossorigin="anonymous", + ) diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py index c4df33d..c1abf02 100644 --- a/Biz/PodcastItLater/Web.py +++ b/Biz/PodcastItLater/Web.py @@ -20,6 +20,7 @@ import Biz.EmailAgent import Biz.PodcastItLater.Admin as Admin import Biz.PodcastItLater.Billing as Billing import Biz.PodcastItLater.Core as Core +import Biz.PodcastItLater.UI as UI import html as html_module import httpx import ludic.html as html @@ -53,38 +54,6 @@ from typing import override logger = Log.setup() -def create_auto_dark_mode_style() -> html.style: - """Create CSS for automatic dark mode based on prefers-color-scheme.""" - return html.style( - """ - /* Auto dark mode - applies Bootstrap dark theme via media query */ - @media (prefers-color-scheme: dark) { - :root { - color-scheme: dark; - --bs-body-color: #dee2e6; - --bs-body-color-rgb: 222, 226, 230; - --bs-body-bg: #212529; - --bs-body-bg-rgb: 33, 37, 41; - --bs-emphasis-color: #fff; - --bs-emphasis-color-rgb: 255, 255, 255; - --bs-secondary-color: rgba(222, 226, 230, 0.75); - --bs-secondary-bg: #343a40; - --bs-tertiary-color: rgba(222, 226, 230, 0.5); - --bs-tertiary-bg: #2b3035; - --bs-heading-color: inherit; - --bs-link-color: #6ea8fe; - --bs-link-hover-color: #8bb9fe; - --bs-link-color-rgb: 110, 168, 254; - --bs-link-hover-color-rgb: 139, 185, 254; - --bs-code-color: #e685b5; - --bs-border-color: #495057; - --bs-border-color-translucent: rgba(255, 255, 255, 0.15); - } - } - """, - ) - - # Configuration area = App.from_env() BASE_URL = os.getenv("BASE_URL", "http://localhost:8000") @@ -697,7 +666,7 @@ class BillingPage(Component[AnyChildren, BillingPageAttrs]): "@1.11.3/font/bootstrap-icons.min.css');", ), # Auto dark mode CSS (must come after Bootstrap) - create_auto_dark_mode_style(), + UI.create_auto_dark_mode_style(), html.div( html.div( html.h1( @@ -1019,7 +988,7 @@ class HomePage(Component[AnyChildren, HomePageAttrs]): "@1.11.3/font/bootstrap-icons.min.css');", ), # Auto dark mode CSS (must come after Bootstrap) - create_auto_dark_mode_style(), + UI.create_auto_dark_mode_style(), # Bootstrap container html.div( # Header |
