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}