maud_ui/primitives/
tabs.rs1use maud::{html, Markup};
4
5#[derive(Clone, Debug)]
6pub struct Tab {
7 pub id: String,
8 pub label: String,
9 pub content: Markup,
10}
11
12#[derive(Clone, Debug)]
13pub struct Props {
14 pub tabs: Vec<Tab>,
15 pub default_active: usize,
16 pub aria_label: String,
17}
18
19impl Default for Props {
20 fn default() -> Self {
21 Self {
22 tabs: vec![],
23 default_active: 0,
24 aria_label: "Tabs".to_string(),
25 }
26 }
27}
28
29pub fn render(props: Props) -> Markup {
30 html! {
31 div class="mui-tabs" data-mui="tabs" {
32 div class="mui-tabs__list" role="tablist" aria-label=(props.aria_label) {
33 @for (i, tab) in props.tabs.iter().enumerate() {
34 @let is_active = i == props.default_active;
35 @let tabindex = if is_active { "0" } else { "-1" };
36 button type="button"
37 class="mui-tabs__trigger"
38 role="tab"
39 id=(format!("{}-trigger", tab.id))
40 aria-controls=(format!("{}-panel", tab.id))
41 aria-selected=(if is_active { "true" } else { "false" })
42 tabindex=(tabindex) {
43 (tab.label)
44 }
45 }
46 }
47 @for (i, tab) in props.tabs.iter().enumerate() {
48 @let is_active = i == props.default_active;
49 div class="mui-tabs__panel"
50 role="tabpanel"
51 id=(format!("{}-panel", tab.id))
52 aria-labelledby=(format!("{}-trigger", tab.id))
53 tabindex="0"
54 hidden[!is_active]
55 {
56 (tab.content)
57 }
58 }
59 }
60 }
61}
62
63pub fn showcase() -> Markup {
64 use crate::primitives::{input, label, button};
65
66 let tabs = vec![
67 Tab {
68 id: "account".to_string(),
69 label: "Account".to_string(),
70 content: html! {
71 div style="padding:1rem 0;" {
72 p style="font-size:0.875rem;color:var(--mui-text-muted);margin:0 0 1rem;" {
73 "Make changes to your account here. Click save when you\u{2019}re done."
74 }
75 div style="display:flex;flex-direction:column;gap:0.75rem;max-width:24rem;" {
76 div class="mui-field" {
77 (label::render(label::Props {
78 text: "Name".into(),
79 html_for: Some("tab-account-name".into()),
80 ..Default::default()
81 }))
82 (input::render(input::Props {
83 name: "name".into(),
84 id: "tab-account-name".into(),
85 input_type: input::InputType::Text,
86 value: "Pedro Duarte".into(),
87 ..Default::default()
88 }))
89 }
90 div class="mui-field" {
91 (label::render(label::Props {
92 text: "Email".into(),
93 html_for: Some("tab-account-email".into()),
94 ..Default::default()
95 }))
96 (input::render(input::Props {
97 name: "email".into(),
98 id: "tab-account-email".into(),
99 input_type: input::InputType::Email,
100 value: "pedro@example.com".into(),
101 ..Default::default()
102 }))
103 }
104 div style="display:flex;justify-content:flex-end;margin-top:0.5rem;" {
105 (button::render(button::Props {
106 label: "Save changes".into(),
107 variant: button::Variant::Primary,
108 size: button::Size::Md,
109 ..Default::default()
110 }))
111 }
112 }
113 }
114 },
115 },
116 Tab {
117 id: "password".to_string(),
118 label: "Password".to_string(),
119 content: html! {
120 div style="padding:1rem 0;" {
121 p style="font-size:0.875rem;color:var(--mui-text-muted);margin:0 0 1rem;" {
122 "Change your password here. After saving, you\u{2019}ll be logged out."
123 }
124 div style="display:flex;flex-direction:column;gap:0.75rem;max-width:24rem;" {
125 div class="mui-field" {
126 (label::render(label::Props {
127 text: "Current password".into(),
128 html_for: Some("tab-pw-current".into()),
129 ..Default::default()
130 }))
131 (input::render(input::Props {
132 name: "current_password".into(),
133 id: "tab-pw-current".into(),
134 input_type: input::InputType::Password,
135 placeholder: "Enter current password".into(),
136 ..Default::default()
137 }))
138 }
139 div class="mui-field" {
140 (label::render(label::Props {
141 text: "New password".into(),
142 html_for: Some("tab-pw-new".into()),
143 ..Default::default()
144 }))
145 (input::render(input::Props {
146 name: "new_password".into(),
147 id: "tab-pw-new".into(),
148 input_type: input::InputType::Password,
149 placeholder: "Enter new password".into(),
150 ..Default::default()
151 }))
152 }
153 div style="display:flex;justify-content:flex-end;margin-top:0.5rem;" {
154 (button::render(button::Props {
155 label: "Change password".into(),
156 variant: button::Variant::Primary,
157 size: button::Size::Md,
158 ..Default::default()
159 }))
160 }
161 }
162 }
163 },
164 },
165 Tab {
166 id: "team".to_string(),
167 label: "Team".to_string(),
168 content: html! {
169 div style="padding:1rem 0;" {
170 p style="font-size:0.875rem;color:var(--mui-text-muted);margin:0 0 1rem;" {
171 "Invite your team members to collaborate."
172 }
173 div style="display:flex;flex-direction:column;gap:0.75rem;max-width:28rem;" {
174 @for (name, email, role) in [
175 ("Sofia Davis", "sofia@example.com", "Owner"),
176 ("Jackson Lee", "jackson@example.com", "Member"),
177 ("Isabella Nguyen", "isabella@example.com", "Member"),
178 ] {
179 div style="display:flex;align-items:center;justify-content:space-between;padding:0.5rem 0;border-bottom:1px solid var(--mui-border,#e5e7eb);" {
180 div {
181 p style="font-size:0.875rem;font-weight:500;margin:0;" { (name) }
182 p style="font-size:0.8125rem;color:var(--mui-text-muted);margin:0.125rem 0 0;" { (email) }
183 }
184 span style="font-size:0.75rem;color:var(--mui-text-muted);padding:0.25rem 0.5rem;border:1px solid var(--mui-border,#e5e7eb);border-radius:0.375rem;" {
185 (role)
186 }
187 }
188 }
189 }
190 }
191 },
192 },
193 ];
194
195 let props = Props {
196 tabs,
197 default_active: 0,
198 aria_label: "Account settings".to_string(),
199 };
200
201 render(props)
202}