Source code for pysepal.solara.theme
"""Session-scoped theme state and Solara hooks."""
from __future__ import annotations
from typing import Optional
import solara
from traitlets import Bool, Enum, HasTraits
from pysepal.frontend.styles import get_theme
[docs]
class ThemeState(HasTraits):
"""Session-scoped theme preference and resolved dark/light state."""
mode = Enum(values=["dark", "light", "auto"], default_value="auto")
dark = Bool(False)
[docs]
def __init__(self, mode: str = "auto", dark: Optional[bool] = None, **kwargs):
"""Initialize with an initial mode and optional explicit dark value."""
super().__init__(**kwargs)
self.set_mode(mode)
if dark is not None:
self.set_dark(dark)
[docs]
def set_mode(self, mode: str) -> None:
"""Update theme preference and keep fixed modes aligned with `dark`."""
self.mode = mode
if mode == "dark":
self.dark = True
elif mode == "light":
self.dark = False
[docs]
def set_dark(self, dark: bool) -> None:
"""Update the effective dark/light value."""
self.dark = bool(dark)
_fallback_theme_state: Optional[ThemeState] = None
def _get_fallback_theme_state() -> ThemeState:
"""Get or create a process-local fallback theme state."""
global _fallback_theme_state
if _fallback_theme_state is None:
fallback_mode = get_theme()
_fallback_theme_state = ThemeState(mode=fallback_mode, dark=fallback_mode == "dark")
return _fallback_theme_state
[docs]
def get_current_theme_state() -> ThemeState:
"""Return the theme state for the current Solara kernel session."""
from .session_manager import SessionManager
if SessionManager.is_initialized():
session_manager = SessionManager()
theme_state = session_manager.get_session_component("theme_state")
if theme_state is not None:
return theme_state
raise RuntimeError(
"Session manager is active but no theme state exists for the current kernel. "
"Ensure your Page component is decorated with @with_sepal_sessions."
)
return _get_fallback_theme_state()
[docs]
def use_theme_dark(theme_state: Optional[ThemeState] = None) -> bool:
"""Reactively return the effective dark/light state for the current session."""
theme_state = theme_state or get_current_theme_state()
dark, set_dark = solara.use_state(bool(theme_state.dark))
def _observe():
def handler(change):
set_dark(bool(change["new"]))
theme_state.observe(handler, "dark")
return lambda: theme_state.unobserve(handler, "dark")
solara.use_effect(_observe, [id(theme_state)])
return dark