Skip to main content

tablo_ui/components/composites/
theme.rs

1use std::fmt::Write as _;
2
3use topcoat::{
4    Result,
5    view::{View, component, view},
6};
7
8/// Reconciles the `dark` class on `<html>` before first paint.
9///
10/// A stored `light` removes a server-rendered `dark` class.
11///
12/// Uses nested `if`s instead of `&&`, which HTML escaping would rewrite into a syntax error inside
13/// `<script>`.
14fn theme_init_script_body(default_dark: bool) -> String {
15    let mut script = String::from(
16        "(function(){var t=null;try{t=localStorage.getItem('theme')}catch(e){}\
17if(!t){var m=document.cookie.match(/theme=([^;]+)/);if(m)t=m[1]}\
18if(t!=='light'){if(t!=='dark')t=",
19    );
20    let _ = write!(script, "'{}'", if default_dark { "dark" } else { "light" });
21    script.push_str(
22        ";}if(t==='dark')document.documentElement.classList.add('dark');\
23else document.documentElement.classList.remove('dark')})();",
24    );
25    script
26}
27
28/// Renders in the `<head>` of every layout, before the stylesheet link.
29///
30/// `default_dark` is the server's build-time preference, used only when the visitor has no stored
31/// choice.
32///
33/// ```text
34/// <head>
35///     theme_init_script(default_dark)
36///     <link rel="stylesheet" href=(tailwind::stylesheet!())>
37/// </head>
38/// ```
39#[component]
40pub async fn theme_init_script(default_dark: bool) -> Result<impl View> {
41    let body = theme_init_script_body(default_dark);
42    Ok(view! { <script>(body)</script> })
43}
44
45#[cfg(test)]
46mod tests;