Skip to main content

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}