Skip to main content

dioxus_bootstrap_css/
theme.rs

1use dioxus::prelude::*;
2
3use crate::types::Color;
4
5/// Bootstrap theme mode.
6#[derive(Clone, Copy, Debug, Default, PartialEq)]
7pub enum Theme {
8    #[default]
9    Light,
10    Dark,
11}
12
13impl Theme {
14    /// Toggle between light and dark.
15    pub fn toggle(self) -> Self {
16        match self {
17            Theme::Light => Theme::Dark,
18            Theme::Dark => Theme::Light,
19        }
20    }
21
22    /// CSS value for `data-bs-theme` attribute.
23    pub fn as_str(&self) -> &'static str {
24        match self {
25            Theme::Light => "light",
26            Theme::Dark => "dark",
27        }
28    }
29}
30
31impl std::fmt::Display for Theme {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        write!(f, "{}", self.as_str())
34    }
35}
36
37/// Applies `data-bs-theme` to the document root element.
38///
39/// Place this component in your app to enable Bootstrap dark/light mode.
40/// It sets the `data-bs-theme` attribute on `<html>` reactively.
41///
42/// ```rust
43/// let theme = use_signal(|| Theme::Dark);
44/// rsx! {
45///     ThemeProvider { theme: theme }
46///     BootstrapHead {}
47///     // your app
48/// }
49/// ```
50#[derive(Clone, PartialEq, Props)]
51pub struct ThemeProviderProps {
52    /// Signal controlling the current theme.
53    pub theme: Signal<Theme>,
54}
55
56#[component]
57pub fn ThemeProvider(props: ThemeProviderProps) -> Element {
58    let theme = *props.theme.read();
59    let theme_str = theme.as_str();
60
61    // Use a script to set data-bs-theme on the root <html> element
62    rsx! {
63        document::Script { "document.documentElement.setAttribute('data-bs-theme', '{theme_str}');" }
64    }
65}
66
67/// A toggle button that switches between light and dark mode.
68///
69/// ```rust
70/// let theme = use_signal(|| Theme::Dark);
71/// rsx! {
72///     ThemeProvider { theme: theme }
73///     ThemeToggle { theme: theme }
74/// }
75/// ```
76#[derive(Clone, PartialEq, Props)]
77pub struct ThemeToggleProps {
78    /// Signal controlling the current theme.
79    pub theme: Signal<Theme>,
80    /// Button color.
81    #[props(default)]
82    pub color: Option<Color>,
83    /// Additional CSS classes.
84    #[props(default)]
85    pub class: String,
86}
87
88#[component]
89pub fn ThemeToggle(props: ThemeToggleProps) -> Element {
90    let theme = *props.theme.read();
91    let mut theme_signal = props.theme;
92
93    let icon = match theme {
94        Theme::Light => "moon-stars",
95        Theme::Dark => "sun",
96    };
97
98    let label = match theme {
99        Theme::Light => "Switch to dark mode",
100        Theme::Dark => "Switch to light mode",
101    };
102
103    let btn_class = match &props.color {
104        Some(c) => format!("btn btn-outline-{c}"),
105        None => "btn btn-outline-secondary".to_string(),
106    };
107
108    let full_class = if props.class.is_empty() {
109        btn_class
110    } else {
111        format!("{btn_class} {}", props.class)
112    };
113
114    rsx! {
115        button {
116            class: "{full_class}",
117            r#type: "button",
118            title: "{label}",
119            "aria-label": "{label}",
120            onclick: move |_| {
121                let new_theme = theme.toggle();
122                theme_signal.set(new_theme);
123            },
124            i { class: "bi bi-{icon}" }
125        }
126    }
127}