use dioxus::prelude::*;
use crate::utils;
const AVATAR_CSS: &str = include_str!("./avatar.css");
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum ImageLoadingStatus {
Idle,
Loading,
Loaded,
Error,
}
#[derive(Props, Clone, PartialEq)]
pub struct AvatarProps {
#[props(default)]
pub children: Element,
#[props(optional)]
pub class: Option<String>,
}
#[component]
pub fn Avatar(props: AvatarProps) -> Element {
let image_loading_status = use_signal(|| ImageLoadingStatus::Idle);
use_context_provider(|| image_loading_status);
let class_name = utils::cn(vec![
Some("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full"),
props.class.as_deref(),
]);
rsx! {
style { {AVATAR_CSS} }
span {
class: "{class_name}",
{props.children}
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct AvatarImageProps {
pub src: String,
#[props(optional)]
pub alt: Option<String>,
#[props(optional)]
pub class: Option<String>,
#[props(optional)]
pub referrer_policy: Option<String>,
#[props(optional)]
pub cross_origin: Option<String>,
#[props(optional)]
pub on_loading_status_change: Option<EventHandler<ImageLoadingStatus>>,
}
#[component]
pub fn AvatarImage(props: AvatarImageProps) -> Element {
let mut image_loading_status = use_context::<Signal<ImageLoadingStatus>>();
let mut local_status = use_signal(|| ImageLoadingStatus::Idle);
let mut is_mounted = use_signal(|| false);
use_effect(move || {
if !is_mounted() {
local_status.set(ImageLoadingStatus::Loading);
is_mounted.set(true);
}
});
use_effect(move || {
let status = local_status();
image_loading_status.set(status);
if let Some(handler) = &props.on_loading_status_change {
handler.call(status);
}
});
let class_name = utils::cn(vec![
Some("aspect-square h-full w-full object-cover"),
props.class.as_deref(),
]);
let src = props.src.clone();
let alt = props.alt.clone().unwrap_or_default();
match local_status() {
ImageLoadingStatus::Loaded | ImageLoadingStatus::Loading => rsx! {
img {
class: "{class_name}",
src: "{src}",
alt: "{alt}",
referrerpolicy: props.referrer_policy.as_deref(),
crossorigin: props.cross_origin.as_deref(),
onload: move |_| {
local_status.set(ImageLoadingStatus::Loaded);
},
onerror: move |_| {
local_status.set(ImageLoadingStatus::Error);
},
}
},
_ => rsx! {},
}
}
#[derive(Props, Clone, PartialEq)]
pub struct AvatarFallbackProps {
#[props(default)]
pub children: Element,
#[props(optional)]
pub class: Option<String>,
}
#[component]
pub fn AvatarFallback(props: AvatarFallbackProps) -> Element {
let image_loading_status = use_context::<Signal<ImageLoadingStatus>>();
let class_name = utils::cn(vec![
Some("flex h-full w-full items-center justify-center rounded-full bg-muted"),
props.class.as_deref(),
]);
if image_loading_status() != ImageLoadingStatus::Loaded {
rsx! {
span {
class: "{class_name}",
{props.children}
}
}
} else {
rsx! {}
}
}