dioxus_bootstrap_css/button.rs
1use dioxus::prelude::*;
2
3use crate::types::{Color, Size};
4
5/// Bootstrap Button component.
6///
7/// # Bootstrap HTML → Dioxus
8///
9/// | HTML | Dioxus |
10/// |---|---|
11/// | `<button class="btn btn-primary">` | `Button { color: Color::Primary, "Text" }` |
12/// | `<button class="btn btn-outline-danger btn-sm">` | `Button { color: Color::Danger, outline: true, size: Size::Sm, "Text" }` |
13/// | `<button class="btn btn-success btn-lg" disabled>` | `Button { color: Color::Success, size: Size::Lg, disabled: true, "Text" }` |
14/// | `<button class="btn btn-primary active">` | `Button { color: Color::Primary, active: true, "Text" }` |
15///
16/// ```rust,no_run
17/// rsx! {
18/// Button { color: Color::Primary, "Click me" }
19/// Button { color: Color::Danger, outline: true, size: Size::Sm, "Delete" }
20/// Button { color: Color::Success, disabled: true, "Saved" }
21/// Button { color: Color::Warning, onclick: move |_| { /* handler */ }, "Action" }
22/// }
23/// ```
24#[derive(Clone, PartialEq, Props)]
25pub struct ButtonProps {
26 /// Button color variant.
27 #[props(default)]
28 pub color: Color,
29 /// Use outline style instead of filled.
30 #[props(default)]
31 pub outline: bool,
32 /// Button size.
33 #[props(default)]
34 pub size: Size,
35 /// Whether the button is disabled.
36 #[props(default)]
37 pub disabled: bool,
38 /// HTML button type attribute.
39 #[props(default = "button".to_string())]
40 pub r#type: String,
41 /// Click event handler.
42 #[props(default)]
43 pub onclick: Option<EventHandler<MouseEvent>>,
44 /// Active (pressed) state.
45 #[props(default)]
46 pub active: bool,
47 /// Tooltip text (HTML title attribute).
48 #[props(default)]
49 pub title: String,
50 /// Additional CSS classes.
51 #[props(default)]
52 pub class: String,
53 /// Child elements.
54 pub children: Element,
55}
56
57#[component]
58pub fn Button(props: ButtonProps) -> Element {
59 let style = if props.outline { "btn-outline" } else { "btn" };
60 let color = props.color;
61 let color_class = format!("{style}-{color}");
62
63 let size_class = match props.size {
64 Size::Md => String::new(),
65 s => format!(" btn-{s}"),
66 };
67
68 let active_class = if props.active { " active" } else { "" };
69
70 let full_class = if props.class.is_empty() {
71 format!("btn {color_class}{size_class}{active_class}")
72 } else {
73 format!(
74 "btn {color_class}{size_class}{active_class} {}",
75 props.class
76 )
77 };
78
79 rsx! {
80 button {
81 class: "{full_class}",
82 r#type: "{props.r#type}",
83 disabled: props.disabled,
84 title: if !props.title.is_empty() { "{props.title}" } else { "" },
85 onclick: move |evt| {
86 if let Some(handler) = &props.onclick {
87 handler.call(evt);
88 }
89 },
90 {props.children}
91 }
92 }
93}
94
95/// Bootstrap ButtonGroup component.
96///
97/// ```rust
98/// rsx! {
99/// ButtonGroup {
100/// Button { color: Color::Primary, "Left" }
101/// Button { color: Color::Primary, "Middle" }
102/// Button { color: Color::Primary, "Right" }
103/// }
104/// }
105/// ```
106#[derive(Clone, PartialEq, Props)]
107pub struct ButtonGroupProps {
108 /// Button group size.
109 #[props(default)]
110 pub size: Size,
111 /// Additional CSS classes.
112 #[props(default)]
113 pub class: String,
114 /// Child elements (buttons).
115 pub children: Element,
116}
117
118#[component]
119pub fn ButtonGroup(props: ButtonGroupProps) -> Element {
120 let size_class = match props.size {
121 Size::Md => String::new(),
122 s => format!(" btn-group-{s}"),
123 };
124
125 let full_class = if props.class.is_empty() {
126 format!("btn-group{size_class}")
127 } else {
128 format!("btn-group{size_class} {}", props.class)
129 };
130
131 rsx! {
132 div {
133 class: "{full_class}",
134 role: "group",
135 {props.children}
136 }
137 }
138}
139
140/// Bootstrap ButtonToolbar — groups multiple ButtonGroups.
141///
142/// ```rust
143/// rsx! {
144/// ButtonToolbar {
145/// ButtonGroup {
146/// Button { color: Color::Primary, "1" }
147/// Button { color: Color::Primary, "2" }
148/// }
149/// ButtonGroup {
150/// Button { color: Color::Secondary, "A" }
151/// }
152/// }
153/// }
154/// ```
155#[derive(Clone, PartialEq, Props)]
156pub struct ButtonToolbarProps {
157 /// Additional CSS classes.
158 #[props(default)]
159 pub class: String,
160 /// Child elements (ButtonGroups).
161 pub children: Element,
162}
163
164#[component]
165pub fn ButtonToolbar(props: ButtonToolbarProps) -> Element {
166 let full_class = if props.class.is_empty() {
167 "btn-toolbar".to_string()
168 } else {
169 format!("btn-toolbar {}", props.class)
170 };
171
172 rsx! {
173 div {
174 class: "{full_class}",
175 role: "toolbar",
176 {props.children}
177 }
178 }
179}