diff --git a/TODO.md b/TODO.md
index f50fceb..b710c5e 100644
--- a/TODO.md
+++ b/TODO.md
@@ -201,6 +201,12 @@ After completing a milestone, create a pull request with your changes for review
- [x] Enforce maximum file size during upload
- [x] Add tests covering oversized file uploads
+## PR20: Theme Improvements
+
+- [x] Extend sidebar and Plotly figure styling for both themes
+- [x] Add global theme toggle stored in session state
+- [x] Update tests to verify theme CSS output
+
## Notes for Development
- Create comprehensive commit messages that clearly describe changes
diff --git a/app.py b/app.py
index 3144964..cc69bc9 100644
--- a/app.py
+++ b/app.py
@@ -8,6 +8,11 @@
st.set_page_config(page_title="PredictStream", layout="wide")
+if "theme" not in st.session_state:
+ st.session_state["theme"] = "Light"
+
+ui.apply_theme(st.session_state["theme"])
+
def main() -> None:
"""Render the home page with navigation links."""
@@ -17,6 +22,7 @@ def main() -> None:
"Use the sidebar to navigate to different sections of the application."
)
with st.sidebar:
+ st.selectbox("Theme", ["Light", "Dark"], key="theme")
st.page_link("app.py", label="Home", icon="🏠")
st.page_link("pages/data_explorer.py", label="Data Explorer", icon="📊")
st.page_link("pages/modeling.py", label="Modeling", icon="🧠")
diff --git a/pages/data_explorer.py b/pages/data_explorer.py
index 58681d9..39f573f 100644
--- a/pages/data_explorer.py
+++ b/pages/data_explorer.py
@@ -17,13 +17,7 @@ def main() -> None:
"""Render the data exploration and modeling page."""
ui.apply_branding()
st.title("Data Explorer")
- theme = st.sidebar.selectbox(
- "Theme",
- ["Light", "Dark"],
- help="Toggle interface theme",
- key="theme",
- )
- st.markdown(ui.get_theme_css(theme), unsafe_allow_html=True)
+ ui.apply_theme()
with st.expander("Getting Started"):
st.markdown(ui.getting_started_markdown())
diff --git a/pages/modeling.py b/pages/modeling.py
index f884bfe..dce4f56 100644
--- a/pages/modeling.py
+++ b/pages/modeling.py
@@ -32,6 +32,7 @@ def main() -> None:
"""Render the modeling page."""
ui.apply_branding()
st.title("Modeling")
+ ui.apply_theme()
data_keys = _available_datasets()
if not data_keys:
diff --git a/pages/prediction.py b/pages/prediction.py
index 1903c91..c5c3e4c 100644
--- a/pages/prediction.py
+++ b/pages/prediction.py
@@ -22,6 +22,7 @@ def main() -> None:
"""Render the prediction page."""
ui.apply_branding()
st.title("Prediction")
+ ui.apply_theme()
with st.sidebar:
mode = st.radio("Mode", ["Single", "Batch"], key="pred_mode")
diff --git a/pages/report.py b/pages/report.py
index a1b7fcc..79c97eb 100644
--- a/pages/report.py
+++ b/pages/report.py
@@ -22,6 +22,7 @@ def main() -> None:
"""Render the report generation page."""
ui.apply_branding()
st.title("Report Generator")
+ ui.apply_theme()
with st.sidebar:
data_utils.upload_data_to_session(
diff --git a/pages/time_series.py b/pages/time_series.py
index fe5518d..30838ab 100644
--- a/pages/time_series.py
+++ b/pages/time_series.py
@@ -18,6 +18,7 @@ def main() -> None:
"""Render the time series analysis page."""
ui.apply_branding()
st.title("Time Series Analysis")
+ ui.apply_theme()
df = st.session_state.get("data")
if df is None or df.empty:
diff --git a/tests/test_ui.py b/tests/test_ui.py
index d994b82..e1f5ba7 100644
--- a/tests/test_ui.py
+++ b/tests/test_ui.py
@@ -4,8 +4,11 @@
def test_get_theme_css():
- css = ui.get_theme_css("Dark")
- assert "background-color" in css
+ light = ui.get_theme_css("Light")
+ dark = ui.get_theme_css("Dark")
+ for css in (light, dark):
+ assert "stSidebar" in css
+ assert "plotly-chart" in css
def test_getting_started_markdown():
diff --git a/utils/ui.py b/utils/ui.py
index 7a6bca7..00f09b9 100644
--- a/utils/ui.py
+++ b/utils/ui.py
@@ -8,8 +8,19 @@
import streamlit as st
THEME_CSS: Dict[str, str] = {
- "Light": "",
- "Dark": "body { background-color: #0e1117; color: #f0f0f0; }",
+ "Light": """
+
+""",
+ "Dark": """
+
+""",
}
# Branding colors
@@ -31,6 +42,13 @@ def get_theme_css(theme: str) -> str:
return THEME_CSS.get(theme, "")
+def apply_theme(theme: str | None = None) -> None:
+ """Apply the selected theme's CSS to the app."""
+ if theme is None:
+ theme = st.session_state.get("theme", "Light")
+ st.markdown(get_theme_css(theme), unsafe_allow_html=True)
+
+
def getting_started_markdown() -> str:
"""Return markdown text for the getting started guide."""
return (