orbital-base-components 0.1.1

Headless base primitives for Orbital — structure, semantics, and accessibility
Documentation
use leptos::{either::Either, html, prelude::*};
use orbital_motion::{
    resolve_presence_motion, resolve_presence_motion_derived, MotionSlot, OrbitalPresence,
    PresenceMotion,
};

use crate::overlay::{
    backdrop::BaseBackdrop, focus_trap::FocusTrap, open_bind::OpenBind, themed_portal::ThemedPortal,
};
use crate::Handler;

use super::sizes::{
    drawer_presence_motion, drawer_size_css, DrawerModalType, DrawerPosition, DrawerSize,
};

#[component]
pub fn BaseOverlayDrawer(
    #[prop(optional, into)] class: MaybeProp<String>,
    #[prop(optional, into)] container_class: MaybeProp<String>,
    #[prop(into)] open: OpenBind,
    #[prop(default = true.into(), into)] mask_closeable: Signal<bool>,
    #[prop(optional)] close_on_esc: bool,
    #[prop(default = DrawerPosition::Right.into(), into)] position: Signal<DrawerPosition>,
    #[prop(default = DrawerSize::Small.into(), into)] size: Signal<DrawerSize>,
    #[prop(default = DrawerModalType::Modal)] modal_type: DrawerModalType,
    #[prop(optional)] mask_motion: MotionSlot,
    #[prop(optional)] panel_motion: MotionSlot,
    #[prop(default = None)] mount: Option<NodeRef<html::Div>>,
    children: Children,
) -> impl IntoView {
    let open_drawer = RwSignal::new(open.get_untracked());
    let open_signal = open.signal();

    Effect::new(move |_| {
        open_drawer.set(open.get());
    });

    let on_mask_click = move |_| {
        if mask_closeable.get_untracked() {
            open.set(false);
        }
    };
    let on_esc = Handler::with(move |_: leptos::ev::KeyboardEvent| {
        if close_on_esc {
            open.set(false);
        }
    });

    let fade_motion = resolve_presence_motion(mask_motion, PresenceMotion::fade());
    let panel_motion = resolve_presence_motion_derived(panel_motion, move || {
        drawer_presence_motion(position.get())
    });

    view! {
        <ThemedPortal
            immediate=open_signal
            class=container_class
            mount_ref=mount
        >
            <FocusTrap disabled=!close_on_esc active=open_signal on_esc>
                <div class="orbital-overlay-drawer-container">
                    {if modal_type == DrawerModalType::Modal {
                        Either::Left(view! {
                            <OrbitalPresence
                                appear=open.get_untracked()
                                show=open_signal
                                motion=fade_motion
                            >
                                <BaseBackdrop on_click=Callback::new(on_mask_click) />
                            </OrbitalPresence>
                        })
                    } else {
                        Either::Right(())
                    }}
                    <OrbitalPresence
                        appear=open_drawer.get_untracked()
                        show=open_drawer.read_only()
                        motion=panel_motion
                    >
                        <div
                            class=move || {
                                let mut parts = vec![
                                    "orbital-overlay-drawer".to_string(),
                                    format!(
                                        "orbital-overlay-drawer--position-{}",
                                        position.get().as_str()
                                    ),
                                ];
                                if let Some(extra) = class.get() {
                                    if !extra.is_empty() {
                                        parts.push(extra);
                                    }
                                }
                                parts.join(" ")
                            }
                            style=(
                                "--orbital-drawer--size",
                                move || drawer_size_css(size.get(), position.get()),
                            )
                            role="dialog"
                            aria-modal=if modal_type == DrawerModalType::Modal {
                                "true"
                            } else {
                                "false"
                            }
                        >
                            {children()}
                        </div>
                    </OrbitalPresence>
                </div>
            </FocusTrap>
        </ThemedPortal>
    }
}