Skip to main content

dioxus_bootstrap_css/
list_group.rs

1use dioxus::prelude::*;
2
3use crate::types::Color;
4
5/// Bootstrap ListGroup component.
6///
7/// # Bootstrap HTML → Dioxus
8///
9/// ```html
10/// <!-- Bootstrap HTML -->
11/// <ul class="list-group list-group-flush">
12///   <li class="list-group-item active">Active</li>
13///   <li class="list-group-item">Normal</li>
14///   <li class="list-group-item list-group-item-danger">Danger</li>
15///   <li class="list-group-item disabled">Disabled</li>
16/// </ul>
17/// ```
18///
19/// ```rust,no_run
20/// # use dioxus::prelude::*;
21/// # use dioxus_bootstrap_css::prelude::*;
22/// # fn _doctest() -> Element {
23/// # let handler = move |_: MouseEvent| {};
24/// rsx! {
25///     ListGroup { flush: true,
26///         ListGroupItem { active: true, "Active" }
27///         ListGroupItem { "Normal" }
28///         ListGroupItem { color: Color::Danger, "Danger" }
29///         ListGroupItem { disabled: true, "Disabled" }
30///     }
31///     // Clickable list group
32///     ListGroup {
33///         ListGroupItem { onclick: handler, "Click me" }
34///     }
35///     // Numbered list
36///     ListGroup { numbered: true,
37///         ListGroupItem { "First" }
38///         ListGroupItem { "Second" }
39///     }
40/// }
41/// # }
42/// ```
43#[derive(Clone, PartialEq, Props)]
44pub struct ListGroupProps {
45    /// Remove borders and rounded corners for use inside cards.
46    #[props(default)]
47    pub flush: bool,
48    /// Use numbered list style.
49    #[props(default)]
50    pub numbered: bool,
51    /// Additional CSS classes.
52    #[props(default)]
53    pub class: String,
54    /// Any additional HTML attributes.
55    #[props(extends = GlobalAttributes)]
56    attributes: Vec<Attribute>,
57    /// Child elements (ListGroupItem components).
58    pub children: Element,
59}
60
61#[component]
62pub fn ListGroup(props: ListGroupProps) -> Element {
63    let mut classes = vec!["list-group".to_string()];
64    if props.flush {
65        classes.push("list-group-flush".to_string());
66    }
67    if props.numbered {
68        classes.push("list-group-numbered".to_string());
69    }
70    if !props.class.is_empty() {
71        classes.push(props.class.clone());
72    }
73    let full_class = classes.join(" ");
74
75    if props.numbered {
76        rsx! {
77            ol { class: "{full_class}", ..props.attributes, {props.children} }
78        }
79    } else {
80        rsx! {
81            ul { class: "{full_class}", ..props.attributes, {props.children} }
82        }
83    }
84}
85
86/// Bootstrap ListGroupItem component.
87#[derive(Clone, PartialEq, Props)]
88pub struct ListGroupItemProps {
89    /// Active state.
90    #[props(default)]
91    pub active: bool,
92    /// Disabled state.
93    #[props(default)]
94    pub disabled: bool,
95    /// Item color variant.
96    #[props(default)]
97    pub color: Option<Color>,
98    /// Click event handler. When set, renders as a button for interactivity.
99    #[props(default)]
100    pub onclick: Option<EventHandler<MouseEvent>>,
101    /// Additional CSS classes.
102    #[props(default)]
103    pub class: String,
104    /// Any additional HTML attributes.
105    #[props(extends = GlobalAttributes)]
106    attributes: Vec<Attribute>,
107    /// Child elements.
108    pub children: Element,
109}
110
111#[component]
112pub fn ListGroupItem(props: ListGroupItemProps) -> Element {
113    let mut classes = vec!["list-group-item".to_string()];
114    if props.active {
115        classes.push("active".to_string());
116    }
117    if props.disabled {
118        classes.push("disabled".to_string());
119    }
120    if let Some(ref c) = props.color {
121        classes.push(format!("list-group-item-{c}"));
122    }
123    if props.onclick.is_some() {
124        classes.push("list-group-item-action".to_string());
125    }
126    if !props.class.is_empty() {
127        classes.push(props.class.clone());
128    }
129    let full_class = classes.join(" ");
130
131    if let Some(handler) = &props.onclick {
132        let handler = *handler;
133        rsx! {
134            button {
135                class: "{full_class}",
136                r#type: "button",
137                disabled: props.disabled,
138                onclick: move |evt| handler.call(evt),
139                ..props.attributes,
140                {props.children}
141            }
142        }
143    } else {
144        rsx! {
145            li { class: "{full_class}", ..props.attributes, {props.children} }
146        }
147    }
148}