Skip to main content

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}