1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! Accordion component — multiple collapsibles with optional single-open mode.
use maud::{html, Markup};
/// Individual accordion item
#[derive(Clone, Debug)]
pub struct Item {
/// Unique identifier for aria-controls and content linking
pub id: String,
/// Label text displayed in the trigger button
pub trigger: String,
/// Markup content displayed when expanded
pub content: Markup,
/// Initial open state (default false)
pub open: bool,
}
/// Accordion rendering properties
#[derive(Clone, Debug)]
pub struct Props {
/// Array of accordion items
pub items: Vec<Item>,
/// If true, multiple items can be open; if false, only one item at a time
pub multiple: bool,
}
impl Default for Props {
fn default() -> Self {
Self {
items: vec![],
multiple: false,
}
}
}
/// Render an accordion with the given properties
pub fn render(props: Props) -> Markup {
let multiple_attr = if props.multiple { "true" } else { "false" };
html! {
div class="mui-accordion" data-mui="accordion" data-multiple=(multiple_attr) {
@for item in props.items {
div class="mui-accordion__item" {
button type="button"
class="mui-accordion__trigger"
id=(format!("{}-trigger", item.id))
aria-expanded=(if item.open { "true" } else { "false" })
aria-controls=(format!("{}-content", item.id))
{
span class="mui-accordion__label" { (item.trigger) }
span class="mui-accordion__chevron" aria-hidden="true" { "\u{25BE}" }
}
div class="mui-accordion__content"
id=(format!("{}-content", item.id))
aria-labelledby=(format!("{}-trigger", item.id))
role="region"
hidden[!item.open]
{
(item.content)
}
}
}
}
}
}
/// Showcase all accordion use cases
pub fn showcase() -> Markup {
html! {
div.mui-showcase__grid {
// FAQ — single open (shadcn-style)
div {
h3 class="mui-showcase__caption" { "FAQ" }
(render(Props {
items: vec![
Item {
id: "faq-accessible".to_string(),
trigger: "Is it accessible?".to_string(),
content: html! {
p { "Yes. It adheres to the WAI-ARIA design pattern." }
},
open: true,
},
Item {
id: "faq-styled".to_string(),
trigger: "Is it styled?".to_string(),
content: html! {
p { "Yes. It comes with a default theme that matches shadcn." }
},
open: false,
},
Item {
id: "faq-animated".to_string(),
trigger: "Is it animated?".to_string(),
content: html! {
p { "Yes. JavaScript handles expand/collapse with ARIA state." }
},
open: false,
},
],
multiple: false,
}))
}
// Multiple — multiple items can be open simultaneously
div {
h3 class="mui-showcase__caption" { "Multiple" }
(render(Props {
items: vec![
Item {
id: "multi-acc-a".to_string(),
trigger: "Section A".to_string(),
content: html! { p { "Content for Section A with relevant information." } },
open: true,
},
Item {
id: "multi-acc-b".to_string(),
trigger: "Section B".to_string(),
content: html! { p { "Content for Section B with additional details." } },
open: false,
},
Item {
id: "multi-acc-c".to_string(),
trigger: "Section C".to_string(),
content: html! { p { "Content for Section C with more information." } },
open: true,
},
],
multiple: true,
}))
}
}
}
}