Skip to main content

fc_dioxus/components/
button_group.rs

1//! Groups of related actions: connected button groups and general control groups, from
2//! button-group.css and control-group.css.
3use crate::{
4    core::{assets, attrs::component_attributes},
5    prelude::*,
6    theme::use_stylesheets,
7};
8const BUTTONS: Bem = Bem::new("fc-button-group");
9const CONTROLS: Bem = Bem::new("fc-control-group");
10
11/// Connected buttons sharing edges, such as alignment or playback speed. Every
12/// [`Button`] inside becomes an item; give toggles `pressed`.
13#[component]
14pub fn ButtonGroup(
15    label: AccessibleName,
16    #[props(default)] class: Option<String>,
17    #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
18    children: Element,
19) -> Element {
20    super::provide_group_item(BUTTONS.element("item"), None);
21    let attributes = component_attributes(
22        attributes,
23        ClassList::new(BUTTONS.block()).extend(class.as_deref()),
24        &["role", "aria-label"],
25    );
26    rsx! {
27        if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_BUTTON_GROUP_CSS } }
28        div { role: "group", aria_label: label.to_string(), ..attributes, {children} }
29    }
30}
31
32fc_modifier! {
33    /// How a control group lays out its items.
34    pub enum ControlLayout for CONTROLS {
35        /// Items wrap in a row with gaps.
36        Wrap => None,
37        /// Items touch and share borders, e.g. a search box between two buttons.
38        Joined => "joined",
39        /// Items stack in a column.
40        Vertical => "vertical",
41        /// No frame: a status beside its action.
42        Plain => "plain",
43    }
44}
45
46/// How a control group is named.
47#[derive(Debug, Clone, PartialEq)]
48pub enum GroupName {
49    /// A `role="group"` named by `aria-label`.
50    Label(AccessibleName),
51    /// A `<fieldset>` with a visible legend, which can disable everything inside at once.
52    Legend(Element),
53}
54
55/// Related controls of any kind. [`Button`]s, [`Input`]s, and [`NativeSelect`]s inside
56/// become items; in a joined group, an `Input` also fills the remaining width.
57#[component]
58pub fn ControlGroup(
59    name: GroupName,
60    #[props(default)] layout: ControlLayout,
61    /// Disable every control at once. Only a legend-named group (a fieldset) can.
62    #[props(default)]
63    disabled: bool,
64    #[props(default)] class: Option<String>,
65    #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
66    children: Element,
67) -> Element {
68    let joined = layout == ControlLayout::Joined;
69    super::provide_group_item(
70        CONTROLS.element(if joined { "joined-item" } else { "item" }),
71        joined.then(|| CONTROLS.element("control")),
72    );
73    let attributes = component_attributes(
74        attributes,
75        ClassList::new(CONTROLS.block())
76            .with_modifier(CONTROLS, layout)
77            .extend(class.as_deref()),
78        &["role", "aria-label", "disabled"],
79    );
80    rsx! {
81        if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_CONTROL_GROUP_CSS } }
82        match name {
83            GroupName::Label(label) => rsx! {
84                div { role: "group", aria_label: label.to_string(), ..attributes, {children} }
85            },
86            GroupName::Legend(legend) => rsx! {
87                fieldset { disabled, ..attributes,
88                    legend { class: CONTROLS.element("label"), {legend} }
89                    {children}
90                }
91            },
92        }
93    }
94}
95
96/// A visible label for one control in a vertical [`ControlGroup`].
97#[component]
98pub fn ControlLabel(r#for: Id, children: Element) -> Element {
99    // Formatted explicitly: an inline capture of a raw identifier (`{r#for}`)
100    // is a hard error in the format-string parser of current nightlies, which
101    // breaks the crate for consumers on newer toolchains than the pinned one.
102    rsx! { label { class: CONTROLS.element("label"), r#for: format!("{}", r#for), {children} } }
103}