Skip to main content

dioxus_bootstrap_css/
grid.rs

1use dioxus::prelude::*;
2
3use crate::types::ColumnSize;
4
5/// Bootstrap Container component.
6///
7/// # Bootstrap HTML → Dioxus
8///
9/// | HTML | Dioxus |
10/// |---|---|
11/// | `<div class="container">` | `Container { }` |
12/// | `<div class="container-fluid">` | `Container { fluid: true }` |
13/// | `<div class="container py-4">` | `Container { class: "py-4" }` |
14///
15/// ```rust
16/// rsx! {
17///     Container { "Fixed width content" }
18///     Container { fluid: true, "Full width content" }
19/// }
20/// ```
21#[derive(Clone, PartialEq, Props)]
22pub struct ContainerProps {
23    /// Use container-fluid for full width.
24    #[props(default)]
25    pub fluid: bool,
26    /// Additional CSS classes.
27    #[props(default)]
28    pub class: String,
29    /// Any additional HTML attributes.
30    #[props(extends = GlobalAttributes)]
31    attributes: Vec<Attribute>,
32    /// Child elements.
33    pub children: Element,
34}
35
36#[component]
37pub fn Container(props: ContainerProps) -> Element {
38    let base = if props.fluid {
39        "container-fluid"
40    } else {
41        "container"
42    };
43    let full_class = if props.class.is_empty() {
44        base.to_string()
45    } else {
46        format!("{base} {}", props.class)
47    };
48
49    rsx! {
50        div { class: "{full_class}", ..props.attributes, {props.children} }
51    }
52}
53
54/// Bootstrap Row component.
55///
56/// # Bootstrap HTML → Dioxus
57///
58/// | HTML | Dioxus |
59/// |---|---|
60/// | `<div class="row">` | `Row { }` |
61/// | `<div class="row g-3">` | `Row { class: "g-3" }` |
62/// | `<div class="row align-items-center">` | `Row { class: "align-items-center" }` |
63///
64/// ```rust
65/// rsx! {
66///     Row { class: "g-3",
67///         Col { lg: ColumnSize::Span(6), "Left" }
68///         Col { lg: ColumnSize::Span(6), "Right" }
69///     }
70/// }
71/// ```
72#[derive(Clone, PartialEq, Props)]
73pub struct RowProps {
74    /// Additional CSS classes (e.g., "g-3", "align-items-center").
75    #[props(default)]
76    pub class: String,
77    /// Any additional HTML attributes.
78    #[props(extends = GlobalAttributes)]
79    attributes: Vec<Attribute>,
80    /// Child elements.
81    pub children: Element,
82}
83
84#[component]
85pub fn Row(props: RowProps) -> Element {
86    let full_class = if props.class.is_empty() {
87        "row".to_string()
88    } else {
89        format!("row {}", props.class)
90    };
91
92    rsx! {
93        div { class: "{full_class}", ..props.attributes, {props.children} }
94    }
95}
96
97/// Bootstrap Col (column) component with responsive breakpoint props.
98///
99/// # Bootstrap HTML → Dioxus
100///
101/// | HTML | Dioxus |
102/// |---|---|
103/// | `<div class="col">` | `Col { }` |
104/// | `<div class="col-lg-3">` | `Col { lg: ColumnSize::Span(3) }` |
105/// | `<div class="col-md-6 col-lg-4">` | `Col { md: ColumnSize::Span(6), lg: ColumnSize::Span(4) }` |
106/// | `<div class="col-auto">` | `Col { xs: ColumnSize::Auto }` |
107/// | `<div class="col-md-6 offset-md-3">` | `Col { md: ColumnSize::Span(6), offset_md: Some(3) }` |
108///
109/// ```rust
110/// rsx! {
111///     Col { xs: ColumnSize::Span(12), md: ColumnSize::Span(6), lg: ColumnSize::Span(4),
112///         "Responsive column"
113///     }
114///     Col { lg: ColumnSize::Auto, "Auto-width column" }
115/// }
116/// ```
117#[derive(Clone, PartialEq, Props)]
118pub struct ColProps {
119    /// Column size at xs breakpoint (default, no breakpoint prefix).
120    #[props(default)]
121    pub xs: Option<ColumnSize>,
122    /// Column size at sm breakpoint.
123    #[props(default)]
124    pub sm: Option<ColumnSize>,
125    /// Column size at md breakpoint.
126    #[props(default)]
127    pub md: Option<ColumnSize>,
128    /// Column size at lg breakpoint.
129    #[props(default)]
130    pub lg: Option<ColumnSize>,
131    /// Column size at xl breakpoint.
132    #[props(default)]
133    pub xl: Option<ColumnSize>,
134    /// Column size at xxl breakpoint.
135    #[props(default)]
136    pub xxl: Option<ColumnSize>,
137    /// Offset at xs breakpoint.
138    #[props(default)]
139    pub offset: Option<u8>,
140    /// Offset at sm breakpoint.
141    #[props(default)]
142    pub offset_sm: Option<u8>,
143    /// Offset at md breakpoint.
144    #[props(default)]
145    pub offset_md: Option<u8>,
146    /// Offset at lg breakpoint.
147    #[props(default)]
148    pub offset_lg: Option<u8>,
149    /// Offset at xl breakpoint.
150    #[props(default)]
151    pub offset_xl: Option<u8>,
152    /// Offset at xxl breakpoint.
153    #[props(default)]
154    pub offset_xxl: Option<u8>,
155    /// Column order (0-5 or "first"/"last" via class prop).
156    #[props(default)]
157    pub order: Option<u8>,
158    /// Column order at sm breakpoint.
159    #[props(default)]
160    pub order_sm: Option<u8>,
161    /// Column order at md breakpoint.
162    #[props(default)]
163    pub order_md: Option<u8>,
164    /// Column order at lg breakpoint.
165    #[props(default)]
166    pub order_lg: Option<u8>,
167    /// Additional CSS classes.
168    #[props(default)]
169    pub class: String,
170    /// Any additional HTML attributes.
171    #[props(extends = GlobalAttributes)]
172    attributes: Vec<Attribute>,
173    /// Child elements.
174    pub children: Element,
175}
176
177#[component]
178pub fn Col(props: ColProps) -> Element {
179    let mut classes = Vec::new();
180
181    if let Some(size) = &props.xs {
182        classes.push(format!("col-{size}"));
183    }
184    if let Some(size) = &props.sm {
185        classes.push(format!("col-sm-{size}"));
186    }
187    if let Some(size) = &props.md {
188        classes.push(format!("col-md-{size}"));
189    }
190    if let Some(size) = &props.lg {
191        classes.push(format!("col-lg-{size}"));
192    }
193    if let Some(size) = &props.xl {
194        classes.push(format!("col-xl-{size}"));
195    }
196    if let Some(size) = &props.xxl {
197        classes.push(format!("col-xxl-{size}"));
198    }
199
200    // Default to "col" if no breakpoints specified
201    if classes.is_empty() {
202        classes.push("col".to_string());
203    }
204
205    // Offsets
206    if let Some(n) = props.offset {
207        classes.push(format!("offset-{n}"));
208    }
209    if let Some(n) = props.offset_sm {
210        classes.push(format!("offset-sm-{n}"));
211    }
212    if let Some(n) = props.offset_md {
213        classes.push(format!("offset-md-{n}"));
214    }
215    if let Some(n) = props.offset_lg {
216        classes.push(format!("offset-lg-{n}"));
217    }
218    if let Some(n) = props.offset_xl {
219        classes.push(format!("offset-xl-{n}"));
220    }
221    if let Some(n) = props.offset_xxl {
222        classes.push(format!("offset-xxl-{n}"));
223    }
224
225    // Order
226    if let Some(n) = props.order {
227        classes.push(format!("order-{n}"));
228    }
229    if let Some(n) = props.order_sm {
230        classes.push(format!("order-sm-{n}"));
231    }
232    if let Some(n) = props.order_md {
233        classes.push(format!("order-md-{n}"));
234    }
235    if let Some(n) = props.order_lg {
236        classes.push(format!("order-lg-{n}"));
237    }
238
239    if !props.class.is_empty() {
240        classes.push(props.class.clone());
241    }
242
243    let full_class = classes.join(" ");
244
245    rsx! {
246        div { class: "{full_class}", ..props.attributes, {props.children} }
247    }
248}