Skip to main content

orbital_motion/
group.rs

1use leptos::prelude::*;
2#[cfg(feature = "preview")]
3use orbital_macros::component_doc;
4
5use crate::callback::MotionElementCallback;
6use crate::presence::{stagger_motion, OrbitalPresence, PresenceMotion};
7use crate::tokens::MotionDuration;
8
9/// Context for [`OrbitalPresenceGroup`] stagger choreography.
10#[derive(Clone, Copy)]
11pub struct MotionGroupContext {
12    pub motion: Signal<PresenceMotion>,
13    pub stagger: Signal<MotionDuration>,
14}
15
16/// Keyed list enter/exit with staggered enter delays between children.
17///
18/// Wrap a keyed [`ForEnumerate`] of [`OrbitalPresenceGroupItem`] children, pass a shared `motion` preset, and tune `stagger` (default [`MotionDuration::Normal`](crate::MotionDuration::Normal)) for spacing between items. Each item needs a stable `index` signal and its own `show` gate.
19///
20/// Stagger delays accumulate via [`MotionDuration::stagger_step_delay`](crate::MotionDuration::stagger_step_delay) — index 0 enters immediately, index 1 waits one step, index 2 waits two steps, and so on. Use `on_after_leave` on each item to remove rows from the DOM after the exit animation finishes.
21///
22/// # Examples
23///
24/// ## Staggered list
25/// <!-- preview -->
26/// ```rust
27/// use crate::preview::OrbitalPresenceGroupDemo;
28/// view! {
29///     <OrbitalPresenceGroupDemo />
30/// }
31/// ```
32#[cfg_attr(
33    feature = "preview",
34    component_doc(
35        category = "Motion",
36        preview_slug = "orbital-presence-group",
37        preview_label = "Orbital Presence Group",
38        preview_icon = icondata::AiOrderedListOutlined,
39    )
40)]
41#[component]
42pub fn OrbitalPresenceGroup(
43    #[prop(into)] motion: Signal<PresenceMotion>,
44    #[prop(optional, into)] stagger: Option<Signal<MotionDuration>>,
45    children: Children,
46) -> impl IntoView {
47    let stagger = stagger.unwrap_or_else(|| Signal::from(MotionDuration::Normal));
48    provide_context(MotionGroupContext { motion, stagger });
49    children()
50}
51
52/// One staggered child inside [`OrbitalPresenceGroup`].
53#[component]
54pub fn OrbitalPresenceGroupItem(
55    #[prop(into)] show: Signal<bool>,
56    /// Zero-based index in the keyed list (used for stagger delay).
57    #[prop(into)]
58    index: Signal<usize>,
59    /// Fired after the leave transition completes.
60    #[prop(optional)]
61    on_after_leave: Option<MotionElementCallback>,
62    children: Children,
63) -> impl IntoView {
64    let ctx = use_context::<MotionGroupContext>()
65        .expect("OrbitalPresenceGroupItem must be used inside OrbitalPresenceGroup");
66    let motion =
67        Signal::derive(move || stagger_motion(ctx.motion.get(), ctx.stagger.get(), index.get()));
68    let on_after_leave =
69        on_after_leave.unwrap_or_else(|| MotionElementCallback::new(|_: web_sys::HtmlElement| {}));
70
71    view! {
72        <OrbitalPresence show=show motion=motion appear=true on_after_leave=on_after_leave>
73            {children()}
74        </OrbitalPresence>
75    }
76}