1use maud::{html, Markup};
3
4#[derive(Clone, Debug)]
5pub struct CellMarkup {
6 pub content: Markup,
7 pub align_right: bool,
8}
9
10impl CellMarkup {
11 pub fn text(s: &str) -> Self {
12 Self {
13 content: html! { (s) },
14 align_right: false,
15 }
16 }
17 pub fn right(s: &str) -> Self {
18 Self {
19 content: html! { (s) },
20 align_right: true,
21 }
22 }
23 pub fn markup(m: Markup, align_right: bool) -> Self {
24 Self {
25 content: m,
26 align_right,
27 }
28 }
29}
30
31#[derive(Clone, Debug)]
32pub struct Props {
33 pub headers: Vec<String>,
34 pub rows: Vec<Vec<String>>,
35 pub rich_rows: Vec<Vec<CellMarkup>>,
36 pub footer_row: Vec<CellMarkup>,
37 pub striped: bool,
38 pub hoverable: bool,
39 pub compact: bool,
40 pub caption: Option<String>,
41 pub right_align_cols: Vec<usize>,
43}
44
45impl Default for Props {
46 fn default() -> Self {
47 Self {
48 headers: vec![],
49 rows: vec![],
50 rich_rows: vec![],
51 footer_row: vec![],
52 striped: false,
53 hoverable: false,
54 compact: false,
55 caption: None,
56 right_align_cols: vec![],
57 }
58 }
59}
60
61pub fn render(props: Props) -> Markup {
62 let mut modifiers = String::new();
63
64 if props.striped {
65 modifiers.push_str(" mui-table--striped");
66 }
67 if props.hoverable {
68 modifiers.push_str(" mui-table--hoverable");
69 }
70 if props.compact {
71 modifiers.push_str(" mui-table--compact");
72 }
73
74 let class = format!("mui-table{}", modifiers);
75 let has_rich = !props.rich_rows.is_empty();
76 let has_footer = !props.footer_row.is_empty();
77
78 html! {
79 div.mui-table-wrapper {
80 table class=(class) {
81 @if let Some(caption_text) = props.caption {
82 caption.mui-table__caption { (caption_text) }
83 }
84 thead {
85 tr {
86 @for (i, header) in props.headers.iter().enumerate() {
87 @if props.right_align_cols.contains(&i) {
88 th.mui-table__th style="text-align:right;" { (header) }
89 } @else {
90 th.mui-table__th { (header) }
91 }
92 }
93 }
94 }
95 tbody {
96 @if has_rich {
97 @for row in &props.rich_rows {
98 tr.mui-table__row {
99 @for cell in row {
100 @if cell.align_right {
101 td.mui-table__td style="text-align:right;" { (cell.content) }
102 } @else {
103 td.mui-table__td { (cell.content) }
104 }
105 }
106 }
107 }
108 } @else {
109 @for row in &props.rows {
110 tr.mui-table__row {
111 @for cell in row {
112 td.mui-table__td { (cell) }
113 }
114 }
115 }
116 }
117 }
118 @if has_footer {
119 tfoot {
120 tr.mui-table__row {
121 @for cell in &props.footer_row {
122 @if cell.align_right {
123 td.mui-table__td style="text-align:right;font-weight:600;" { (cell.content) }
124 } @else {
125 td.mui-table__td style="font-weight:600;" { (cell.content) }
126 }
127 }
128 }
129 }
130 }
131 }
132 }
133 }
134}
135
136pub fn showcase() -> Markup {
137 use crate::primitives::badge;
138
139 let headers = vec![
140 "Invoice".to_string(),
141 "Status".to_string(),
142 "Method".to_string(),
143 "Amount".to_string(),
144 ];
145
146 let status_badge = |label: &str| -> Markup {
148 let variant = match label {
149 "Paid" => badge::Variant::Success,
150 "Pending" => badge::Variant::Warning,
151 "Unpaid" => badge::Variant::Danger,
152 _ => badge::Variant::Default,
153 };
154 badge::render(badge::Props {
155 label: label.to_string(),
156 variant,
157 })
158 };
159
160 let rich_rows = vec![
161 vec![
162 CellMarkup::text("INV001"),
163 CellMarkup::markup(status_badge("Paid"), false),
164 CellMarkup::text("Credit Card"),
165 CellMarkup::right("$250.00"),
166 ],
167 vec![
168 CellMarkup::text("INV002"),
169 CellMarkup::markup(status_badge("Pending"), false),
170 CellMarkup::text("PayPal"),
171 CellMarkup::right("$150.00"),
172 ],
173 vec![
174 CellMarkup::text("INV003"),
175 CellMarkup::markup(status_badge("Unpaid"), false),
176 CellMarkup::text("Bank Transfer"),
177 CellMarkup::right("$350.00"),
178 ],
179 vec![
180 CellMarkup::text("INV004"),
181 CellMarkup::markup(status_badge("Paid"), false),
182 CellMarkup::text("Credit Card"),
183 CellMarkup::right("$450.00"),
184 ],
185 vec![
186 CellMarkup::text("INV005"),
187 CellMarkup::markup(status_badge("Paid"), false),
188 CellMarkup::text("PayPal"),
189 CellMarkup::right("$550.00"),
190 ],
191 ];
192
193 let footer_row = vec![
194 CellMarkup::text("Total"),
195 CellMarkup::text(""),
196 CellMarkup::text(""),
197 CellMarkup::right("$1,750.00"),
198 ];
199
200 let plain_rows = vec![
202 vec!["INV001".into(), "Paid".into(), "Credit Card".into(), "$250.00".into()],
203 vec!["INV002".into(), "Pending".into(), "PayPal".into(), "$150.00".into()],
204 vec!["INV003".into(), "Unpaid".into(), "Bank Transfer".into(), "$350.00".into()],
205 vec!["INV004".into(), "Paid".into(), "Credit Card".into(), "$450.00".into()],
206 vec!["INV005".into(), "Paid".into(), "PayPal".into(), "$550.00".into()],
207 ];
208
209 html! {
210 div.mui-showcase__grid {
211 div {
212 p.mui-showcase__caption { "With badges, right-aligned amounts, and footer total" }
213 (render(Props {
214 headers: headers.clone(),
215 rich_rows,
216 footer_row,
217 hoverable: true,
218 right_align_cols: vec![3],
219 caption: Some("A list of your recent invoices.".to_string()),
220 ..Default::default()
221 }))
222 }
223 div {
224 p.mui-showcase__caption { "Striped + hoverable" }
225 (render(Props {
226 headers: headers.clone(),
227 rows: plain_rows.clone(),
228 striped: true,
229 hoverable: true,
230 right_align_cols: vec![3],
231 ..Default::default()
232 }))
233 }
234 div {
235 p.mui-showcase__caption { "Compact" }
236 (render(Props {
237 headers,
238 rows: plain_rows,
239 compact: true,
240 right_align_cols: vec![3],
241 ..Default::default()
242 }))
243 }
244 }
245 }
246}