pub fn row_gutter<F>(gutter: u8, f: F) -> Element<Div>Expand description
Create a row with custom gutter.
Generates: <div class="row g-{gutter}">...</div>
Examples found in repository?
examples/landing_page.rs (lines 125-131)
113fn features_section(title: &str, subtitle: &str, features: &[Feature]) -> Element<Section> {
114 Element::<Section>::new()
115 .class("py-5")
116 .id("features")
117 .child::<Div, _>(|_| {
118 grid::container(|c| {
119 c.child::<Div, _>(|d| {
120 d.class("text-center mb-5")
121 .child::<H2, _>(|h| h.class("fw-bold").text(title))
122 .child::<P, _>(|p| p.class("text-muted").text(subtitle))
123 })
124 .child::<Div, _>(|_| {
125 grid::row_gutter(4, |r| {
126 features.iter().fold(r, |row, feature| {
127 row.child::<Div, _>(|_| {
128 grid::col(4, |col| col.child::<Div, _>(|_| feature_card(feature)))
129 })
130 })
131 })
132 })
133 })
134 })
135}
136
137/// Pricing card - reusable component
138fn pricing_card(tier: &PricingTier) -> Element<Div> {
139 let card_class = if tier.highlighted {
140 "card h-100 border-primary shadow"
141 } else {
142 "card h-100 shadow-sm"
143 };
144
145 Element::<Div>::new()
146 .class(card_class)
147 .child::<Div, _>(|body| {
148 let body = body.class("card-body d-flex flex-column");
149 let body = if tier.highlighted {
150 body.child::<Span, _>(|s| {
151 s.class("badge bg-primary text-white position-absolute")
152 .attr("style", "top: -10px; right: 10px;")
153 .text("Popular")
154 })
155 } else {
156 body
157 };
158
159 body.child::<H4, _>(|h| h.class("card-title text-center").text(tier.name))
160 .child::<Div, _>(|d| {
161 d.class("text-center mb-4")
162 .child::<Span, _>(|s| s.class("display-4 fw-bold").text(tier.price))
163 .child::<Span, _>(|s| s.class("text-muted").text(tier.period))
164 })
165 .child::<Ul, _>(|ul| {
166 tier.features
167 .iter()
168 .fold(ul.class("list-unstyled mb-4"), |ul, feature| {
169 ul.child::<Li, _>(|li| {
170 li.class("mb-2")
171 .child::<I, _>(|i| {
172 i.class("bi bi-check-circle-fill text-success me-2")
173 })
174 .text(*feature)
175 })
176 })
177 })
178 .child::<Div, _>(|d| {
179 d.class("mt-auto").child::<A, _>(|a| {
180 let class = alloc::format!(
181 "btn btn-{} w-100 py-2",
182 if tier.highlighted {
183 "primary"
184 } else {
185 "outline-primary"
186 }
187 );
188 a.class(&class).attr("href", "#signup").text(tier.cta)
189 })
190 })
191 })
192}
193
194/// Pricing section
195fn pricing_section(title: &str, tiers: &[PricingTier]) -> Element<Section> {
196 Element::<Section>::new()
197 .class("py-5 bg-light")
198 .id("pricing")
199 .child::<Div, _>(|_| {
200 grid::container(|c| {
201 c.child::<H2, _>(|h| h.class("text-center fw-bold mb-5").text(title))
202 .child::<Div, _>(|_| {
203 grid::row_gutter(4, |r| {
204 tiers.iter().fold(r, |row, tier| {
205 row.child::<Div, _>(|_| {
206 grid::col(4, |col| col.child::<Div, _>(|_| pricing_card(tier)))
207 })
208 })
209 })
210 })
211 })
212 })
213}
214
215/// Testimonial card - reusable component
216fn testimonial_card(testimonial: &Testimonial) -> Element<Div> {
217 cards::card(|body| {
218 body.class("h-100 border-0 shadow-sm")
219 .child::<Blockquote, _>(|bq| {
220 bq.class("blockquote mb-4")
221 .child::<P, _>(|p| {
222 p.child::<I, _>(|i| i.class("bi bi-quote text-primary me-2"))
223 .text(testimonial.quote)
224 })
225 })
226 .child::<Div, _>(|d| {
227 d.class("d-flex align-items-center")
228 .child::<Div, _>(|avatar| {
229 avatar
230 .class("rounded-circle bg-primary text-white d-flex align-items-center justify-content-center me-3")
231 .attr("style", "width: 48px; height: 48px;")
232 .text(testimonial.author.chars().next().unwrap().to_string())
233 })
234 .child::<Div, _>(|info| {
235 info.child::<Strong, _>(|s| s.text(testimonial.author))
236 .child::<Br, _>(|br| br)
237 .child::<Small, _>(|s| s.class("text-muted").text(testimonial.role))
238 })
239 })
240 })
241}
242
243/// Testimonials section
244fn testimonials_section(testimonials: &[Testimonial]) -> Element<Section> {
245 Element::<Section>::new()
246 .class("py-5")
247 .id("testimonials")
248 .child::<Div, _>(|_| {
249 grid::container(|c| {
250 c.child::<H2, _>(|h| {
251 h.class("text-center fw-bold mb-5")
252 .text("What Our Customers Say")
253 })
254 .child::<Div, _>(|_| {
255 grid::row_gutter(4, |r| {
256 testimonials.iter().fold(r, |row, t| {
257 row.child::<Div, _>(|_| {
258 grid::col(4, |col| col.child::<Div, _>(|_| testimonial_card(t)))
259 })
260 })
261 })
262 })
263 })
264 })
265}More examples
examples/wallet_dashboard.rs (lines 408-436)
364fn generate_wallet_page(state: &WalletState) -> Document {
365 let can_send = state.balance.available > 0.0 && !state.is_syncing;
366 let can_receive = !state.is_syncing;
367
368 Document::new()
369 .doctype()
370 .root::<Html, _>(|html| {
371 let mut html = html.attr("lang", "en");
372 if matches!(state.config.theme, Theme::Dark) {
373 html = html.attr("data-bs-theme", "dark");
374 }
375
376 html.child::<Head, _>(|head| {
377 head.child::<Meta, _>(|m| m.attr("charset", "UTF-8"))
378 .child::<Meta, _>(|m| {
379 m.attr("name", "viewport")
380 .attr("content", "width=device-width, initial-scale=1")
381 })
382 .child::<Title, _>(|t| {
383 let title = format!("{} Wallet", state.config.name);
384 t.text(&title)
385 })
386 .child::<Link, _>(|l| {
387 l.attr("href", "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css")
388 .attr("rel", "stylesheet")
389 })
390 .child::<Link, _>(|l| {
391 l.attr("href", "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css")
392 .attr("rel", "stylesheet")
393 })
394 })
395 .child::<Body, _>(|body| {
396 let body_class = match state.config.theme {
397 Theme::Light => "bg-light",
398 Theme::Dark => "bg-dark",
399 };
400
401 body.class(body_class)
402 .child::<Nav, _>(|_| wallet_navbar(state))
403 // Main content
404 .child::<Main, _>(|main| {
405 main.child::<Div, _>(|_| {
406 grid::container(|c| {
407 c.child::<Div, _>(|_| {
408 grid::row_gutter(4, |r| {
409 // Left column: Balance and actions
410 r.child::<Div, _>(|_| {
411 grid::col_bp(Breakpoint::Md, 4, |col| {
412 col.child::<Div, _>(|_| balance_card(&state.balance, &state.config.currency_symbol, state.has_pending_tx))
413 .child::<Div, _>(|d| {
414 d.class("mt-4")
415 .child::<Div, _>(|_| action_buttons(can_send, can_receive))
416 })
417 .child::<Div, _>(|d| {
418 d.class("mt-4")
419 .child::<Label, _>(|l| l.class("form-label small text-muted").text("Your Address"))
420 .child::<Div, _>(|_| address_display(&state.address))
421 })
422 // Conditional: Show testnet warning
423 .when(state.config.network == Network::Testnet, |col| {
424 col.child::<Div, _>(|_| {
425 alerts::alert(Color::Warning, "You are on testnet. Coins have no real value.")
426 })
427 })
428 })
429 })
430 // Right column: Transactions
431 .child::<Div, _>(|_| {
432 grid::col_bp(Breakpoint::Md, 8, |col| {
433 col.child::<Div, _>(|_| transaction_list(&state.transactions, &state.config.currency_symbol))
434 })
435 })
436 })
437 })
438 })
439 })
440 })
441 // Footer
442 .child::<Footer, _>(|f| {
443 f.class("py-3 mt-4")
444 .child::<Div, _>(|_| {
445 grid::container(|c| {
446 c.class("text-center text-muted small")
447 .child::<P, _>(|p| {
448 let version = format!("{} Wallet v1.0.0", state.config.name);
449 p.class("mb-0").text(&version)
450 })
451 })
452 })
453 })
454 // Bootstrap JS
455 .child::<Script, _>(|s| {
456 s.attr("src", "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js")
457 })
458 })
459 })
460}