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