ironhtml_bootstrap/
lib.rs

1//! # ironhtml-bootstrap
2//!
3//! Type-safe Bootstrap 5.3 components for Rust.
4//!
5//! This crate provides ergonomic, type-safe Bootstrap component generation
6//! that integrates seamlessly with the `ironhtml` ecosystem. Build
7//! Bootstrap UIs with Rust's compile-time guarantees.
8//!
9//! ## Examples
10//!
11//! Complete examples demonstrating the library's capabilities:
12//!
13//! - **[Landing Page](https://github.com/LeakIX/ironhtml/blob/main/crates/ironhtml-bootstrap/examples/landing_page.rs)** -
14//!   A complete `SaaS` landing page with navbar, hero section, features, testimonials, and footer.
15//!
16//! - **[Wallet Dashboard](https://github.com/LeakIX/ironhtml/blob/main/crates/ironhtml-bootstrap/examples/wallet_dashboard.rs)** -
17//!   Dynamic page generation based on Rust parameters, showing conditional rendering.
18//!
19//! - **[Bootstrap Docs](https://github.com/LeakIX/ironhtml/blob/main/crates/ironhtml-bootstrap/examples/bootstrap_docs.rs)** -
20//!   A documentation site replica showcasing all Bootstrap components.
21//!
22//! Run examples with: `cargo run --example landing_page`
23//!
24//! ## Available Components
25//!
26//! | Module | Description |
27//! |--------|-------------|
28//! | [`accordion`] | Collapsible content panels |
29//! | [`alerts`] | Contextual feedback messages |
30//! | [`badge`] | Small count and labeling component |
31//! | [`breadcrumb`] | Navigation hierarchy indicator |
32//! | [`buttons`] | Button styles and variants |
33//! | [`cards`] | Flexible content containers |
34//! | [`carousel`] | Slideshow component |
35//! | [`close_button`] | Dismissal buttons |
36//! | [`collapse`] | Collapsible content |
37//! | [`dropdown`] | Dropdown menus |
38//! | [`grid`] | Bootstrap grid system |
39//! | [`list_group`] | Flexible list display |
40//! | [`modal`] | Dialog overlays |
41//! | [`navbar`] | Responsive navigation |
42//! | [`offcanvas`] | Sidebar panels |
43//! | [`pagination`] | Page navigation |
44//! | [`placeholder`] | Loading placeholders |
45//! | [`progress`] | Progress bars |
46//! | [`spinner`] | Loading indicators |
47//! | [`toast`] | Push notifications |
48//! | [`tooltip`] | Tooltips and popovers |
49//!
50//! ## Quick Start
51//!
52//! ```rust
53//! use ironhtml_bootstrap::*;
54//!
55//! // Create a simple button
56//! let button = buttons::btn(Color::Primary, "Click me");
57//!
58//! // Create an alert
59//! let warning = alerts::alert(Color::Warning, "This is a warning!");
60//!
61//! // Create a card
62//! let card = cards::card_simple(
63//!     "Welcome",
64//!     "Thanks for using ironhtml-bootstrap!",
65//!     "Learn more",
66//!     "#docs"
67//! );
68//! ```
69//!
70//! ## Reusable Components (React-like Pattern)
71//!
72//! Define custom components as functions, just like React components.
73//! This is the recommended pattern for building maintainable UIs.
74//!
75//! ```rust
76//! use ironhtml_bootstrap::*;
77//! use ironhtml::typed::Element;
78//! use ironhtml_elements::{Button, Div, A};
79//!
80//! // ============================================================
81//! // REUSABLE COMPONENT: Product Card
82//! // ============================================================
83//! // Define once, use everywhere. Just like React components!
84//!
85//! struct Product {
86//!     name: String,
87//!     description: String,
88//!     price: f64,
89//!     image_url: String,
90//! }
91//!
92//! fn product_card(product: &Product) -> Element<Div> {
93//!     cards::card_with_image(
94//!         &product.image_url,
95//!         &product.name,
96//!         &product.name,
97//!         &product.description,
98//!     )
99//! }
100//!
101//! // ============================================================
102//! // REUSABLE COMPONENT: Pricing Tier
103//! // ============================================================
104//!
105//! struct PricingTier {
106//!     name: &'static str,
107//!     price: &'static str,
108//!     features: Vec<&'static str>,
109//!     highlighted: bool,
110//! }
111//!
112//! fn pricing_card(tier: &PricingTier) -> Element<Div> {
113//!     let color = if tier.highlighted { Color::Primary } else { Color::Light };
114//!
115//!     cards::card_colored(color, |body| {
116//!         use ironhtml_elements::{H3, H4, Ul, Li, P};
117//!
118//!         body.child::<H3, _>(|h| h.class("card-title").text(tier.name))
119//!             .child::<H4, _>(|h| h.text(tier.price))
120//!             .child::<Ul, _>(|ul| {
121//!                 tier.features.iter().fold(ul.class("list-unstyled"), |ul, feature| {
122//!                     ul.child::<Li, _>(|li| li.text(*feature))
123//!                 })
124//!             })
125//!             .child::<Button, _>(|_| {
126//!                 buttons::btn(Color::Primary, "Choose Plan")
127//!             })
128//!     })
129//! }
130//!
131//! // ============================================================
132//! // USAGE: Compose components into a page
133//! // ============================================================
134//!
135//! fn render_product_catalog(products: &[Product]) -> Element<Div> {
136//!     grid::container(|c| {
137//!         c.child::<Div, _>(|_| {
138//!             grid::row(|r| {
139//!                 products.iter().fold(r, |row, product| {
140//!                     row.child::<Div, _>(|_| {
141//!                         grid::col(4, |col| {
142//!                             col.child::<Div, _>(|_| product_card(product))
143//!                         })
144//!                     })
145//!                 })
146//!             })
147//!         })
148//!     })
149//! }
150//! ```
151//!
152//! ## Building a Complete Page
153//!
154//! Here's how to build a complete landing page with navbar, hero section,
155//! features, and footer:
156//!
157//! ```rust
158//! use ironhtml_bootstrap::*;
159//! use ironhtml::typed::{Document, Element};
160//! use ironhtml_elements::*;
161//!
162//! // ============================================================
163//! // COMPONENT: Navigation Menu
164//! // ============================================================
165//!
166//! struct MenuItem {
167//!     href: &'static str,
168//!     label: &'static str,
169//!     active: bool,
170//! }
171//!
172//! fn main_navbar(brand: &str, items: &[MenuItem]) -> Element<Nav> {
173//!     navbar::navbar(brand, NavbarExpand::Lg, "mainNav", |ul| {
174//!         items.iter().fold(ul, |ul, item| {
175//!             ul.child::<Li, _>(|_| navbar::nav_item(item.href, item.label, item.active))
176//!         })
177//!     })
178//! }
179//!
180//! // ============================================================
181//! // COMPONENT: Hero Section
182//! // ============================================================
183//!
184//! fn hero_section(title: &str, subtitle: &str, cta_text: &str, cta_href: &str) -> Element<Div> {
185//!     Element::<Div>::new()
186//!         .class("bg-primary text-white py-5")
187//!         .child::<Div, _>(|_| {
188//!             grid::container(|c| {
189//!                 c.class("text-center")
190//!                     .child::<H1, _>(|h| h.class("display-4").text(title))
191//!                     .child::<P, _>(|p| p.class("lead").text(subtitle))
192//!                     .child::<A, _>(|a| {
193//!                         a.class("btn btn-light btn-lg")
194//!                             .attr("href", cta_href)
195//!                             .text(cta_text)
196//!                     })
197//!             })
198//!         })
199//! }
200//!
201//! // ============================================================
202//! // COMPONENT: Feature Card
203//! // ============================================================
204//!
205//! struct Feature {
206//!     icon: &'static str,  // Bootstrap icon class
207//!     title: &'static str,
208//!     description: &'static str,
209//! }
210//!
211//! fn feature_card(feature: &Feature) -> Element<Div> {
212//!     cards::card(|body| {
213//!         body.class("text-center")
214//!             .child::<I, _>(|i| i.class(feature.icon).class("fs-1 text-primary mb-3"))
215//!             .child::<H5, _>(|h| h.class("card-title").text(feature.title))
216//!             .child::<P, _>(|p| p.class("card-text text-muted").text(feature.description))
217//!     })
218//! }
219//!
220//! fn features_section(title: &str, features: &[Feature]) -> Element<Div> {
221//!     Element::<Div>::new()
222//!         .class("py-5")
223//!         .child::<Div, _>(|_| {
224//!             grid::container(|c| {
225//!                 c.child::<H2, _>(|h| h.class("text-center mb-5").text(title))
226//!                     .child::<Div, _>(|_| {
227//!                         grid::row_gutter(4, |r| {
228//!                             features.iter().fold(r, |row, feature| {
229//!                                 row.child::<Div, _>(|_| {
230//!                                     grid::col(4, |col| {
231//!                                         col.child::<Div, _>(|_| feature_card(feature))
232//!                                     })
233//!                                 })
234//!                             })
235//!                         })
236//!                     })
237//!             })
238//!         })
239//! }
240//!
241//! // ============================================================
242//! // COMPONENT: Footer
243//! // ============================================================
244//!
245//! fn footer(copyright: &str) -> Element<Footer> {
246//!     Element::<Footer>::new()
247//!         .class("bg-dark text-white py-4 mt-5")
248//!         .child::<Div, _>(|_| {
249//!             grid::container(|c| {
250//!                 c.class("text-center")
251//!                     .child::<P, _>(|p| p.class("mb-0").text(copyright))
252//!             })
253//!         })
254//! }
255//!
256//! // ============================================================
257//! // COMPOSE: Full Landing Page
258//! // ============================================================
259//!
260//! fn landing_page() -> Document {
261//!     let menu_items = vec![
262//!         MenuItem { href: "/", label: "Home", active: true },
263//!         MenuItem { href: "/features", label: "Features", active: false },
264//!         MenuItem { href: "/pricing", label: "Pricing", active: false },
265//!         MenuItem { href: "/contact", label: "Contact", active: false },
266//!     ];
267//!
268//!     let features = vec![
269//!         Feature { icon: "bi bi-lightning", title: "Fast", description: "Blazing fast performance" },
270//!         Feature { icon: "bi bi-shield", title: "Secure", description: "Enterprise-grade security" },
271//!         Feature { icon: "bi bi-gear", title: "Flexible", description: "Highly customizable" },
272//!     ];
273//!
274//!     Document::new()
275//!         .doctype()
276//!         .root::<Html, _>(|html| {
277//!             html.attr("lang", "en")
278//!                 .child::<Head, _>(|h| {
279//!                     h.child::<Meta, _>(|m| m.attr("charset", "UTF-8"))
280//!                      .child::<Title, _>(|t| t.text("My App"))
281//!                      .child::<Link, _>(|l| {
282//!                          l.attr("href", "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css")
283//!                           .attr("rel", "stylesheet")
284//!                      })
285//!                 })
286//!                 .child::<Body, _>(|body| {
287//!                     body.child::<Nav, _>(|_| main_navbar("MyApp", &menu_items))
288//!                         .child::<Div, _>(|_| hero_section(
289//!                             "Build Amazing Apps",
290//!                             "The fastest way to create beautiful web applications",
291//!                             "Get Started",
292//!                             "#signup"
293//!                         ))
294//!                         .child::<Div, _>(|_| features_section("Features", &features))
295//!                         .child::<Footer, _>(|_| footer("© 2024 MyApp. All rights reserved."))
296//!                 })
297//!         })
298//! }
299//! ```
300//!
301//! ## Dashboard Example
302//!
303//! ```rust
304//! use ironhtml_bootstrap::*;
305//! use ironhtml::typed::Element;
306//! use ironhtml_elements::*;
307//!
308//! // ============================================================
309//! // COMPONENT: Stat Card (reusable)
310//! // ============================================================
311//!
312//! fn stat_card(title: &str, value: &str, color: Color, trend: &str) -> Element<Div> {
313//!     cards::card_border(color, |body| {
314//!         body.child::<H6, _>(|h| h.class("text-muted").text(title))
315//!             .child::<H2, _>(|h| h.class("mb-0").text(value))
316//!             .child::<Small, _>(|s| s.class("text-success").text(trend))
317//!     })
318//! }
319//!
320//! // ============================================================
321//! // COMPONENT: Activity Item (reusable)
322//! // ============================================================
323//!
324//! struct Activity {
325//!     user: String,
326//!     action: String,
327//!     time: String,
328//! }
329//!
330//! fn activity_item(activity: &Activity) -> Element<Li> {
331//!     Element::<Li>::new()
332//!         .class("list-group-item d-flex justify-content-between")
333//!         .child::<Div, _>(|d| {
334//!             d.child::<Strong, _>(|s| s.text(&activity.user))
335//!                 .text(" ")
336//!                 .child::<Span, _>(|s| s.text(&activity.action))
337//!         })
338//!         .child::<Small, _>(|s| s.class("text-muted").text(&activity.time))
339//! }
340//!
341//! // ============================================================
342//! // COMPOSE: Dashboard Page
343//! // ============================================================
344//!
345//! fn dashboard() -> Element<Div> {
346//!     let activities = vec![
347//!         Activity { user: "John".into(), action: "created a new project".into(), time: "5m ago".into() },
348//!         Activity { user: "Jane".into(), action: "pushed 3 commits".into(), time: "10m ago".into() },
349//!         Activity { user: "Bob".into(), action: "deployed to production".into(), time: "1h ago".into() },
350//!     ];
351//!
352//!     grid::container(|c| {
353//!         c.class("py-4")
354//!             // Stats row
355//!             .child::<Div, _>(|_| {
356//!                 grid::row_gutter(4, |r| {
357//!                     r.child::<Div, _>(|_| grid::col(3, |c| c.child::<Div, _>(|_| stat_card("Users", "1,234", Color::Primary, "+12%"))))
358//!                      .child::<Div, _>(|_| grid::col(3, |c| c.child::<Div, _>(|_| stat_card("Revenue", "$45K", Color::Success, "+8%"))))
359//!                      .child::<Div, _>(|_| grid::col(3, |c| c.child::<Div, _>(|_| stat_card("Orders", "567", Color::Info, "+23%"))))
360//!                      .child::<Div, _>(|_| grid::col(3, |c| c.child::<Div, _>(|_| stat_card("Tickets", "12", Color::Warning, "-5%"))))
361//!                 })
362//!             })
363//!             // Alert
364//!             .child::<Div, _>(|_| {
365//!                 alerts::alert_dismissible(Color::Info, "Welcome to your dashboard!")
366//!             })
367//!             // Activity section
368//!             .child::<Div, _>(|_| {
369//!                 grid::row(|r| {
370//!                     r.child::<Div, _>(|_| {
371//!                         grid::col(8, |c| {
372//!                             c.child::<Div, _>(|_| {
373//!                                 cards::card(|body| {
374//!                                     body.child::<H5, _>(|h| h.text("Recent Activity"))
375//!                                         .child::<Ul, _>(|ul| {
376//!                                             activities.iter().fold(ul.class("list-group list-group-flush"), |ul, act| {
377//!                                                 ul.child::<Li, _>(|_| activity_item(act))
378//!                                             })
379//!                                         })
380//!                                 })
381//!                             })
382//!                         })
383//!                     })
384//!                     .child::<Div, _>(|_| {
385//!                         grid::col(4, |c| {
386//!                             c.child::<Div, _>(|_| {
387//!                                 cards::card(|body| {
388//!                                     body.child::<H5, _>(|h| h.text("Quick Actions"))
389//!                                         .child::<Div, _>(|d| {
390//!                                             d.class("d-grid gap-2")
391//!                                                 .child::<Button, _>(|_| buttons::btn(Color::Primary, "New Project"))
392//!                                                 .child::<Button, _>(|_| buttons::btn_outline(Color::Secondary, "View Reports"))
393//!                                                 .child::<Button, _>(|_| buttons::btn_outline(Color::Success, "Export Data"))
394//!                                         })
395//!                                 })
396//!                             })
397//!                         })
398//!                     })
399//!                 })
400//!             })
401//!     })
402//! }
403//! ```
404//!
405//! ## Features
406//!
407//! - **Type-safe**: All components are strongly typed
408//! - **Composable**: Build complex UIs from simple components
409//! - **Zero runtime overhead**: All abstractions compile away
410//! - **Bootstrap 5.3**: Full support for the latest Bootstrap
411//! - **No JavaScript required**: Pure HTML output (add Bootstrap JS separately if needed)
412//!
413//! ## Feature Flags
414//!
415//! - `v5` (default): Bootstrap 5.3 support
416
417#![no_std]
418
419extern crate alloc;
420
421pub mod accordion;
422pub mod alerts;
423pub mod badge;
424pub mod breadcrumb;
425pub mod buttons;
426pub mod cards;
427pub mod carousel;
428pub mod close_button;
429pub mod collapse;
430pub mod dropdown;
431pub mod grid;
432pub mod list_group;
433pub mod modal;
434pub mod navbar;
435pub mod offcanvas;
436pub mod pagination;
437pub mod placeholder;
438pub mod progress;
439pub mod spinner;
440pub mod toast;
441pub mod tooltip;
442mod types;
443
444pub use types::{Breakpoint, Color, NavbarExpand, Size};
445
446// Re-export ironhtml and ironhtml_elements for convenience
447pub use ironhtml;
448pub use ironhtml_elements;
449pub use ironhtml_macro;
450
451#[cfg(test)]
452mod tests {
453    use super::{Breakpoint, Color, NavbarExpand, Size};
454
455    #[test]
456    fn test_reexports() {
457        // Ensure types are accessible
458        let _ = Color::Primary;
459        let _ = Size::Large;
460        let _ = Breakpoint::Md;
461        let _ = NavbarExpand::Lg;
462    }
463}