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 children: Element,
104 #[props(optional)]
105 onclick: EventHandler<MouseEvent>,
106 #[props(optional)]
107 onmounted: EventHandler<MountedEvent>,
108}
109
110#[component]
111pub fn Button(props: ButtonProps) -> Element {
112 let mut class_list = vec!["btn".to_string()];
113
114 if props.disabled { class_list.push("btn-disabled".into()) }
115 if props.toggle && props.active { class_list.push("active".into()) }
116
117 let variant: &str = props.variant.into();
118 if variant.len() > 0 {
119 if props.outline {
120 class_list.push(format!("btn-outline-{}", variant))
121 } else {
122 class_list.push(format!("btn-{}", variant))
123 }
124 }
125
126 let size: &str = props.size.into();
127 if props.size != Size::Normal {
128 class_list.push(format!("btn-{}", size));
129 }
130
131 if !props.class.is_empty() {
133 class_list.push(props.class.clone());
134 }
135
136 if props.loading {
137 class_list.push("btn-loading".to_string());
138 }
139
140 if props.floating {
141 class_list.push("btn-floating".to_string());
142 }
143
144 let class_list = class_list.join(" ");
145
146 if props.toggle {
147 return rsx! {
148 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} }
149 }
150 }
151
152 if props.close {
154 return rsx! {
155 button {
156 id: props.id,
157 r#type: "button",
158 class: "btn-close",
159 style: props.style,
160 onclick: props.onclick,
161 disabled: props.disabled,
162 "aria-label": "Close",
163 onmounted: props.onmounted,
164 }
165 };
166 }
167
168 let button_type_str: &str = props.button_type.into();
169
170 match props.button_type {
171 ButtonType::Submit | ButtonType::Reset => rsx!{
172 input {
173 id: props.id,
174 r#type: button_type_str,
175 value: if props.button_type == ButtonType::Submit { "Submit" } else { "Reset" },
176 style: props.style,
177 onclick: props.onclick,
178 class: class_list,
179 disabled: props.disabled,
180 onmounted: props.onmounted
181 }
182 },
183 _ => match props.link_to {
184 Some(t) => rsx!{
185 Link {
186 to: t,
187 id: props.id,
188 onclick: props.onclick,
189 style: props.style,
190 class: class_list,
191 "aria-disabled": props.disabled,
192 {if let Some(text) = props.text { rsx! { "{text}" } } else { props.children }}
193 }
194 },
195 _ => rsx! {
196 button {
197 id: props.id,
198 r#type: button_type_str,
199 style: props.style,
200 onclick: props.onclick,
201 class: class_list,
202 disabled: props.disabled,
203 onmounted: props.onmounted,
204 {if let Some(text) = props.text { rsx! { "{text}" } } else { props.children }}
205 }
206 }
207 }
208 }
209
210}
211
212#[derive(Clone, Copy, PartialEq)]
213pub enum ButtonGroupOrientation {
214 Horizontal,
215 Vertical,
216}
217
218#[derive(Clone, Props, PartialEq)]
219pub struct ButtonGroupProps {
220 #[props(optional, default = "Button group".to_string())]
222 label: String,
223 #[props(optional, default = Size::Normal)]
224 size: Size,
225 #[props(optional, default = ButtonGroupOrientation::Horizontal)]
226 orientation: ButtonGroupOrientation,
227 #[props(optional, default = false)]
228 toolbar: bool,
229 children: Element,
230}
231
232#[component]
233pub fn ButtonGroup(props: ButtonGroupProps) -> Element {
234 let mut class_list = vec!["btn-group".to_string()];
235
236 if props.orientation == ButtonGroupOrientation::Vertical {
237 class_list = vec!["btn-group-vertical".to_string()];
238 }
239
240 let size: &str = props.size.into();
241 if props.size != Size::Normal {
242 class_list.push(format!("btn-group-{}", size));
243 }
244
245 if props.toolbar {
246 class_list = vec!["btn-toolbar".to_string()];
247 }
248
249 let class_list = class_list.join(" ");
250 let role = if props.toolbar { "toolbar" } else { "group" };
251
252 rsx! {
253 div {
254 class: class_list,
255 role: role,
256 "aria-label": props.label,
257 {props.children}
258 }
259 }
260}
261
262#[derive(Clone, Props, PartialEq)]
263pub struct DropdownButtonProps {
264 #[props(optional)]
265 id: String,
266 #[props(optional, default = "".to_string())]
267 class: String,
268 #[props(optional, default = ButtonVariant::Primary)]
269 variant: ButtonVariant,
270 #[props(optional, default = Size::Normal)]
271 size: Size,
272 #[props(optional, default = false)]
273 disabled: bool,
274 #[props(optional, default = false)]
275 outline: bool,
276 #[props(optional, default = false)]
277 split: bool,
278 #[props(optional, default = "Dropdown".to_string())]
279 text: String,
280 children: Element,
281}
282
283#[component]
284pub fn DropdownButton(props: DropdownButtonProps) -> Element {
285 let mut class_list = vec!["btn".to_string(), "dropdown-toggle".to_string()];
286
287 let variant: &str = props.variant.into();
288 if !variant.is_empty() {
289 if props.outline {
290 class_list.push(format!("btn-outline-{}", variant));
291 } else {
292 class_list.push(format!("btn-{}", variant));
293 }
294 }
295
296 let size: &str = props.size.into();
297 if props.size != Size::Normal {
298 class_list.push(format!("btn-{}", size));
299 }
300
301 if !props.class.is_empty() {
303 class_list.push(props.class.clone());
304 }
305
306 let class_list = class_list.join(" ");
307
308 if props.split {
309 rsx! {
310 div {
311 class: "btn-group",
312 button {
313 r#type: "button",
314 class: class_list.replace("dropdown-toggle", "").trim(),
315 disabled: props.disabled,
316 "{props.text}"
317 }
318 button {
319 r#type: "button",
320 class: format!("{} dropdown-toggle dropdown-toggle-split", class_list.replace("dropdown-toggle", "").trim()),
321 "data-bs-toggle": "dropdown",
322 "aria-expanded": "false",
323 disabled: props.disabled,
324 span {
325 class: "visually-hidden",
326 "Toggle Dropdown"
327 }
328 }
329 ul {
330 class: "dropdown-menu",
331 {props.children}
332 }
333 }
334 }
335 } else {
336 rsx! {
337 div {
338 class: "dropdown",
339 button {
340 id: props.id,
341 r#type: "button",
342 class: class_list,
343 "data-bs-toggle": "dropdown",
344 "aria-expanded": "false",
345 disabled: props.disabled,
346 "{props.text}"
347 }
348 ul {
349 class: "dropdown-menu",
350 {props.children}
351 }
352 }
353 }
354 }
355}