leptonic 0.5.0

The Leptos component library.
use leptos::*;

use crate::{icon::Icon, prelude::{Button, ButtonVariant}};

#[component]
pub fn H1(
    #[prop(into, optional)] id: Option<AttributeValue>,
    #[prop(into, optional)] class: Option<AttributeValue>,
    #[prop(into, optional)] style: Option<AttributeValue>,
    children: Children,
) -> impl IntoView {
    view! {
        <h1 id=id class=class style=style>
            { children() }
        </h1>
    }
}

#[component]
pub fn H2(
    #[prop(into, optional)] id: Option<AttributeValue>,
    #[prop(into, optional)] class: Option<AttributeValue>,
    #[prop(into, optional)] style: Option<AttributeValue>,
    children: Children,
) -> impl IntoView {
    view! {
        <h2 id=id class=class style=style>
            { children() }
        </h2>
    }
}

#[component]
pub fn H3(
    #[prop(into, optional)] id: Option<AttributeValue>,
    #[prop(into, optional)] class: Option<AttributeValue>,
    #[prop(into, optional)] style: Option<AttributeValue>,
    children: Children,
) -> impl IntoView {
    view! {
        <h3 id=id class=class style=style>
            { children() }
        </h3>
    }
}

#[component]
pub fn H4(
    #[prop(into, optional)] id: Option<AttributeValue>,
    #[prop(into, optional)] class: Option<AttributeValue>,
    #[prop(into, optional)] style: Option<AttributeValue>,
    children: Children,
) -> impl IntoView {
    view! {
        <h4 id=id class=class style=style>
            { children() }
        </h4>
    }
}

#[component]
pub fn H5(
    #[prop(into, optional)] id: Option<AttributeValue>,
    #[prop(into, optional)] class: Option<AttributeValue>,
    #[prop(into, optional)] style: Option<AttributeValue>,
    children: Children,
) -> impl IntoView {
    view! {
        <h5 id=id class=class style=style>
            { children() }
        </h5>
    }
}

#[component]
pub fn H6(
    #[prop(into, optional)] id: Option<AttributeValue>,
    #[prop(into, optional)] class: Option<AttributeValue>,
    #[prop(into, optional)] style: Option<AttributeValue>,
    children: Children,
) -> impl IntoView {
    view! {
        <h6 id=id class=class style=style>
            { children() }
        </h6>
    }
}

#[component]
pub fn P(
    #[prop(into, optional)] id: Option<AttributeValue>,
    #[prop(into, optional)] class: Option<AttributeValue>,
    #[prop(into, optional)] style: Option<AttributeValue>,
    children: Children,
) -> impl IntoView {
    view! {
        <p id=id class=class style=style>
            { children() }
        </p>
    }
}

#[derive(Clone)]
#[slot]
pub struct Li {
    children: ChildrenFn,
}

#[component]
pub fn Ul(
    #[prop(into, optional)] id: Option<AttributeValue>,
    #[prop(into, optional)] class: Option<AttributeValue>,
    #[prop(into, optional)] style: Option<AttributeValue>,
    #[prop(default=vec![])] li: Vec<Li>,
) -> impl IntoView {
    view! {
        <ul id=id class=class style=style>
            <For
                each=move || { li.clone().into_iter().enumerate() }
                key=move |(index, _e)| *index
                children=move |(_index, e)| view!{ 
                    <li>
                        {e.children}
                    </li>
                }
            />
        </ul>
    }
}

#[component]
pub fn Code(
    #[prop(into, optional)] id: Option<AttributeValue>,
    #[prop(into, optional)] class: Option<AttributeValue>,
    #[prop(into, optional)] style: Option<AttributeValue>,
    #[prop(optional)] inline: Option<bool>,
    #[prop(optional)] show_copy_button: Option<bool>,
    #[prop(optional)] on_copy: Option<Callback<Result<(), ()>>>,
    children: Children,
) -> impl IntoView {
    let code_text = store_value(
        itertools::Itertools::intersperse(
            children().nodes.iter().map(|e| match e {
                View::Text(t) => t.content.clone(),
                _ => panic!("non-text children are not allowed: {:?}", e.to_owned()),
            }),
            "\n".into(),
        )
        .collect::<String>(),
    );

    let show_copy_button = show_copy_button.unwrap_or_else(|| !inline.unwrap_or(false));
    let on_success = move || {
        if let Some(on_copy) = on_copy {
            on_copy.call(Ok(()));
        }
    };
    let on_err = move || {
        if let Some(on_copy) = on_copy {
            on_copy.call(Err(()));
        } else {
            tracing::warn!("copy to clipboard failed");
        }
    };

    let copy_btn = show_copy_button.then(|| {
        view!(
            <Button
                class="leptonic-code-copy-button"
                variant=ButtonVariant::Flat
                on_click=move |_| copy_to_clipboard(
                    code_text.with_value(|c| c.clone()),
                    on_success,
                    on_err
                )>
                <Icon icon=icondata::VsCopy/>
            </Button>
        )
    });

    view! {
        <leptonic-code inline=inline.map(|it| it.to_string())>
            <leptonic-code-text id=id class=class style=style inline=inline.map(|it| it.to_string()) >
                { code_text.get_value() }
            </leptonic-code-text>
            { copy_btn }
        </leptonic-code>
    }
}

#[cfg(feature = "clipboard")]
fn copy_to_clipboard<T: AsRef<str>, S: Fn() + 'static, E: Fn() + 'static>(
    text: T,
    on_success: S,
    on_err: E,
) {
    match leptos_use::use_window().navigator() {
        Some(navigator) => {
            match navigator.clipboard() {
                Some(clipboard) => {
                    let promise = clipboard.write_text(text.as_ref());
                    let future = wasm_bindgen_futures::JsFuture::from(promise);
                    wasm_bindgen_futures::spawn_local(async move {
                        match future.await {
                            Ok(_result) => {
                                on_success();
                            }
                            Err(_err) => {
                                on_err();
                            }
                        }
                    });
                }
                None => {
                    on_err();
                }
            };
        }
        None => {
            on_err();
        }
    }
}

#[cfg(not(feature = "clipboard"))]
fn copy_to_clipboard<T: AsRef<str>, S: Fn() + 'static, E: Fn() + 'static>(
    _text: T,
    _on_success: S,
    _on_err: E,
) {
    tracing::warn!("Clipboard related functionality requires leptonic's 'Clipboard' feature as well as '--cfg=web_sys_unstable_apis'.");
}