Skip to main content

yggdrasil/components/
empty_state.rs

1//! 空状态组件。
2//!
3//! 当列表(首页、标签、搜索等)无数据时展示:插画配图 + 标题 + 副文案,
4//! 可选行动按钮。视觉语言沿用项目 Forest 调色板(鼠尾草绿强调色)与
5//! Source Serif 4 衬线标题,留白克制,与首页 HomeInfo 标题区风格一致。
6//!
7//! 配图为「线条小狗」插画(`public/images/xiaotiaoxiaogou_01.webp`),
8//! 通过 `<img>` 引用绝对路径,由 Dioxus 的静态资源服务直接返回。
9
10use dioxus::prelude::*;
11use dioxus::router::components::Link;
12
13/// 空状态行动按钮。
14#[derive(Props, Clone, PartialEq)]
15pub struct EmptyStateAction {
16    /// 按钮文案。
17    #[props(into)]
18    pub label: String,
19    /// 跳转目标路由。
20    pub to: crate::router::Route,
21}
22
23/// 空状态组件。
24///
25/// 默认渲染「线条小狗」配图;提供 `title` / `description` / `action` 可覆盖默认文案。
26/// 所有元素垂直居中,配图下方留白,与首页 `HomeInfo` 的居中布局对齐。
27///
28/// Props 由 `#[component]` 宏自动生成(`title` / `description` / `action` 均为可选)。
29#[component]
30pub fn EmptyState(
31    /// 主标题(通常为「还没有文章」之类)。
32    #[props(into, default = "还没有文章".to_string())]
33    title: String,
34    /// 副文案,说明当前状态或引导用户。
35    #[props(into, default = String::new())]
36    description: String,
37    /// 可选的行动按钮。
38    #[props(default)]
39    action: Option<EmptyStateAction>,
40) -> Element {
41    rsx! {
42        div { class: "flex flex-col items-center justify-center text-center py-20 px-4 page-enter",
43            // 配图:线条小狗(双手持相机,取景器内两只小狗)。
44            img {
45                class: "w-48 h-auto rounded-lg select-none dark:brightness-90",
46                src: "/images/xiaotiaoxiaogou_01.webp",
47                alt: "线条小狗插画",
48                draggable: "false",
49            }
50            // 主标题:衬线字体,与首页 H1 风格呼应但更轻量。
51            h2 { class: "mt-8 text-2xl font-bold tracking-tight text-paper-primary",
52                "{title}"
53            }
54            // 副文案:次要色,限宽保证可读性。
55            if !description.is_empty() {
56                p { class: "mt-3 text-sm leading-relaxed text-paper-secondary max-w-md",
57                    "{description}"
58                }
59            }
60            // 行动按钮:药丸形,与搜索页主按钮一致。
61            if let Some(act) = action {
62                Link {
63                    class: "mt-8 inline-flex items-center px-6 py-2 bg-paper-accent text-white rounded-full font-medium text-sm hover:brightness-110 active:scale-[0.98] transition-all duration-200",
64                    to: act.to,
65                    "{act.label}"
66                }
67            }
68        }
69    }
70}