From 323f12d9119730890bc52400513199e25bb3e037 Mon Sep 17 00:00:00 2001 From: Jim Lancaster <40281233+zoltan57@users.noreply.github.com> Date: Mon, 3 Aug 2026 16:27:02 -0500 Subject: [PATCH] UI style refresh: Final cleanup --- src/transcription/ui/components/__init__.py | 6 +++ src/transcription/ui/components/primitives.py | 19 +++++++++ .../ui/components/table/documents.py | 3 +- src/transcription/ui/components/table/jobs.py | 3 +- .../ui/components/table/people.py | 3 +- .../ui/components/table/sources.py | 3 +- src/transcription/ui/pages/documents_page.py | 11 +++-- src/transcription/ui/pages/jobs_page.py | 13 ++++-- src/transcription/ui/pages/people_page.py | 11 +++-- src/transcription/ui/static/theme.css | 20 +++++++++ src/transcription/ui/theme.py | 42 +++++++++---------- 11 files changed, 96 insertions(+), 38 deletions(-) diff --git a/src/transcription/ui/components/__init__.py b/src/transcription/ui/components/__init__.py index 398969e..20b4e62 100644 --- a/src/transcription/ui/components/__init__.py +++ b/src/transcription/ui/components/__init__.py @@ -4,10 +4,16 @@ from transcription.ui.components.app_shell import NAV_ITEMS from transcription.ui.components.app_shell import render_app_shell from transcription.ui.components.app_shell import render_navigation_header from transcription.ui.components.document_panzoom import render_document_panzoom +from transcription.ui.components.primitives import destructive_button +from transcription.ui.components.primitives import render_empty_state +from transcription.ui.components.primitives import section_header_row __all__ = [ "NAV_ITEMS", + "destructive_button", "render_app_shell", "render_document_panzoom", + "render_empty_state", "render_navigation_header", + "section_header_row", ] diff --git a/src/transcription/ui/components/primitives.py b/src/transcription/ui/components/primitives.py index ec58bf1..55dff93 100644 --- a/src/transcription/ui/components/primitives.py +++ b/src/transcription/ui/components/primitives.py @@ -17,3 +17,22 @@ def render_empty_state(message: str, *, italic: bool = False, extra_classes: str if italic: classes = f"{classes} italic" ui.label(message).classes(f"{classes} {extra_classes}".strip()) + + +def destructive_button( + label: str, + *, + on_click, + icon: str, + variant: str = "outlined", + extra_classes: str = "", +): + """Render a standardized destructive action button.""" + button = ui.button(label, on_click=on_click, icon=icon) + if variant == "solid": + button.props("unelevated color=negative") + else: + button.props("outlined color=negative") + if extra_classes: + button.classes(extra_classes) + return button diff --git a/src/transcription/ui/components/table/documents.py b/src/transcription/ui/components/table/documents.py index f492ecc..43a7066 100644 --- a/src/transcription/ui/components/table/documents.py +++ b/src/transcription/ui/components/table/documents.py @@ -10,6 +10,7 @@ from uuid import UUID from nicegui import ui from transcription.ui.components.cards import archival_card +from transcription.ui.components.primitives import render_empty_state from transcription.ui.components.table.common import build_table @@ -41,7 +42,7 @@ def render_documents_table(rows: Sequence[DocumentTableRow]) -> None: """Render documents table and open detail page when clicking a row.""" if not rows: with archival_card(extra_classes="p-8 text-center"): - ui.label("No documents in repository yet.").classes("text-xs ui-text-muted") + render_empty_state("No documents in repository yet.") return build_table( diff --git a/src/transcription/ui/components/table/jobs.py b/src/transcription/ui/components/table/jobs.py index d5d0ff2..31102a2 100644 --- a/src/transcription/ui/components/table/jobs.py +++ b/src/transcription/ui/components/table/jobs.py @@ -12,6 +12,7 @@ from uuid import UUID from nicegui import ui from transcription.ui.components.cards import archival_card +from transcription.ui.components.primitives import render_empty_state from .common import build_table @@ -57,7 +58,7 @@ def render_jobs_table(rows: Sequence[JobTableRow]) -> None: """Render jobs table and open a detail page when clicking a row.""" if not rows: with archival_card(extra_classes="p-8 text-center"): - ui.label("No active or historical processing jobs found.").classes("text-xs ui-text-muted") + render_empty_state("No active or historical processing jobs found.") return build_table( diff --git a/src/transcription/ui/components/table/people.py b/src/transcription/ui/components/table/people.py index c2b094b..269fcd9 100644 --- a/src/transcription/ui/components/table/people.py +++ b/src/transcription/ui/components/table/people.py @@ -10,6 +10,7 @@ from uuid import UUID from nicegui import ui from transcription.ui.components.cards import archival_card +from transcription.ui.components.primitives import render_empty_state from transcription.ui.components.table.common import build_table @@ -41,7 +42,7 @@ def render_people_table(rows: Sequence[PersonTableRow]) -> None: """Render people table and open detail page when clicking a row.""" if not rows: with archival_card(extra_classes="p-8 text-center"): - ui.label("No person records found in repository.").classes("text-xs ui-text-muted") + render_empty_state("No person records found in repository.") return build_table( diff --git a/src/transcription/ui/components/table/sources.py b/src/transcription/ui/components/table/sources.py index 399f68a..7d9751f 100644 --- a/src/transcription/ui/components/table/sources.py +++ b/src/transcription/ui/components/table/sources.py @@ -10,6 +10,7 @@ from uuid import UUID from nicegui import ui from transcription.ui.components.cards import archival_card +from transcription.ui.components.primitives import render_empty_state from transcription.ui.components.table.common import build_table @@ -41,7 +42,7 @@ def render_sources_table(rows: Sequence[SourceTableRow]) -> None: """Render sources table and open detail page when clicking a row.""" if not rows: with archival_card(extra_classes="p-8 text-center"): - ui.label("No source file records found.").classes("text-xs ui-text-muted") + render_empty_state("No source file records found.") return build_table( diff --git a/src/transcription/ui/pages/documents_page.py b/src/transcription/ui/pages/documents_page.py index 450a80c..134649e 100644 --- a/src/transcription/ui/pages/documents_page.py +++ b/src/transcription/ui/pages/documents_page.py @@ -21,6 +21,7 @@ from transcription.ui.components.cards import archival_card from transcription.ui.components.data_display import archival_badge from transcription.ui.components.data_display import metadata_row from transcription.ui.components.error_presenter import show_error +from transcription.ui.components.primitives import destructive_button from transcription.ui.components.primitives import render_empty_state from transcription.ui.components.primitives import section_header_row from transcription.ui.components.table.documents import DocumentTableRow @@ -222,11 +223,12 @@ def register_page() -> None: on_click=lambda: ui.navigate.to(f"/documents/{document.id}/edit"), icon="edit", ).classes("ui-btn-primary text-xs") - ui.button( + destructive_button( "Delete", on_click=lambda: ui.navigate.to(f"/documents/{document.id}/delete"), icon="delete", - ).props("outlined color=negative text-xs") + extra_classes="text-xs", + ) # High-Density Bento Grid Layout with ui.grid().classes("w-full grid-cols-12 gap-4"): @@ -587,11 +589,12 @@ def register_page() -> None: ui.navigate.to("/documents") with ui.row().classes("w-full items-center gap-2 mt-2"): - ui.button( + destructive_button( "Delete document permanently", on_click=submit_delete, icon="delete_forever", - ).props("unelevated color=negative") + variant="solid", + ) ui.button("Cancel", on_click=lambda: ui.navigate.to(f"/documents/{document.id}"), icon="arrow_back").props( "flat" ) \ No newline at end of file diff --git a/src/transcription/ui/pages/jobs_page.py b/src/transcription/ui/pages/jobs_page.py index 5bf74a5..242f248 100644 --- a/src/transcription/ui/pages/jobs_page.py +++ b/src/transcription/ui/pages/jobs_page.py @@ -19,6 +19,7 @@ from transcription.ui.components.cards import archival_card from transcription.ui.components.data_display import archival_badge from transcription.ui.components.data_display import metadata_row from transcription.ui.components.error_presenter import show_error +from transcription.ui.components.primitives import destructive_button from transcription.ui.components.primitives import render_empty_state from transcription.ui.components.primitives import section_header_row from transcription.ui.components.table.jobs import render_jobs_table @@ -224,11 +225,12 @@ def register_page() -> None: # noqa: PLR0915 page_header(f"Job Record: {job.id}") with ui.row().classes("items-center gap-2"): archival_badge(job.status.value.upper()) - ui.button( + destructive_button( "Delete Job", on_click=lambda: ui.navigate.to(f"/jobs/{job.id}/delete"), icon="delete", - ).props("outlined color=negative text-xs") + extra_classes="text-xs", + ) with ui.grid().classes("w-full grid-cols-1 md:grid-cols-2 gap-4"): with archival_card(title="Execution Logistics"): @@ -314,7 +316,10 @@ def register_page() -> None: # noqa: PLR0915 ui.navigate.to("/jobs") with ui.row().classes("w-full items-center gap-2 mt-2"): - ui.button("Delete job permanently", on_click=submit_delete, icon="delete_forever").props( - "unelevated color=negative" + destructive_button( + "Delete job permanently", + on_click=submit_delete, + icon="delete_forever", + variant="solid", ) ui.button("Cancel", on_click=lambda: ui.navigate.to(f"/jobs/{job.id}"), icon="arrow_back").props("flat") \ No newline at end of file diff --git a/src/transcription/ui/pages/people_page.py b/src/transcription/ui/pages/people_page.py index 1c4a6fd..c193f3e 100644 --- a/src/transcription/ui/pages/people_page.py +++ b/src/transcription/ui/pages/people_page.py @@ -22,6 +22,7 @@ from transcription.ui.components.app_shell import render_navigation_header from transcription.ui.components.cards import archival_card from transcription.ui.components.data_display import metadata_row from transcription.ui.components.error_presenter import show_error +from transcription.ui.components.primitives import destructive_button from transcription.ui.components.primitives import render_empty_state from transcription.ui.components.primitives import section_header_row from transcription.ui.components.table.people import PersonTableRow, render_people_table @@ -241,11 +242,12 @@ def register_page() -> None: # noqa: PLR0915 on_click=lambda: ui.navigate.to(f"/people/{person.id}/edit"), icon="edit", ).classes("ui-btn-primary text-xs") - ui.button( + destructive_button( "Delete", on_click=lambda: ui.navigate.to(f"/people/{person.id}/delete"), icon="delete", - ).props("outlined color=negative text-xs") + extra_classes="text-xs", + ) with ui.grid().classes("w-full grid-cols-12 gap-4"): with ui.column().classes("col-span-12 lg:col-span-4"): @@ -467,9 +469,10 @@ def register_page() -> None: # noqa: PLR0915 ui.navigate.to("/people") with ui.row().classes("w-full items-center gap-2 mt-2"): - ui.button( + destructive_button( "Delete person permanently", on_click=submit_delete, icon="delete_forever", - ).props("unelevated color=negative") + variant="solid", + ) ui.button("Cancel", on_click=lambda: ui.navigate.to(f"/people/{person.id}"), icon="arrow_back").props("flat") \ No newline at end of file diff --git a/src/transcription/ui/static/theme.css b/src/transcription/ui/static/theme.css index f3f483d..a9a7859 100644 --- a/src/transcription/ui/static/theme.css +++ b/src/transcription/ui/static/theme.css @@ -209,6 +209,26 @@ input:focus-visible, color: var(--theme-secondary); } +.ui-page-header { + display: flex; + flex-direction: column; + gap: 0; + width: 100%; +} + +.ui-page-title { + color: var(--theme-text); + font-family: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Palatino, serif; + font-size: 1.75rem; + font-weight: 700; + line-height: 1.2; +} + +.ui-page-subtitle { + color: var(--theme-text-muted); + font-size: 0.75rem; +} + .ui-table { border: 1px solid var(--theme-border); color: var(--theme-text); diff --git a/src/transcription/ui/theme.py b/src/transcription/ui/theme.py index c91093c..ffca4cd 100644 --- a/src/transcription/ui/theme.py +++ b/src/transcription/ui/theme.py @@ -1,35 +1,33 @@ from nicegui import ui -# Exact Palette & Typography Constants from UI Design Specification +# Runtime bridge for Quasar color slots. Visual ownership remains in theme.css tokens/classes. THEME_COLORS = { - "primary": "#2D5A4C", # Library Green - "secondary": "#E2C7A8", # Aged Sepia - "positive": "#2D5A4C", - "accent": "#E2C7A8", - "dark": "#2B2D2C", # Matte Slate - "negative": "#A83232", + "primary": "#5e6572", + "secondary": "#7d98a1", + "accent": "#a9b4c2", + "dark": "#1c2321", + "positive": "#7d98a1", + "negative": "#1c2321", + "info": "#7d98a1", + "warning": "#a9b4c2", } -# Typography Macros -STYLE_SERIF_HEADER = "font-family: 'Georgia', 'Times New Roman', serif;" -STYLE_SANS_BODY = "font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;" +_THEME_APPLIED = False def apply_archival_theme() -> None: - """Configures global NiceGUI theme variables and injects custom global styles.""" + """Apply runtime color slots once; visual styling is defined in theme.css.""" + global _THEME_APPLIED + if _THEME_APPLIED: + return + ui.colors(**THEME_COLORS) - - # Global body rules: Archival Cream (#FAF9F6) background, Iron Ink (#333333) text - ui.query("body").style( - f"background-color: #FAF9F6; color: #333333; {STYLE_SANS_BODY}" - ) + _THEME_APPLIED = True def page_header(title: str, subtitle: str | None = None) -> None: - """Standardized page title component with Archival Serif styling.""" - with ui.column().classes("gap-0 pb-2 border-b border-[#6B6A65]/30 w-full"): - ui.label(title).style( - f"{STYLE_SERIF_HEADER} font-size: 1.75rem; font-weight: 700; color: #333333;" - ) + """Standardized page title component using semantic classes.""" + with ui.column().classes("ui-page-header"): + ui.label(title).classes("ui-page-title") if subtitle: - ui.label(subtitle).classes("text-xs text-[#6B6A65]") \ No newline at end of file + ui.label(subtitle).classes("ui-page-subtitle") \ No newline at end of file