use crate::PresenceAnimation;
use dioxus::prelude::*;
use std::fmt;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum TransitionMode {
#[default]
Sequential,
Parallel,
CrossFade,
SlideOver,
MorphHero,
}
impl fmt::Display for TransitionMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
#[component]
pub fn PageTransition(
mode: Option<TransitionMode>,
route_key: Option<String>,
enter: Option<PresenceAnimation>,
exit: Option<PresenceAnimation>,
children: Element,
) -> Element {
let mode = mode.unwrap_or_default();
let enter = enter.unwrap_or_else(|| match mode {
TransitionMode::SlideOver => PresenceAnimation::slide_right(),
TransitionMode::MorphHero => PresenceAnimation::zoom_in(),
_ => PresenceAnimation::fade(),
});
let _exit = exit.unwrap_or_else(|| enter.reversed());
let base_style = container_css(mode);
let style = format!(
"{base_style}{}{}",
enter.to.to_css(),
crate::presence::transition_css(enter.duration)
);
let route_key = route_key.unwrap_or_default();
rsx! {
div {
key: "{route_key}",
style: "{style}",
{children}
}
}
}
#[cfg(feature = "router")]
pub fn route_transition_key<R>() -> String
where
R: dioxus_router::Routable + Clone + ToString + 'static,
{
dioxus_router::hooks::use_route::<R>().to_string()
}
#[cfg(not(feature = "router"))]
pub fn route_transition_key() -> String {
String::new()
}
pub(crate) fn container_css(mode: TransitionMode) -> &'static str {
match mode {
TransitionMode::SlideOver => "display:block; position:relative; overflow:hidden;",
_ => "display:block; position:relative;",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(non_snake_case)]
fn PageTransitionApp() -> Element {
rsx! {
PageTransition {
mode: Some(TransitionMode::MorphHero),
route_key: Some("route-a".to_owned()),
enter: None::<PresenceAnimation>,
exit: None::<PresenceAnimation>,
div { "page" }
}
}
}
#[test]
fn container_css_matches_transition_mode() {
assert!(container_css(TransitionMode::SlideOver).contains("overflow:hidden"));
assert_eq!(
container_css(TransitionMode::Sequential),
"display:block; position:relative;"
);
}
#[test]
fn display_matches_debug_label() {
assert_eq!(TransitionMode::CrossFade.to_string(), "CrossFade");
}
#[test]
fn all_transition_modes_have_stable_container_css() {
for mode in [
TransitionMode::Sequential,
TransitionMode::Parallel,
TransitionMode::CrossFade,
TransitionMode::SlideOver,
TransitionMode::MorphHero,
] {
let css = container_css(mode);
assert!(css.contains("display:block"));
assert!(css.contains("position:relative"));
}
}
#[test]
fn page_transition_component_renders_with_default_mode_animation() {
let mut dom = VirtualDom::new(PageTransitionApp);
let mutations = dom.rebuild_to_vec();
assert!(!mutations.edits.is_empty());
}
}