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