1use dioxus::prelude::*;
2use super::size::*;
3use dioxus_router::navigation::NavigationTarget;
4use dioxus_router::components::Link;
5
6#[derive(Clone, Copy, PartialEq)]
7pub enum ButtonVariant {
8 Basic,
9 Primary,
10 Secondary,
11 Success,
12 Danger,
13 Warning,
14 Info,
15 Light,
16 Dark,
17 Link,
18}
19
20impl Into<&'static str> for ButtonVariant {
21 fn into(self) -> &'static str {
22 match self {
23 ButtonVariant::Primary => "primary",
24 ButtonVariant::Secondary => "secondary",
25 ButtonVariant::Success => "success",
26 ButtonVariant::Danger => "danger",
27 ButtonVariant::Warning => "warning",
28 ButtonVariant::Info => "info",
29 ButtonVariant::Light => "light",
30 ButtonVariant::Dark => "dark",
31 ButtonVariant::Link => "link",
32 _ => "",
33 }
34 }
35}
36
37#[derive(Clone, Copy, Default, PartialEq)]
38pub enum ButtonType {
39 #[default]
40 Button,
41 Reset,
42 Submit,
43}
44
45impl Into<&'static str> for ButtonType {
46 fn into(self) -> &'static str {
47 match self {
48 ButtonType::Button => "button",
49 ButtonType::Reset => "reset",
50 ButtonType::Submit => "submit",
51 }
52 }
53}
54
55#[derive(Clone, Props, PartialEq)]
56pub struct ButtonProps {
57 #[props(optional)]
58 id: String,
59 #[props(optional, default = ButtonVariant::Basic)]
60 variant: ButtonVariant,
61 #[props(optional, default = Size::Normal)]
62 size: Size,
63 #[props(optional, default = false)]
64 disabled: bool,
65 #[props(optional, default = false)]
66 outline: bool,
67 #[props(optional, default = false)]
68 nowrap: bool,
69 #[props(optional, default = false)]
70 toggle: bool,
71 #[props(optional, default = false)]
73 active: bool,
74 #[props(optional, default = "".to_string())]
75 style: String,
76 #[props(optional, default = ButtonType::Button)]
77 button_type: ButtonType,
78
79 #[props(optional, default = "".to_string())]
81 class: String,
82
83 #[props(optional, default = None)]
85 text: Option<String>,
86
87 #[props(optional, default = false)]
89 loading: bool,
90
91 #[props(optional, default = false)]
93 close: bool,
94
95 #[props(optional, default = false)]
97 floating: bool,
98
99 #[props(optional, default = None)]
101 link_to: Option<NavigationTarget>,
102
103 #[props(optional)]
104 children: Element,
105 #[props(optional)]
106 onclick: EventHandler<MouseEvent>,
107 #[props(optional)]
108 onmounted: EventHandler<MountedEvent>,
109}
110
111#[component]
112pub fn Button(props: ButtonProps) -> Element {
113 let mut class_list = vec!["btn".to_string()];
114
115 if props.disabled { class_list.push("btn-disabled".into()) }
116 if props.toggle && props.active { class_list.push("active".into()) }
117
118 let variant: &str = props.variant.into();
119 if variant.len() > 0 {
120 if props.outline {
121 class_list.push(format!("btn-outline-{}", variant))
122 } else {
123 class_list.push(format!("btn-{}", variant))
124 }
125 }
126
127 let size: &str = props.size.into();
128 if props.size != Size::Normal {
129 class_list.push(format!("btn-{}", size));
130 }
131
132 if !props.class.is_empty() {
134 class_list.push(props.class.clone());
135 }
136
137 if props.loading {
138 class_list.push("btn-loading".to_string());
139 }
140
141 if props.floating {
142 class_list.push("btn-floating".to_string());
143 }
144
145 let class_list = class_list.join(" ");
146
147 if props.toggle {
148 return rsx! {
149 button { id: props.id, r#type: "button", style: props.style, onclick: props.onclick, class: class_list, "data-bs-toggle": "button", "aria-pressed": true, onmounted: props.onmounted, {props.children} }
150 }
151 }
152
153 if props.close {
155 return rsx! {
156 button {
157 id: props.id,
158 r#type: "button",
159 class: "btn-close",
160 style: props.style,
161 onclick: props.onclick,
162 disabled: props.disabled,
163 "aria-label": "Close",
164 onmounted: props.onmounted,
165 }
166 };
167 }
168
169 let button_type_str: &str = props.button_type.into();
170
171 match props.button_type {
172 ButtonType::Submit | ButtonType::Reset => rsx!{
173 input {
174 id: props.id,
175 r#type: button_type_str,
176 value: if props.button_type == ButtonType::Submit { "Submit" } else { "Reset" },
177 style: props.style,
178 onclick: props.onclick,
179 class: class_list,
180 disabled: props.disabled,
181 onmounted: props.onmounted
182 }
183 },
184 _ => match props.link_to {
185 Some(t) => rsx!{
186 Link {
187 to: t,
188 id: props.id,
189 onclick: props.onclick,
190 style: props.style,
191 class: class_list,
192 "aria-disabled": props.disabled,
193 {if let Some(text) = props.text { rsx! { "{text}" } } else { props.children }}
194 }
195 },
196 _ => rsx! {
197 button {
198 id: props.id,
199 r#type: button_type_str,
200 style: props.style,
201 onclick: props.onclick,
202 class: class_list,
203 disabled: props.disabled,
204 onmounted: props.onmounted,
205 {if let Some(text) = props.text { rsx! { "{text}" } } else { props.children }}
206 }
207 }
208 }
209 }
210
211}
212
213#[derive(Clone, Copy, PartialEq)]
214pub enum ButtonGroupOrientation {
215 Horizontal,
216 Vertical,
217}
218
219#[derive(Clone, Props, PartialEq)]
220pub struct ButtonGroupProps {
221 #[props(optional, default = "Button group".to_string())]
223 label: String,
224 #[props(optional, default = Size::Normal)]
225 size: Size,
226 #[props(optional, default = ButtonGroupOrientation::Horizontal)]
227 orientation: ButtonGroupOrientation,
228 #[props(optional, default = false)]
229 toolbar: bool,
230 children: Element,
231}
232
233#[component]
234pub fn ButtonGroup(props: ButtonGroupProps) -> Element {
235 let mut class_list = vec!["btn-group".to_string()];
236
237 if props.orientation == ButtonGroupOrientation::Vertical {
238 class_list = vec!["btn-group-vertical".to_string()];
239 }
240
241 let size: &str = props.size.into();
242 if props.size != Size::Normal {
243 class_list.push(format!("btn-group-{}", size));
244 }
245
246 if props.toolbar {
247 class_list = vec!["btn-toolbar".to_string()];
248 }
249
250 let class_list = class_list.join(" ");
251 let role = if props.toolbar { "toolbar" } else { "group" };
252
253 rsx! {
254 div {
255 class: class_list,
256 role: role,
257 "aria-label": props.label,
258 {props.children}
259 }
260 }
261}
262
263#[derive(Clone, Props, PartialEq)]
264pub struct DropdownButtonProps {
265 #[props(optional)]
266 id: String,
267 #[props(optional, default = "".to_string())]
268 class: String,
269 #[props(optional, default = ButtonVariant::Primary)]
270 variant: ButtonVariant,
271 #[props(optional, default = Size::Normal)]
272 size: Size,
273 #[props(optional, default = false)]
274 disabled: bool,
275 #[props(optional, default = false)]
276 outline: bool,
277 #[props(optional, default = false)]
278 split: bool,
279 #[props(optional, default = "Dropdown".to_string())]
280 text: String,
281 children: Element,
282}
283
284#[component]
285pub fn DropdownButton(props: DropdownButtonProps) -> Element {
286 let mut class_list = vec!["btn".to_string(), "dropdown-toggle".to_string()];
287
288 let variant: &str = props.variant.into();
289 if !variant.is_empty() {
290 if props.outline {
291 class_list.push(format!("btn-outline-{}", variant));
292 } else {
293 class_list.push(format!("btn-{}", variant));
294 }
295 }
296
297 let size: &str = props.size.into();
298 if props.size != Size::Normal {
299 class_list.push(format!("btn-{}", size));
300 }
301
302 if !props.class.is_empty() {
304 class_list.push(props.class.clone());
305 }
306
307 let class_list = class_list.join(" ");
308
309 if props.split {
310 rsx! {
311 div {
312 class: "btn-group",
313 button {
314 r#type: "button",
315 class: class_list.replace("dropdown-toggle", "").trim(),
316 disabled: props.disabled,
317 "{props.text}"
318 }
319 button {
320 r#type: "button",
321 class: format!("{} dropdown-toggle dropdown-toggle-split", class_list.replace("dropdown-toggle", "").trim()),
322 "data-bs-toggle": "dropdown",
323 "aria-expanded": "false",
324 disabled: props.disabled,
325 span {
326 class: "visually-hidden",
327 "Toggle Dropdown"
328 }
329 }
330 ul {
331 class: "dropdown-menu",
332 {props.children}
333 }
334 }
335 }
336 } else {
337 rsx! {
338 div {
339 class: "dropdown",
340 button {
341 id: props.id,
342 r#type: "button",
343 class: class_list,
344 "data-bs-toggle": "dropdown",
345 "aria-expanded": "false",
346 disabled: props.disabled,
347 "{props.text}"
348 }
349 ul {
350 class: "dropdown-menu",
351 {props.children}
352 }
353 }
354 }
355 }
356}