yggdrasil/components/
empty_state.rs1use leptos::prelude::*;
4
5use crate::components::mascot::Mascot;
6use crate::components::ui::{Icon, IconName, BTN_SECONDARY};
7
8#[derive(Clone)]
10pub struct EmptyStateAction {
11 pub label: String,
12 pub onclick: Callback<()>,
13}
14
15impl EmptyStateAction {
16 pub fn new(label: impl Into<String>, onclick: impl Fn(()) + Send + Sync + 'static) -> Self {
18 Self {
19 label: label.into(),
20 onclick: Callback::new(onclick),
21 }
22 }
23}
24
25#[component]
29pub fn EmptyState(
30 #[prop(into, default = "还没有文章".to_string())] title: String,
31 #[prop(optional, into)] description: String,
32 #[prop(default = IconName::Inbox)] icon: IconName,
33 #[prop(optional)] mascot: Option<Mascot>,
34 #[prop(optional)] action: Option<EmptyStateAction>,
35) -> impl IntoView {
36 view! {
37 <section class="empty-state">
38 {match mascot {
39 Some(m) => m.img("mascot empty-state-mascot").into_any(),
40 None => {
41 view! {
42 <span class="empty-state-icon">
43 <Icon name=icon size=20/>
44 </span>
45 }
46 .into_any()
47 }
48 }}
49 <h2 class="empty-state-title">{title}</h2>
50 {(!description.is_empty())
51 .then(|| view! { <p class="empty-state-description">{description}</p> })}
52 {action
53 .map(|act| {
54 let onclick = act.onclick;
55 view! {
56 <button
57 class=format!("{BTN_SECONDARY} empty-state-action")
58 type="button"
59 on:click=move |_| onclick.run(())
60 >
61 {act.label}
62 </button>
63 }
64 })}
65 </section>
66 }
67}