dioxus_bootstrap_css/figure.rs
1use dioxus::prelude::*;
2
3/// Bootstrap Figure component — image with optional caption.
4///
5/// # Bootstrap HTML → Dioxus
6///
7/// ```html
8/// <!-- Bootstrap HTML -->
9/// <figure class="figure">
10/// <img src="/photo.jpg" class="figure-img img-fluid rounded" alt="Photo">
11/// <figcaption class="figure-caption text-end">A caption.</figcaption>
12/// </figure>
13/// ```
14///
15/// ```rust,no_run
16/// # use dioxus::prelude::*;
17/// # use dioxus_bootstrap_css::prelude::*;
18/// # fn _doctest() -> Element {
19/// rsx! {
20/// Figure { src: "/photo.jpg", alt: "A photo",
21/// caption: "A caption for the image.",
22/// caption_align: "end",
23/// rounded: true,
24/// }
25/// }
26/// # }
27/// ```
28///
29/// # Props
30///
31/// - `src` — image URL
32/// - `alt` — alt text
33/// - `caption` / `caption_align` — caption text and alignment
34/// - `fluid` — responsive image (default: true)
35/// - `rounded` — rounded corners
36/// - `thumbnail` — thumbnail border
37#[derive(Clone, PartialEq, Props)]
38pub struct FigureProps {
39 /// Image source URL.
40 pub src: String,
41 /// Alt text for the image.
42 #[props(default)]
43 pub alt: String,
44 /// Caption text.
45 #[props(default)]
46 pub caption: String,
47 /// Align caption (start, center, end).
48 #[props(default)]
49 pub caption_align: String,
50 /// Make the image fluid (responsive).
51 #[props(default = true)]
52 pub fluid: bool,
53 /// Add rounded corners to the image.
54 #[props(default)]
55 pub rounded: bool,
56 /// Add thumbnail border to the image.
57 #[props(default)]
58 pub thumbnail: bool,
59 /// Additional CSS classes for the figure.
60 #[props(default)]
61 pub class: String,
62 /// Additional CSS classes for the image.
63 #[props(default)]
64 pub img_class: String,
65}
66
67#[component]
68pub fn Figure(props: FigureProps) -> Element {
69 let fig_class = if props.class.is_empty() {
70 "figure".to_string()
71 } else {
72 format!("figure {}", props.class)
73 };
74
75 let mut img_classes = vec!["figure-img".to_string()];
76 if props.fluid {
77 img_classes.push("img-fluid".to_string());
78 }
79 if props.rounded {
80 img_classes.push("rounded".to_string());
81 }
82 if props.thumbnail {
83 img_classes.push("img-thumbnail".to_string());
84 }
85 if !props.img_class.is_empty() {
86 img_classes.push(props.img_class.clone());
87 }
88 let img_class = img_classes.join(" ");
89
90 let caption_class = if props.caption_align.is_empty() {
91 "figure-caption".to_string()
92 } else {
93 format!("figure-caption text-{}", props.caption_align)
94 };
95
96 rsx! {
97 figure { class: "{fig_class}",
98 img {
99 class: "{img_class}",
100 src: "{props.src}",
101 alt: "{props.alt}",
102 }
103 if !props.caption.is_empty() {
104 figcaption { class: "{caption_class}", "{props.caption}" }
105 }
106 }
107 }
108}
109
110/// Bootstrap responsive embed / aspect ratio wrapper.
111///
112/// # Bootstrap HTML → Dioxus
113///
114/// ```html
115/// <!-- Bootstrap HTML -->
116/// <div class="ratio ratio-16x9">
117/// <iframe src="https://www.youtube.com/embed/..."></iframe>
118/// </div>
119/// ```
120///
121/// ```rust,no_run
122/// # use dioxus::prelude::*;
123/// # use dioxus_bootstrap_css::prelude::*;
124/// # fn _doctest() -> Element {
125/// rsx! {
126/// Ratio { aspect: "16x9",
127/// iframe { src: "https://www.youtube.com/embed/..." }
128/// }
129/// Ratio { aspect: "1x1",
130/// div { class: "bg-primary text-white d-flex align-items-center justify-content-center", "1:1" }
131/// }
132/// }
133/// # }
134/// ```
135///
136/// # Props
137///
138/// - `aspect` — "1x1", "4x3", "16x9", "21x9"
139#[derive(Clone, PartialEq, Props)]
140pub struct RatioProps {
141 /// Aspect ratio: "1x1", "4x3", "16x9", "21x9".
142 #[props(default = "16x9".to_string())]
143 pub aspect: String,
144 /// Additional CSS classes.
145 #[props(default)]
146 pub class: String,
147 /// Child element (iframe, video, embed, etc.).
148 pub children: Element,
149}
150
151#[component]
152pub fn Ratio(props: RatioProps) -> Element {
153 let full_class = if props.class.is_empty() {
154 format!("ratio ratio-{}", props.aspect)
155 } else {
156 format!("ratio ratio-{} {}", props.aspect, props.class)
157 };
158
159 rsx! {
160 div { class: "{full_class}",
161 {props.children}
162 }
163 }
164}