Skip to main content

maud_ui/primitives/
progress.rs

1//! Progress component — determinate and indeterminate progress indicators.
2
3use maud::{html, Markup};
4
5/// Progress bar rendering properties
6#[derive(Debug, Clone)]
7pub struct Props {
8    /// Current progress value (0..=100, or use any value with max)
9    pub value: u32,
10    /// Maximum value (default 100)
11    pub max: u32,
12    /// ARIA label for the progress bar
13    pub label: String,
14    /// If true, renders as indeterminate (animated) progress
15    pub indeterminate: bool,
16}
17
18impl Default for Props {
19    fn default() -> Self {
20        Self {
21            value: 0,
22            max: 100,
23            label: String::new(),
24            indeterminate: false,
25        }
26    }
27}
28
29/// Render a progress bar with the given properties
30pub fn render(props: Props) -> Markup {
31    let pct = if props.max == 0 { 0 } else { (props.value * 100) / props.max };
32
33    html! {
34        @if props.indeterminate {
35            div.mui-progress.mui-progress--indeterminate role="progressbar" aria-valuemin="0" aria-valuemax=(props.max) aria-label=(props.label) {
36                div.mui-progress__bar {}
37            }
38        } @else {
39            div.mui-progress role="progressbar" aria-valuenow=(props.value) aria-valuemin="0" aria-valuemax=(props.max) aria-label=(props.label) {
40                div.mui-progress__bar style=(format!("width: {}%", pct)) {}
41            }
42        }
43    }
44}
45
46/// Showcase all progress variants and use cases
47pub fn showcase() -> Markup {
48    html! {
49        div.mui-showcase__grid {
50            // File upload in progress
51            div {
52                p.mui-showcase__caption { "File upload" }
53                div style="display:flex;flex-direction:column;gap:0.5rem" {
54                    div style="display:flex;justify-content:space-between;align-items:center" {
55                        span style="font-size:0.875rem;color:var(--mui-text)" { "Uploading file..." }
56                        span style="font-size:0.875rem;font-weight:500;color:var(--mui-text)" { "65%" }
57                    }
58                    (render(Props {
59                        value: 65,
60                        max: 100,
61                        label: "Uploading file, 65 percent".into(),
62                        indeterminate: false,
63                    }))
64                    span style="font-size:0.75rem;color:var(--mui-text-muted)" { "report-2026.pdf — 3.2 MB of 4.9 MB" }
65                }
66            }
67
68            // Stepped progress
69            div {
70                p.mui-showcase__caption { "Stepped progress" }
71                div style="display:flex;flex-direction:column;gap:0.5rem" {
72                    div style="display:flex;justify-content:space-between;align-items:center" {
73                        span style="font-size:0.875rem;font-weight:500;color:var(--mui-text)" { "Step 2 of 4" }
74                        span style="font-size:0.75rem;color:var(--mui-text-muted)" { "Account details" }
75                    }
76                    (render(Props {
77                        value: 50,
78                        max: 100,
79                        label: "Step 2 of 4, account details".into(),
80                        indeterminate: false,
81                    }))
82                }
83            }
84
85            // Indeterminate — processing
86            div {
87                p.mui-showcase__caption { "Indeterminate" }
88                div style="display:flex;flex-direction:column;gap:0.5rem" {
89                    span style="font-size:0.875rem;color:var(--mui-text-muted)" { "Processing your request..." }
90                    (render(Props {
91                        value: 0,
92                        max: 100,
93                        label: "Processing request".into(),
94                        indeterminate: true,
95                    }))
96                }
97            }
98
99            // Download complete (100%)
100            div {
101                p.mui-showcase__caption { "Download complete" }
102                div style="display:flex;flex-direction:column;gap:0.5rem" {
103                    div style="display:flex;justify-content:space-between;align-items:center" {
104                        span style="font-size:0.875rem;color:var(--mui-text)" { "Download complete" }
105                        span style="font-size:0.875rem;font-weight:500;color:var(--mui-accent,var(--mui-text))" { "100%" }
106                    }
107                    (render(Props {
108                        value: 100,
109                        max: 100,
110                        label: "Download complete".into(),
111                        indeterminate: false,
112                    }))
113                    span style="font-size:0.75rem;color:var(--mui-text-muted)" { "dataset.csv — 12.7 MB" }
114                }
115            }
116        }
117    }
118}