use tairitsu_hooks::{provide_context, use_context, use_memo, use_signal};
use tairitsu_macros::{component, define_props, rsx};
use tairitsu_vdom::{Callback, Signal, VNode as Element};
use crate::{config::AnimationConfig, prefers_reduced_motion::prefers_reduced_motion};
#[derive(Clone)]
pub struct AnimationContext {
pub config: Signal<AnimationConfig>,
pub set_config: Callback<AnimationConfig>,
}
impl AnimationContext {
pub fn is_enabled(&self) -> bool {
self.config.read().enabled
}
pub fn duration_scale(&self) -> f32 {
self.config.read().duration_scale
}
pub fn is_reduced_motion(&self) -> bool {
self.config.read().reduced_motion
}
pub fn scale_duration(&self, duration_ms: u64) -> u64 {
self.config.read().scale_duration(duration_ms)
}
pub fn should_skip(&self) -> bool {
self.config.read().should_skip()
}
}
#[define_props]
pub struct AnimationProviderProps {
#[default(true)]
pub enabled: bool,
#[default(1.0)]
pub duration_scale: f32,
#[default(true)]
pub respect_reduced_motion: bool,
pub force_reduced_motion: Option<bool>,
pub children: Element,
}
#[component]
pub fn AnimationProvider(props: AnimationProviderProps) -> Element {
let system_reduced = if props.respect_reduced_motion {
prefers_reduced_motion()
} else {
false
};
let reduced_motion = props.force_reduced_motion.unwrap_or(system_reduced);
let initial_config = AnimationConfig {
enabled: props.enabled && !reduced_motion,
duration_scale: if reduced_motion {
0.0
} else {
props.duration_scale
},
reduced_motion,
};
let config = use_signal(move || initial_config);
let set_config = Callback::new({
let config = config.clone();
move |new_config: AnimationConfig| {
config.set(new_config);
}
});
provide_context(AnimationContext {
config: config.inner().clone(),
set_config,
});
let css_vars = use_memo(move || {
let cfg = config.read();
format!(
"--hi-animation-enabled: {}; --hi-animation-duration-scale: {}; --hi-animation-reduced-motion: {};",
if cfg.enabled { 1 } else { 0 },
cfg.duration_scale,
if cfg.reduced_motion { 1 } else { 0 }
)
});
rsx! {
div {
class: "hi-animation-provider",
style: "{css_vars.read()}",
{props.children}
}
}
}
pub fn use_animation_config() -> AnimationContext {
use_context::<AnimationContext>()
.expect("AnimationContext not found — wrap your tree in an AnimationProvider")
.get()
.clone()
}
pub fn try_use_animation_config() -> Option<AnimationContext> {
use_context::<AnimationContext>().map(|ctx| ctx.get().clone())
}
pub fn use_animation_enabled() -> bool {
use_animation_config().is_enabled()
}
pub fn use_animation_duration_scale() -> f32 {
use_animation_config().duration_scale()
}
pub fn use_animation_reduced_motion() -> bool {
use_animation_config().is_reduced_motion()
}