hypersig_ui/theme.rs
1//! The page-wide rules and the dark theme.
2
3use hypersig::style::value::{FontFamily, LineHeight, raw};
4use hypersig::style::{Element, Global, Media, Selector, Style, Theme};
5
6use crate::token;
7
8/// The rules every page with these components needs: a reset, the font,
9/// and the page colors.
10///
11/// `color-scheme` follows the theme, so a scrollbar or a native picker is
12/// drawn dark beside the dark tokens.
13///
14/// ```
15/// use hypersig::{Page, Signals};
16/// use serde::{Deserialize, Serialize};
17///
18/// #[derive(Signals, Serialize, Deserialize)]
19/// struct App {}
20///
21/// let html = Page::new("App", App {})
22/// .global(hypersig_ui::global())
23/// .theme(hypersig_ui::theme())
24/// .render();
25/// assert!(html.contains("--background:#09090b"));
26/// ```
27#[must_use]
28pub fn global() -> Global {
29 Global::preflight()
30 .rule(Selector::Root, Style::new().color_scheme(raw("light dark")))
31 .rule(
32 Element::Body,
33 Style::new()
34 .font_family(raw(
35 "ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"",
36 ))
37 .line_height(LineHeight::Number(1.5))
38 .background_color(token::background().var())
39 .color(token::foreground().var()),
40 )
41 .rule(Element::Code, Style::new().font_family(FontFamily::Monospace))
42}
43
44/// The dark values of the tokens for a visitor whose system prefers dark.
45///
46/// Without it, every visitor sees the light values, which are the tokens'
47/// defaults.
48#[must_use]
49pub fn theme() -> Theme {
50 let dark = token::dark()
51 .into_iter()
52 .fold(Theme::new(), |theme, (token, value)| {
53 theme.set(&token, value)
54 });
55 Theme::new().on(Media::Dark, dark)
56}
57
58/// The dark values of the tokens for one element and those inside it.
59///
60/// Use it with [`El::css_when`](hypersig::html::El::css_when) to let a
61/// signal pick the theme rather than the system setting.
62///
63/// ```
64/// use hypersig::html::div;
65/// use hypersig::Signals;
66/// use serde::{Deserialize, Serialize};
67///
68/// #[derive(Signals, Serialize, Deserialize)]
69/// struct App {
70/// dark: bool,
71/// }
72///
73/// let app = div::<App>().css_when(hypersig_ui::dark(), App::dark());
74/// # let _ = app;
75/// ```
76#[must_use]
77pub fn dark() -> Style {
78 token::dark().into_iter().fold(
79 Style::new().color_scheme(raw("dark")),
80 |style, (token, value)| style.token(&token, value),
81 )
82}
83
84/// The light values of the tokens for one element and those inside it.
85///
86/// Use it where a signal picks the theme and the system prefers dark, since
87/// [`theme`] sets the dark values on the whole page.
88#[must_use]
89pub fn light() -> Style {
90 token::light().into_iter().fold(
91 Style::new().color_scheme(raw("light")),
92 |style, (token, value)| style.token(&token, value),
93 )
94}