canonrs-client 0.1.0

CanonRS client-side runtime
use leptos::prelude::*;
use canonrs_core::AvatarPrimitive;

#[derive(Clone, Copy, Debug)]
struct AvatarImageError(RwSignal<bool>);

#[derive(Clone, Copy, PartialEq, Debug)]
pub enum AvatarSize { Xs, Sm, Md, Lg, Xl }
impl AvatarSize {
    pub fn as_str(&self) -> &'static str {
        match self { Self::Xs=>"xs", Self::Sm=>"sm", Self::Md=>"md", Self::Lg=>"lg", Self::Xl=>"xl" }
    }
}

#[derive(Clone, Copy, PartialEq, Debug)]
pub enum AvatarShape { Circle, Square, Rounded }
impl AvatarShape {
    pub fn as_str(&self) -> &'static str {
        match self { Self::Circle=>"circle", Self::Square=>"square", Self::Rounded=>"rounded" }
    }
}

#[derive(Clone, Copy, PartialEq, Debug)]
pub enum AvatarStatus { Online, Offline, Busy, Away }
impl AvatarStatus {
    pub fn as_str(&self) -> &'static str {
        match self { Self::Online=>"online", Self::Offline=>"offline", Self::Busy=>"busy", Self::Away=>"away" }
    }
}

#[component]
pub fn Avatar(
    #[prop(optional)] children: Option<Children>,
    #[prop(default = AvatarSize::Md)] size: AvatarSize,
    #[prop(default = AvatarShape::Circle)] shape: AvatarShape,
    #[prop(optional)] status: Option<AvatarStatus>,
    #[prop(default = false)] animated: bool,
    #[prop(optional)] badge: Option<i32>,
    #[prop(default = String::new())] class: String,
) -> impl IntoView {
    let img_error = RwSignal::new(false);
    provide_context(AvatarImageError(img_error));

    view! {
        <AvatarPrimitive
            class={format!("{} avatar-size-{} avatar-shape-{}", class, size.as_str(), shape.as_str())}
        >
            {children.map(|c| c())}
            {status.map(|s| {
                let pulse = animated && s == AvatarStatus::Online;
                view! {
                    <span
                        data-avatar-status=""
                        data-status={s.as_str()}
                        data-pulse={pulse.then_some("")}
                    />
                }
            })}
            {badge.map(|b| view! {
                <span data-avatar-badge="">{b}</span>
            })}
        </AvatarPrimitive>
    }
}

#[component]
pub fn AvatarImage(
    src: String,
    alt: String,
    #[prop(default = String::new())] class: String,
    #[prop(into, optional)] id: Option<String>,
) -> impl IntoView {
    let img_error = expect_context::<AvatarImageError>().0;

    view! {
        <img
            data-avatar-image=""
            src={src}
            alt={alt}
            class={class}
            id={id.unwrap_or_default()}
            class:hidden=move || img_error.get()
            on:error=move |_| img_error.set(true)
        />
    }
}

#[component]
pub fn AvatarFallback(
    #[prop(optional)] children: Option<Children>,
    #[prop(default = String::new())] class: String,
    #[prop(into, optional)] id: Option<String>,
) -> impl IntoView {
    let img_error = use_context::<AvatarImageError>().map(|c| c.0);

    view! {
        <span
            data-avatar-fallback=""
            class={class}
            id={id.unwrap_or_default()}
            class:hidden=move || img_error.map_or(false, |sig| !sig.get())
        >
            {children.map(|c| c())}
        </span>
    }
}