leptos/animated_show.rs
1use crate::{children::ChildrenFn, component, control_flow::Show, IntoView};
2use core::time::Duration;
3use leptos_dom::helpers::TimeoutHandle;
4use leptos_macro::view;
5use reactive_graph::{
6 effect::RenderEffect,
7 owner::{on_cleanup, StoredValue},
8 signal::RwSignal,
9 traits::{Get, GetUntracked, GetValue, Set, SetValue},
10 wrappers::read::Signal,
11};
12use tachys::prelude::*;
13
14/// A component that will show its children when the `when` condition is `true`.
15/// Additionally, you need to specify a `hide_delay`. If the `when` condition changes to `false`,
16/// the unmounting of the children will be delayed by the specified Duration.
17/// If you provide the optional `show_class` and `hide_class`, you can create very easy mount /
18/// unmount animations.
19///
20/// ```rust
21/// # use core::time::Duration;
22/// # use leptos::prelude::*;
23/// # #[component]
24/// # pub fn App() -> impl IntoView {
25/// let show = RwSignal::new(false);
26///
27/// view! {
28/// <div
29/// class="hover-me"
30/// on:mouseenter=move |_| show.set(true)
31/// on:mouseleave=move |_| show.set(false)
32/// >
33/// "Hover Me"
34/// </div>
35///
36/// <AnimatedShow
37/// when=show
38/// show_class="fade-in-1000"
39/// hide_class="fade-out-1000"
40/// hide_delay=Duration::from_millis(1000)
41/// >
42/// <div class="here-i-am">
43/// "Here I Am!"
44/// </div>
45/// </AnimatedShow>
46/// }
47/// # }
48/// ```
49#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
50#[component]
51pub fn AnimatedShow(
52 /// The components Show wraps
53 children: ChildrenFn,
54 /// If the component should show or not
55 #[prop(into)]
56 when: Signal<bool>,
57 /// Optional CSS class to apply if `when == true`
58 #[prop(optional)]
59 show_class: &'static str,
60 /// Optional CSS class to apply if `when == false`
61 #[prop(optional)]
62 hide_class: &'static str,
63 /// The timeout after which the component will be unmounted if `when == false`
64 hide_delay: Duration,
65) -> impl IntoView {
66 let handle: StoredValue<Option<TimeoutHandle>> = StoredValue::new(None);
67 let cls = RwSignal::new(if when.get_untracked() {
68 show_class
69 } else {
70 hide_class
71 });
72 let show = RwSignal::new(when.get_untracked());
73
74 let eff = RenderEffect::new(move |_| {
75 if when.get() {
76 // clear any possibly active timer
77 if let Some(h) = handle.get_value() {
78 h.clear();
79 }
80
81 cls.set(show_class);
82 show.set(true);
83 } else {
84 cls.set(hide_class);
85
86 let h = leptos_dom::helpers::set_timeout_with_handle(
87 move || show.set(false),
88 hide_delay,
89 )
90 .expect("set timeout in AnimatedShow");
91 handle.set_value(Some(h));
92 }
93 });
94
95 on_cleanup(move || {
96 if let Some(Some(h)) = handle.try_get_value() {
97 h.clear();
98 }
99 drop(eff);
100 });
101
102 view! {
103 <Show when=move || show.get() fallback=|| ()>
104 <div class=move || cls.get()>{children()}</div>
105 </Show>
106 }
107}