leptonic 0.5.0

The Leptos component library.
use leptos::*;
use std::rc::Rc;
use uuid::Uuid;

use crate::{prelude::{GlobalKeyboardEvent, Producer}, OptionalMaybeSignal};

#[derive(Copy, Clone)]
pub(crate) struct ModalRootContext {
    pub(crate) host: NodeRef<html::Custom>,
    pub(crate) modals: RwSignal<Vec<Uuid>>,
}

#[component]
pub fn ModalRoot(children: Children) -> impl IntoView {
    let host: NodeRef<html::Custom> = create_node_ref();
    let modals = create_rw_signal(Vec::new());
    provide_context::<ModalRootContext>(ModalRootContext { host, modals });

    let has_modals = create_memo(move |_| match modals.with(|it| it.is_empty()) {
        true => "false",
        false => "true",
    });

    let children = children();
    view! {
        { children }

        <leptonic-modal-host data-has-modals=has_modals>
            <leptonic-modal-backdrop />

            <leptonic-modals ref=host />
        </leptonic-modal-host>
    }
}

#[component]
pub fn Modal(
    #[prop(into)] show_when: MaybeSignal<bool>,
    #[prop(into, optional)] id: Option<String>,
    #[prop(into, optional)] class: Option<String>,
    #[prop(into, optional)] on_escape: Option<Producer<()>>,
    children: ChildrenFn,
) -> impl IntoView {
    let internal_id = Uuid::new_v4();

    let ctx = expect_context::<ModalRootContext>();

    if let Some(on_escape) = on_escape {
        let g_keyboard_event = expect_context::<GlobalKeyboardEvent>();
        create_effect(move |_| {
            if let Some(e) = g_keyboard_event.read_signal.get() {
                if show_when.get_untracked() && e.key().as_str() == "Escape" {
                    on_escape.call(());
                }
            }
        });
    }

    create_isomorphic_effect(move |_| match show_when.get() {
        true => ctx.modals.update(|m| m.push(internal_id)),
        false => ctx.modals.update(|m| {
            if let Some(ctx) = m.iter().position(|id| *id == internal_id) {
                m.remove(ctx);
            }
        }),
    });

    let this = Rc::new(move || {
        let id = id.clone();
        let class = class.clone();
        let children = children.clone();
        view! {
            <leptonic-modal id=id.unwrap_or_else(|| internal_id.to_string()) class=class>
                { children() }
            </leptonic-modal>
        }
        .into_view()
        .into()
    });

    let portaled_modal = Callback::new(move |()| {
        use std::ops::Deref;
        let mount = ctx.host.get_untracked().unwrap().into_any();
        let mount: &web_sys::Element = mount.deref();
        let mount: web_sys::Element = mount.clone();

        let this = this.clone();
        view! {
            <Portal mount children=this />
        }
        .into_view()
    });

    view! {
        <Show when=move || show_when.get()>
            { move || portaled_modal.call(()) }
        </Show>
    }
}

#[component]
pub fn ModalHeader(children: Children) -> impl IntoView {
    view! {
        <leptonic-modal-header>
            { children() }
        </leptonic-modal-header>
    }
}

#[component]
pub fn ModalTitle(children: Children) -> impl IntoView {
    view! {
        <leptonic-modal-title>
            { children() }
        </leptonic-modal-title>
    }
}

#[component]
pub fn ModalBody(
    children: Children,
    #[prop(into, optional)] style: OptionalMaybeSignal<String>,
) -> impl IntoView {
    view! {
        <leptonic-modal-body style=move || style.0.as_ref().map(SignalGet::get)>
            { children() }
        </leptonic-modal-body>
    }
}

#[component]
pub fn ModalFooter(children: Children) -> impl IntoView {
    view! {
        <leptonic-modal-footer>
            { children() }
        </leptonic-modal-footer>
    }
}