animato_dioxus/
transition.rs1use crate::PresenceAnimation;
4use dioxus::prelude::*;
5use std::fmt;
6
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
9pub enum TransitionMode {
10 #[default]
12 Sequential,
13 Parallel,
15 CrossFade,
17 SlideOver,
19 MorphHero,
21}
22
23impl fmt::Display for TransitionMode {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 write!(f, "{self:?}")
26 }
27}
28
29#[component]
31pub fn PageTransition(
32 mode: Option<TransitionMode>,
34 route_key: Option<String>,
37 enter: Option<PresenceAnimation>,
39 exit: Option<PresenceAnimation>,
41 children: Element,
43) -> Element {
44 let mode = mode.unwrap_or_default();
45 let enter = enter.unwrap_or_else(|| match mode {
46 TransitionMode::SlideOver => PresenceAnimation::slide_right(),
47 TransitionMode::MorphHero => PresenceAnimation::zoom_in(),
48 _ => PresenceAnimation::fade(),
49 });
50 let _exit = exit.unwrap_or_else(|| enter.reversed());
51 let base_style = container_css(mode);
52 let style = format!(
53 "{base_style}{}{}",
54 enter.to.to_css(),
55 crate::presence::transition_css(enter.duration)
56 );
57 let route_key = route_key.unwrap_or_default();
58
59 rsx! {
60 div {
61 key: "{route_key}",
62 style: "{style}",
63 {children}
64 }
65 }
66}
67
68#[cfg(feature = "router")]
70pub fn route_transition_key<R>() -> String
71where
72 R: dioxus_router::Routable + Clone + ToString + 'static,
73{
74 dioxus_router::hooks::use_route::<R>().to_string()
75}
76
77#[cfg(not(feature = "router"))]
79pub fn route_transition_key() -> String {
80 String::new()
81}
82
83pub(crate) fn container_css(mode: TransitionMode) -> &'static str {
84 match mode {
85 TransitionMode::SlideOver => "display:block; position:relative; overflow:hidden;",
86 _ => "display:block; position:relative;",
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[allow(non_snake_case)]
95 fn PageTransitionApp() -> Element {
96 rsx! {
97 PageTransition {
98 mode: Some(TransitionMode::MorphHero),
99 route_key: Some("route-a".to_owned()),
100 enter: None::<PresenceAnimation>,
101 exit: None::<PresenceAnimation>,
102 div { "page" }
103 }
104 }
105 }
106
107 #[test]
108 fn container_css_matches_transition_mode() {
109 assert!(container_css(TransitionMode::SlideOver).contains("overflow:hidden"));
110 assert_eq!(
111 container_css(TransitionMode::Sequential),
112 "display:block; position:relative;"
113 );
114 }
115
116 #[test]
117 fn display_matches_debug_label() {
118 assert_eq!(TransitionMode::CrossFade.to_string(), "CrossFade");
119 }
120
121 #[test]
122 fn all_transition_modes_have_stable_container_css() {
123 for mode in [
124 TransitionMode::Sequential,
125 TransitionMode::Parallel,
126 TransitionMode::CrossFade,
127 TransitionMode::SlideOver,
128 TransitionMode::MorphHero,
129 ] {
130 let css = container_css(mode);
131 assert!(css.contains("display:block"));
132 assert!(css.contains("position:relative"));
133 }
134 }
135
136 #[test]
137 fn page_transition_component_renders_with_default_mode_animation() {
138 let mut dom = VirtualDom::new(PageTransitionApp);
139 let mutations = dom.rebuild_to_vec();
140 assert!(!mutations.edits.is_empty());
141 }
142}