Skip to main content

maud_ui/primitives/
empty_state.rs

1//! EmptyState component — placeholder for empty content (no results, empty table, etc.).
2
3use maud::{html, Markup};
4
5/// EmptyState rendering properties
6#[derive(Debug, Clone)]
7pub struct Props {
8    /// Optional icon (text or emoji), defaults to 📭
9    pub icon: Option<String>,
10    /// Main title/heading
11    pub title: String,
12    /// Optional description text
13    pub description: Option<String>,
14    /// Optional action markup (e.g., button)
15    pub action: Option<Markup>,
16}
17
18impl Props {
19    pub fn new(title: impl Into<String>) -> Self {
20        Self {
21            icon: None,
22            title: title.into(),
23            description: None,
24            action: None,
25        }
26    }
27
28    pub fn with_icon(mut self, icon: impl Into<String>) -> Self {
29        self.icon = Some(icon.into());
30        self
31    }
32
33    pub fn with_description(mut self, description: impl Into<String>) -> Self {
34        self.description = Some(description.into());
35        self
36    }
37
38    pub fn with_action(mut self, action: Markup) -> Self {
39        self.action = Some(action);
40        self
41    }
42}
43
44/// Render an empty state component with icon, title, description, and optional action
45pub fn render(props: Props) -> Markup {
46    let icon = props.icon.unwrap_or_else(|| "📭".to_string());
47
48    html! {
49        div.mui-empty-state {
50            div.mui-empty-state__icon aria-hidden="true" { (icon) }
51            h3.mui-empty-state__title { (props.title) }
52            @if let Some(desc) = props.description {
53                p.mui-empty-state__description { (desc) }
54            }
55            @if let Some(action) = props.action {
56                div.mui-empty-state__action { (action) }
57            }
58        }
59    }
60}
61
62/// Showcase all empty state variants and use cases
63pub fn showcase() -> Markup {
64    html! {
65        div.mui-showcase__grid {
66            // No results
67            div {
68                p.mui-showcase__caption { "No results" }
69                div style="border:1px solid var(--mui-border);border-radius:var(--mui-radius-lg);background:var(--mui-bg-card)" {
70                    (render(
71                        Props::new("No results found")
72                            .with_icon("\u{1F50D}")
73                            .with_description("Try adjusting your search query or removing some filters to find what you're looking for.")
74                            .with_action(html! {
75                                button type="button" class="mui-btn mui-btn--outline mui-btn--md" { "Clear filters" }
76                            })
77                    ))
78                }
79            }
80
81            // Empty inbox
82            div {
83                p.mui-showcase__caption { "Empty inbox" }
84                div style="border:1px solid var(--mui-border);border-radius:var(--mui-radius-lg);background:var(--mui-bg-card)" {
85                    (render(
86                        Props::new("Your inbox is empty")
87                            .with_icon("\u{2709}\u{FE0F}")
88                            .with_description("Messages you receive will appear here.")
89                    ))
90                }
91            }
92
93            // First-run / onboarding
94            div {
95                p.mui-showcase__caption { "First run" }
96                div style="border:1px solid var(--mui-border);border-radius:var(--mui-radius-lg);background:var(--mui-bg-card)" {
97                    (render(
98                        Props::new("Create your first project")
99                            .with_icon("\u{1F680}")
100                            .with_description("Projects help you organize your work and collaborate with your team.")
101                            .with_action(html! {
102                                button type="button" class="mui-btn mui-btn--default mui-btn--md" { "New project" }
103                            })
104                    ))
105                }
106            }
107
108            // Minimal
109            div {
110                p.mui-showcase__caption { "Minimal" }
111                (render(Props::new("Nothing here yet")))
112            }
113        }
114    }
115}