maud_ui/primitives/
toggle_group.rs1use maud::{html, Markup};
3
4#[derive(Clone, Debug)]
5pub struct GroupItem {
6 pub value: String,
7 pub label: String,
8 pub pressed: bool,
9}
10
11#[derive(Clone, Debug, Default, PartialEq)]
12pub enum Size {
13 #[default]
14 Md,
15 Sm,
16}
17
18#[derive(Clone, Debug)]
19pub struct Props {
20 pub items: Vec<GroupItem>,
21 pub multiple: bool,
22 pub disabled: bool,
23 pub aria_label: String,
24 pub size: Size,
25}
26
27impl Default for Props {
28 fn default() -> Self {
29 Self {
30 items: vec![],
31 multiple: false,
32 disabled: false,
33 aria_label: "Toggle group".to_string(),
34 size: Size::Md,
35 }
36 }
37}
38
39pub fn render(props: Props) -> Markup {
40 let multiple_attr = if props.multiple { "true" } else { "false" };
41 let size_cls = match props.size {
42 Size::Md => "mui-toggle-group--md",
43 Size::Sm => "mui-toggle-group--sm",
44 };
45
46 let first_focusable_idx = props
48 .items
49 .iter()
50 .position(|item| item.pressed)
51 .unwrap_or(0);
52
53 html! {
54 div class={"mui-toggle-group " (size_cls)}
55 role="group"
56 aria-label=(props.aria_label)
57 data-mui="toggle-group"
58 data-multiple=(multiple_attr)
59 data-disabled=[props.disabled.then(|| "true")]
60 {
61 @for (idx, item) in props.items.iter().enumerate() {
62 @let tabindex = if idx == first_focusable_idx { "0" } else { "-1" };
63 @let aria_pressed = if item.pressed { "true" } else { "false" };
64
65 button type="button" class="mui-toggle-group__item"
66 aria-pressed=(aria_pressed)
67 data-value=(item.value)
68 tabindex=(tabindex)
69 disabled[props.disabled]
70 {
71 (item.label.clone())
72 }
73 }
74 }
75 }
76}
77
78pub fn showcase() -> Markup {
79 html! {
80 div.mui-showcase__grid {
81 section {
83 h2 { "Text alignment" }
84 p.mui-showcase__caption { "Paragraph alignment in the document editor." }
85 div.mui-showcase__row {
86 (render(Props {
87 items: vec![
88 GroupItem { value: "left".into(), label: "Left".into(), pressed: true },
89 GroupItem { value: "center".into(), label: "Center".into(), pressed: false },
90 GroupItem { value: "right".into(), label: "Right".into(), pressed: false },
91 GroupItem { value: "justify".into(), label: "Justify".into(), pressed: false },
92 ],
93 aria_label: "Text alignment".into(),
94 ..Default::default()
95 }))
96 }
97 }
98
99 section {
101 h2 { "Text formatting" }
102 p.mui-showcase__caption { "Bold, italic, underline, strikethrough — multi-select." }
103 div.mui-showcase__row {
104 (render(Props {
105 items: vec![
106 GroupItem { value: "bold".into(), label: "Bold".into(), pressed: true },
107 GroupItem { value: "italic".into(), label: "Italic".into(), pressed: false },
108 GroupItem { value: "underline".into(), label: "Underline".into(), pressed: true },
109 GroupItem { value: "strike".into(), label: "Strike".into(), pressed: false },
110 ],
111 multiple: true,
112 aria_label: "Text formatting".into(),
113 ..Default::default()
114 }))
115 }
116 }
117
118 section {
120 h2 { "Calendar view" }
121 p.mui-showcase__caption { "Switch between Day, Week, and Month layouts." }
122 div.mui-showcase__row {
123 (render(Props {
124 items: vec![
125 GroupItem { value: "day".into(), label: "Day".into(), pressed: false },
126 GroupItem { value: "week".into(), label: "Week".into(), pressed: true },
127 GroupItem { value: "month".into(), label: "Month".into(), pressed: false },
128 ],
129 aria_label: "Calendar view".into(),
130 ..Default::default()
131 }))
132 }
133 div.mui-showcase__row style="margin-top:0.5rem;" {
134 span.mui-showcase__label { "Compact" }
135 (render(Props {
136 items: vec![
137 GroupItem { value: "day".into(), label: "Day".into(), pressed: false },
138 GroupItem { value: "week".into(), label: "Week".into(), pressed: true },
139 GroupItem { value: "month".into(), label: "Month".into(), pressed: false },
140 ],
141 size: Size::Sm,
142 aria_label: "Calendar view compact".into(),
143 ..Default::default()
144 }))
145 }
146 }
147 }
148 }
149}