commit 0fd5440ae09738080c9b4851a7a1946dcc71e83a
parent a6f3c8dee48ab9adf52c067975593228ea80a21b
Author: triesap <triesap@radroots.dev>
Date: Wed, 21 Jan 2026 19:57:34 +0000
ui: add motion and contrast preference helpers
- add preference helpers with explicit error types
- use matchMedia for wasm preference queries
- add unit tests for preference helpers
- enable web-sys media query support
Diffstat:
3 files changed, 66 insertions(+), 0 deletions(-)
diff --git a/crates/ui-core/Cargo.toml b/crates/ui-core/Cargo.toml
@@ -19,6 +19,7 @@ web-sys = { workspace = true, features = [
"EventTarget",
"HtmlElement",
"KeyboardEvent",
+ "MediaQueryList",
"PointerEvent",
"Window"
] }
diff --git a/crates/ui-core/src/lib.rs b/crates/ui-core/src/lib.rs
@@ -6,6 +6,7 @@ extern crate alloc;
mod id;
mod event;
mod input;
+mod preference;
pub use event::{
radroots_app_ui_compose_event_handlers,
@@ -19,3 +20,9 @@ pub use input::{
RadrootsAppUiInputModality,
RadrootsAppUiInputModalityError,
};
+pub use preference::{
+ radroots_app_ui_prefers_contrast_more,
+ radroots_app_ui_prefers_reduced_motion,
+ RadrootsAppUiPreferenceError,
+ RadrootsAppUiPreferenceResult,
+};
diff --git a/crates/ui-core/src/preference.rs b/crates/ui-core/src/preference.rs
@@ -0,0 +1,58 @@
+use core::fmt;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum RadrootsAppUiPreferenceError {
+ WindowMissing,
+ MatchMediaFailed,
+}
+
+impl fmt::Display for RadrootsAppUiPreferenceError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ RadrootsAppUiPreferenceError::WindowMissing => {
+ write!(f, "preference_window_missing")
+ }
+ RadrootsAppUiPreferenceError::MatchMediaFailed => {
+ write!(f, "preference_match_media_failed")
+ }
+ }
+ }
+}
+
+pub type RadrootsAppUiPreferenceResult<T> = Result<T, RadrootsAppUiPreferenceError>;
+
+#[cfg(target_arch = "wasm32")]
+fn radroots_app_ui_prefers(query: &str) -> RadrootsAppUiPreferenceResult<bool> {
+ let window = web_sys::window().ok_or(RadrootsAppUiPreferenceError::WindowMissing)?;
+ let media = window
+ .match_media(query)
+ .map_err(|_| RadrootsAppUiPreferenceError::MatchMediaFailed)?;
+ Ok(media.map(|list| list.matches()).unwrap_or(false))
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+fn radroots_app_ui_prefers(_query: &str) -> RadrootsAppUiPreferenceResult<bool> {
+ Ok(false)
+}
+
+pub fn radroots_app_ui_prefers_reduced_motion() -> RadrootsAppUiPreferenceResult<bool> {
+ radroots_app_ui_prefers("(prefers-reduced-motion: reduce)")
+}
+
+pub fn radroots_app_ui_prefers_contrast_more() -> RadrootsAppUiPreferenceResult<bool> {
+ radroots_app_ui_prefers("(prefers-contrast: more)")
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ radroots_app_ui_prefers_contrast_more,
+ radroots_app_ui_prefers_reduced_motion,
+ };
+
+ #[test]
+ fn prefers_helpers_return_result() {
+ let _ = radroots_app_ui_prefers_reduced_motion().expect("reduced motion");
+ let _ = radroots_app_ui_prefers_contrast_more().expect("contrast more");
+ }
+}