"""Theme palettes for the optional Textual explorer.
Purpose:
Keep visual choices out of the TUI event wiring so the app can offer
multiple usable terminal looks without becoming a wall of CSS.
Design:
Themes are intentionally simple named palettes. They avoid third-party
theme packages for now because Textual CSS is enough for the current app
and the TUI extra should stay lightweight.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Literal
[docs]
TUIThemeName = Literal["paper", "slate", "mono", "forest"]
[docs]
TUI_THEME_CHOICES: tuple[TUIThemeName, ...] = ("paper", "slate", "mono", "forest")
@dataclass(frozen=True)
[docs]
class TUITheme:
"""Named color palette used to render the Textual explorer."""
[docs]
TUI_THEMES: dict[TUIThemeName, TUITheme] = {
"paper": TUITheme(
screen_bg="#f7f1df",
screen_fg="#1d241f",
header_bg="#303b35",
header_fg="#fff7dc",
footer_bg="#e2d7bd",
footer_fg="#29322d",
card_bg="#fff9e8",
card_border="#9b8b63",
table_bg="#fffdf5",
table_border="#9c9078",
detail_bg="#fff7df",
detail_border="#8d805f",
focus="#b65c32",
active_bg="#24352f",
active_fg="#fff7dc",
summary="#9a6a28",
status="#527a57",
notes="#a9563f",
),
"slate": TUITheme(
screen_bg="#0d1117",
screen_fg="#e6edf3",
header_bg="#161b22",
header_fg="#f0f6fc",
footer_bg="#111820",
footer_fg="#c9d1d9",
card_bg="#111820",
card_border="#46515f",
table_bg="#0b1016",
table_border="#384553",
detail_bg="#121923",
detail_border="#536273",
focus="#f0b45b",
active_bg="#f0b45b",
active_fg="#0d1117",
summary="#f0b45b",
status="#7ccf8d",
notes="#76b7e5",
),
"mono": TUITheme(
screen_bg="#111111",
screen_fg="#eeeeee",
header_bg="#202020",
header_fg="#ffffff",
footer_bg="#181818",
footer_fg="#cfcfcf",
card_bg="#171717",
card_border="#707070",
table_bg="#101010",
table_border="#5c5c5c",
detail_bg="#191919",
detail_border="#7a7a7a",
focus="#ffffff",
active_bg="#eeeeee",
active_fg="#111111",
summary="#d7d7d7",
status="#bbbbbb",
notes="#999999",
),
"forest": TUITheme(
screen_bg="#10150f",
screen_fg="#edf1dc",
header_bg="#253224",
header_fg="#f5efd0",
footer_bg="#182019",
footer_fg="#cfd8b8",
card_bg="#151d14",
card_border="#5f6f45",
table_bg="#0f140f",
table_border="#455437",
detail_bg="#171c16",
detail_border="#6f7753",
focus="#f0c36a",
active_bg="#f0c36a",
active_fg="#10150f",
summary="#f0c36a",
status="#9cc27d",
notes="#d58b62",
),
}
[docs]
def tui_theme_css(theme: TUIThemeName = "paper") -> str:
"""Return Textual CSS for a named explorer theme."""
palette = TUI_THEMES[theme]
return f"""
Screen {{
background: {palette.screen_bg};
color: {palette.screen_fg};
}}
Header {{
background: {palette.header_bg};
color: {palette.header_fg};
}}
Footer {{
background: {palette.footer_bg};
color: {palette.footer_fg};
}}
#layout {{
height: 100%;
}}
#dashboard {{
height: auto;
min-height: 7;
padding: 1 1 0 1;
}}
.card {{
width: 1fr;
min-height: 6;
margin: 0 1 0 0;
padding: 1 2;
border: round {palette.card_border};
background: {palette.card_bg};
color: {palette.screen_fg};
}}
#summary-card {{
border-title-color: {palette.summary};
}}
#status-card {{
border-title-color: {palette.status};
}}
#notes-card {{
border-title-color: {palette.notes};
}}
#controls {{
height: auto;
min-height: 4;
padding: 0 1 1 1;
}}
#nav {{
height: auto;
min-height: 3;
padding: 0 1 1 1;
}}
.nav-button {{
margin-right: 1;
min-width: 13;
}}
.nav-button.active {{
background: {palette.active_bg};
color: {palette.active_fg};
text-style: bold;
}}
#search {{
width: 2fr;
margin-right: 1;
}}
#provider-filter {{
width: 28;
margin-right: 1;
}}
#view-select {{
width: 22;
margin-right: 1;
}}
#refresh-button {{
width: 15;
}}
ContentSwitcher {{
height: 1fr;
}}
.pane {{
height: 1fr;
padding: 1;
}}
.table-workspace {{
height: 1fr;
}}
DataTable {{
width: 2fr;
height: 1fr;
border: round {palette.table_border};
background: {palette.table_bg};
color: {palette.screen_fg};
}}
DataTable:focus {{
border: heavy {palette.focus};
}}
.detail {{
width: 1fr;
height: 1fr;
margin-left: 1;
padding: 1 2;
border: round {palette.detail_border};
background: {palette.detail_bg};
color: {palette.screen_fg};
}}
#home-markdown, #profile-markdown {{
height: 1fr;
padding: 1 2;
border: round {palette.card_border};
background: {palette.card_bg};
}}
"""