orbital-base-components 0.1.1

Headless base primitives for Orbital — structure, semantics, and accessibility
Documentation
use leptos::{children::ToChildren, ev, html, prelude::*};

use super::config::AnchoredOverlayConfig;
use crate::overlay::{
    appearance::OverlayAppearance,
    arrow::build_anchor_arrow,
    panel_surface::OverlayPanelInjection,
    placement::Placement,
    positioning::{AnchoredPanel, AnchoredPositioner},
    trigger::{render_overlay_trigger, OverlayTrigger, OverlayTriggerType},
    visibility::{OverlayHoverDelays, OverlayLifecycle, UseOverlayVisibility},
};

#[derive(Clone, Copy)]
pub struct OverlayDismiss {
    pub close: Callback<()>,
}

#[component]
pub fn AnchoredOverlay<TTrigger>(
    config: AnchoredOverlayConfig,
    #[prop(optional)] overlay_trigger: Option<OverlayTrigger<TTrigger>>,
    #[prop(optional)] trigger: Option<TypedChildren<TTrigger>>,
    #[prop(default = OverlayLifecycle::default())] lifecycle: OverlayLifecycle,
    #[prop(optional, into)] class: MaybeProp<String>,
    #[prop(optional)] panel_ref: Option<NodeRef<html::Div>>,
    #[prop(default = None)] mount: Option<NodeRef<html::Div>>,
    children: Children,
) -> impl IntoView
where
    TTrigger: AddAnyAttr + IntoView + Send + 'static,
{
    let panel_ref = panel_ref.unwrap_or_default();
    let visibility = UseOverlayVisibility::new(
        config.trigger_type,
        panel_ref,
        Some(lifecycle),
        OverlayHoverDelays {
            show_delay_ms: config.show_delay_ms,
            hide_delay_ms: config.hide_delay_ms,
        },
    );
    provide_context(OverlayDismiss {
        close: Callback::new(move |_| visibility.is_show.set(false)),
    });

    let on_mouse_enter = {
        let vis = visibility;
        Callback::new(move |e: ev::MouseEvent| vis.on_mouse_enter(e))
    };
    let on_mouse_leave = {
        let vis = visibility;
        Callback::new(move |e: ev::MouseEvent| vis.on_mouse_leave(e))
    };
    provide_context(OverlayPanelInjection {
        panel_ref,
        on_mouse_enter,
        on_mouse_leave,
    });

    let arrow_ref = NodeRef::<html::Div>::new();
    let arrow = config
        .arrow
        .enabled()
        .then(|| build_anchor_arrow(arrow_ref));

    let trigger_view = if let Some(overlay_trigger) = overlay_trigger {
        render_overlay_trigger(
            overlay_trigger,
            &visibility,
            config.trigger_type,
            "orbital-overlay-trigger--open",
        )
        .into_any()
    } else if let Some(trigger) = trigger {
        visibility
            .attach_trigger(trigger.into_inner()().into_inner(), config.trigger_type)
            .into_any()
    } else {
        ().into_any()
    };

    let panel_children = children;
    let extra_class = class;

    view! {
        <AnchoredPositioner
            mount=mount
            panel_ref=panel_ref
            dismiss_on_anchor_hidden=config.trigger_type.uses_click()
            panel=AnchoredPanel {
            show: visibility.is_show.read_only().into(),
            width: None,
            placement: config.placement,
            auto_height: config.auto_height,
            arrow,
            motion: config.motion,
            children: ToChildren::to_children(move || {
                let _ = extra_class.get();
                panel_children()
            }),
        }>
            {trigger_view}
        </AnchoredPositioner>
    }
}

pub fn merge_config(
    base: AnchoredOverlayConfig,
    trigger_type: Option<OverlayTriggerType>,
    placement: Option<Placement>,
    appearance: Option<OverlayAppearance>,
    show_delay_ms: Option<u64>,
    hide_delay_ms: Option<u64>,
) -> AnchoredOverlayConfig {
    AnchoredOverlayConfig {
        trigger_type: trigger_type.unwrap_or(base.trigger_type),
        placement: placement.unwrap_or(base.placement),
        appearance: appearance.unwrap_or(base.appearance),
        show_delay_ms: show_delay_ms.unwrap_or(base.show_delay_ms),
        hide_delay_ms: hide_delay_ms.unwrap_or(base.hide_delay_ms),
        ..base
    }
}