diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e064c5..8cae4d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated to web_sys 0.3.70 which unfortunately is breaking some things.
- `use_clipboard` doesn't need the unstable flags anymore.
- `use_locale` now uses `unic_langid::LanguageIdentifier` and proper locale matching (thanks to @mondeja).
+- `use_preferred_dark` and `use_color_mode` now try to read the `Sec-CH-Prefers-Color-Scheme` header in SSR.
## [0.11.4] - 2024-08-12
diff --git a/Cargo.toml b/Cargo.toml
index cf8fd6e..2fd0ca0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -34,11 +34,11 @@ num = { version = "0.4", optional = true }
paste = "1"
thiserror = "1"
unic-langid = "0.9"
-wasm-bindgen = ">=0.2.93"
+wasm-bindgen = "=0.2.93"
wasm-bindgen-futures = "0.4"
[dependencies.web-sys]
-version = ">=0.3.70"
+version = "=0.3.70"
features = [
"AddEventListenerOptions",
"BinaryType",
diff --git a/examples/ssr/Cargo.toml b/examples/ssr/Cargo.toml
index 5d71626..533ce90 100644
--- a/examples/ssr/Cargo.toml
+++ b/examples/ssr/Cargo.toml
@@ -21,8 +21,9 @@ log = "0.4"
simple_logger = "4"
tokio = { version = "1", features = ["full"], optional = true }
tower = { version = "0.4", optional = true }
+tower-default-headers = { git = "https://github.com/banool/tower-default-headers-rs" }
tower-http = { version = "0.5", features = ["fs"], optional = true }
-wasm-bindgen = "0.2.92"
+wasm-bindgen = "=0.2.93"
thiserror = "1.0.38"
tracing = { version = "0.1.37", optional = true }
http = "1"
diff --git a/examples/ssr/src/main.rs b/examples/ssr/src/main.rs
index 4d92392..ff4f4bb 100644
--- a/examples/ssr/src/main.rs
+++ b/examples/ssr/src/main.rs
@@ -2,11 +2,13 @@
#[tokio::main]
async fn main() {
use axum::{routing::post, Router};
+ use http::{HeaderMap, HeaderName, HeaderValue};
use leptos::logging::log;
use leptos::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
use leptos_use_ssr::app::*;
use leptos_use_ssr::fileserv::file_and_error_handler;
+ use tower_default_headers::DefaultHeadersLayer;
simple_logger::init_with_level(log::Level::Info).expect("couldn't initialize logging");
@@ -20,12 +22,19 @@ async fn main() {
let addr = leptos_options.site_addr;
let routes = generate_route_list(|| view! { });
+ let mut default_headers = HeaderMap::new();
+ let color_header = HeaderValue::from_static("Sec-CH-Prefers-Color-Scheme");
+ default_headers.insert(HeaderName::from_static("accept-ch"), color_header.clone());
+ default_headers.insert(HeaderName::from_static("vary"), color_header.clone());
+ default_headers.insert(HeaderName::from_static("critical-ch"), color_header);
+
// build our application with a route
let app = Router::new()
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
.leptos_routes(&leptos_options, routes, || view! { })
.fallback(file_and_error_handler)
- .with_state(leptos_options);
+ .with_state(leptos_options)
+ .layer(DefaultHeadersLayer::new(default_headers));
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
log!("listening on http://{}", &addr);
diff --git a/src/use_color_mode.rs b/src/use_color_mode.rs
index 1df636b..99a688e 100644
--- a/src/use_color_mode.rs
+++ b/src/use_color_mode.rs
@@ -2,7 +2,11 @@ use crate::core::url;
use crate::core::StorageType;
use crate::core::{ElementMaybeSignal, MaybeRwSignal};
use crate::storage::{use_storage_with_options, UseStorageOptions};
-use crate::{sync_signal_with_options, use_cookie, use_preferred_dark, SyncSignalOptions};
+use crate::utils::get_header;
+use crate::{
+ sync_signal_with_options, use_cookie, use_preferred_dark_with_options,
+ SyncSignalOptions, UsePreferredDarkOptions,
+};
use codee::string::FromToStringCodec;
use default_struct_builder::DefaultBuilder;
use leptos::*;
@@ -83,7 +87,7 @@ use wasm_bindgen::JsCast;
/// # }
/// ```
///
-/// ### Cookies
+/// ### Cookie
///
/// To persist color mode in a cookie, use `use_cookie_with_options` and specify `.cookie_enabled(true)`.
///
@@ -112,9 +116,24 @@ use wasm_bindgen::JsCast;
///
/// ## Server-Side Rendering
///
-/// On the server this will by default return `ColorMode::Light`. Persistence with storage is disabled.
+/// On the server this will try to read the
+/// [`Sec-CH-Prefers-Color-Scheme` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-Prefers-Color-Scheme)
+/// to determine the color mode. If the header is not present it will return `ColorMode::Light`.
+/// Please have a look at the linked documentation above for that header to see browser support
+/// as well as potential server requirements.
///
-/// If `cookie_enabled` is set to `true`, cookies will be used and if present this value will be used
+/// > If you're using `axum` you have to enable the `"axum"` feature in your Cargo.toml.
+/// > In case it's `actix-web` enable the feature `"actix"`, for `spin` enable `"spin"`.
+///
+/// ### Bring your own header
+///
+/// In case you're neither using Axum, Actix nor Spin, or the default implementation is not to your liking,
+/// you can provide your own way of reading the color scheme header value using the option
+/// [`crate::UseColorModeOptions::ssr_color_header_getter`].
+///
+/// ### Cookie
+///
+/// If `cookie_enabled` is set to `true`, a cookie will be used and if present this value will be used
/// on the server as well as on the client. Please note that you have to add the `axum` or `actix`
/// feature as described in [`fn@crate::use_cookie`].
///
@@ -151,6 +170,7 @@ where
emit_auto,
transition_enabled,
listen_to_storage_changes,
+ ssr_color_header_getter,
_marker,
} = options;
@@ -162,7 +182,9 @@ where
])
.collect();
- let preferred_dark = use_preferred_dark();
+ let preferred_dark = use_preferred_dark_with_options(UsePreferredDarkOptions {
+ ssr_color_header_getter,
+ });
let system = Signal::derive(move || {
if preferred_dark.get() {
@@ -471,6 +493,14 @@ where
/// Defaults to true.
listen_to_storage_changes: bool,
+ /// Getter function to return the string value of the
+ /// [`Sec-CH-Prefers-Color-Scheme`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-Prefers-Color-Scheme)
+ /// header.
+ /// When you use one of the features `"axum"`, `"actix"` or `"spin"` there's a valid default
+ /// implementation provided.
+ #[allow(dead_code)]
+ ssr_color_header_getter: Rc Option>,
+
#[builder(skip)]
_marker: PhantomData,
}
@@ -496,6 +526,13 @@ impl Default for UseColorModeOptions<&'static str, web_sys::Element> {
emit_auto: false,
transition_enabled: false,
listen_to_storage_changes: true,
+ ssr_color_header_getter: Rc::new(move || {
+ get_header!(
+ HeaderName::from_static("sec-ch-prefers-color-scheme"),
+ use_locale,
+ ssr_color_header_getter
+ )
+ }),
_marker: PhantomData,
}
}
diff --git a/src/use_locales.rs b/src/use_locales.rs
index 4e4f567..8762dc5 100644
--- a/src/use_locales.rs
+++ b/src/use_locales.rs
@@ -39,7 +39,7 @@ use std::rc::Rc;
/// ### Bring your own header
///
/// In case you're neither using Axum, Actix nor Spin, or the default implementation is not to your liking,
-/// you can provide your own way of reading and writing the cookie header value using the option
+/// you can provide your own way of reading the language header value using the option
/// [`crate::UseLocalesOptions::ssr_lang_header_getter`].
pub fn use_locales() -> Signal> {
use_locales_with_options(UseLocalesOptions::default())
diff --git a/src/use_preferred_dark.rs b/src/use_preferred_dark.rs
index 18d8043..ad64f13 100644
--- a/src/use_preferred_dark.rs
+++ b/src/use_preferred_dark.rs
@@ -1,5 +1,7 @@
-use crate::use_media_query;
+use crate::utils::get_header;
+use default_struct_builder::DefaultBuilder;
use leptos::*;
+use std::rc::Rc;
/// Reactive [dark theme preference](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme).
///
@@ -20,12 +22,66 @@ use leptos::*;
///
/// ## Server-Side Rendering
///
-/// On the server this functions returns a Signal that is always `false`.
+/// On the server this will try to read the
+/// [`Sec-CH-Prefers-Color-Scheme` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-Prefers-Color-Scheme)
+/// to determine the color mode. If the header is not present it will return `ColorMode::Light`.
+/// Please have a look at the linked documentation above for that header to see browser support
+/// as well as potential server requirements.
+///
+/// > If you're using `axum` you have to enable the `"axum"` feature in your Cargo.toml.
+/// > In case it's `actix-web` enable the feature `"actix"`, for `spin` enable `"spin"`.
+///
+/// ### Bring your own header
+///
+/// In case you're neither using Axum, Actix nor Spin, or the default implementation is not to your liking,
+/// you can provide your own way of reading the color scheme header value using the option
+/// [`crate::UsePreferredDarkOptions::ssr_color_header_getter`].
///
/// ## See also
///
/// * [`fn@crate::use_media_query`]
/// * [`fn@crate::use_preferred_contrast`]
pub fn use_preferred_dark() -> Signal {
- use_media_query("(prefers-color-scheme: dark)")
+ use_preferred_dark_with_options(Default::default())
+}
+
+/// Version of [`fn@crate::use_preferred_dark`] that accepts a `UsePreferredDarkOptions`.
+pub fn use_preferred_dark_with_options(options: UsePreferredDarkOptions) -> Signal {
+ #[cfg(not(feature = "ssr"))]
+ {
+ let _ = options;
+
+ crate::use_media_query("(prefers-color-scheme: dark)")
+ }
+
+ #[cfg(feature = "ssr")]
+ {
+ Signal::derive(move || (options.ssr_color_header_getter)() == Some("dark".to_string()))
+ }
+}
+
+/// Options for [`fn@crate::use_preferred_dark_with_options`].
+#[derive(DefaultBuilder)]
+pub struct UsePreferredDarkOptions {
+ /// Getter function to return the string value of the
+ /// [`Sec-CH-Prefers-Color-Scheme`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-Prefers-Color-Scheme)
+ /// header.
+ /// When you use one of the features `"axum"`, `"actix"` or `"spin"` there's a valid default
+ /// implementation provided.
+ #[allow(dead_code)]
+ pub(crate) ssr_color_header_getter: Rc Option>,
+}
+
+impl Default for UsePreferredDarkOptions {
+ fn default() -> Self {
+ Self {
+ ssr_color_header_getter: Rc::new(move || {
+ get_header!(
+ HeaderName::from_static("sec-ch-prefers-color-scheme"),
+ use_locale,
+ ssr_color_header_getter
+ )
+ }),
+ }
+ }
}
diff --git a/src/utils/header_macro.rs b/src/utils/header_macro.rs
index 2cb0b52..59dab10 100644
--- a/src/utils/header_macro.rs
+++ b/src/utils/header_macro.rs
@@ -1,6 +1,6 @@
macro_rules! get_header {
(
- $header_name:ident,
+ $header_name:expr,
$function_name:ident,
$option_name:ident
$(,)?
@@ -19,14 +19,19 @@ macro_rules! get_header {
);
return None;
}
-
+
#[cfg(feature = "actix")]
- const $header_name: http0_2::HeaderName = http0_2::header::$header_name;
+ #[allow(unused_imports)]
+ use http0_2::{HeaderName, header::*};
#[cfg(any(feature = "axum", feature = "spin"))]
- const $header_name: http1::HeaderName = http1::header::$header_name;
+ #[allow(unused_imports)]
+ use http1::{HeaderName, header::*};
#[cfg(any(feature = "axum", feature = "actix", feature = "spin"))]
- crate::utils::header($header_name)
+ {
+ let header_name = $header_name;
+ crate::utils::header(header_name)
+ }
} else {
None
}