radix_leptos_primitives/components/
aspect_ratio.rs

1use crate::utils::merge_classes;
2use leptos::children::Children;
3use leptos::prelude::*;
4
5/// Aspect Ratio component - Maintain aspect ratio containers
6#[component]
7pub fn AspectRatio(
8    #[prop(optional)] class: Option<String>,
9    #[prop(optional)] style: Option<String>,
10    #[prop(optional)] children: Option<Children>,
11    #[prop(optional)] ratio: Option<f64>,
12    #[prop(optional)] width: Option<f64>,
13    #[prop(optional)] height: Option<f64>,
14    #[prop(optional)] min_width: Option<f64>,
15    #[prop(optional)] max_width: Option<f64>,
16    #[prop(optional)] min_height: Option<f64>,
17    #[prop(optional)] max_height: Option<f64>,
18) -> impl IntoView {
19    let ratio = ratio.unwrap_or(16.0 / 9.0);
20    let width = width.unwrap_or(100.0);
21    let height = height.unwrap_or(width / ratio);
22
23    let class = merge_classes(vec!["aspect-ratio", class.as_deref().unwrap_or("")]);
24
25    let container_style = format!(
26        "position: relative; width: {}%; padding-bottom: {}%; {}",
27        width,
28        (height / width) * 100.0,
29        style.unwrap_or_default()
30    );
31
32    let content_style = format!(
33        "position: absolute; top: 0; left: 0; width: 100%; height: 100%; min-width: {}px; max-width: {}px; min-height: {}px; max-height: {}px;",
34        min_width.unwrap_or(0.0),
35        max_width.unwrap_or(f64::INFINITY),
36        min_height.unwrap_or(0.0),
37        max_height.unwrap_or(f64::INFINITY)
38    );
39
40    view! {
41        <div
42            class=class
43            style=container_style
44            role="img"
45            aria-label="Aspect ratio container"
46            data-ratio=ratio
47            data-width=width
48            data-height=height
49        >
50            <div
51                class="aspect-ratio-content"
52                style=content_style
53            >
54                {children.map(|c| c())}
55            </div>
56        </div>
57    }
58}
59
60/// Aspect Ratio Container component
61#[component]
62pub fn AspectRatioContainer(
63    #[prop(optional)] class: Option<String>,
64    #[prop(optional)] style: Option<String>,
65    #[prop(optional)] children: Option<Children>,
66    #[prop(optional)] ratio: Option<f64>,
67) -> impl IntoView {
68    let ratio = ratio.unwrap_or(16.0 / 9.0);
69
70    let class = merge_classes(vec![
71        "aspect-ratio-container",
72        class.as_deref().unwrap_or(""),
73    ]);
74
75    let style = format!("aspect-ratio: {} / 1; {}", ratio, style.unwrap_or_default());
76
77    view! {
78        <div
79            class=class
80            style=style
81            role="img"
82            aria-label="Aspect ratio container"
83            data-ratio=ratio
84        >
85            {children.map(|c| c())}
86        </div>
87    }
88}
89
90/// Aspect Ratio Wrapper component
91#[component]
92pub fn AspectRatioWrapper(
93    #[prop(optional)] class: Option<String>,
94    #[prop(optional)] style: Option<String>,
95    #[prop(optional)] children: Option<Children>,
96    #[prop(optional)] ratio: Option<f64>,
97    #[prop(optional)] fit: Option<AspectRatioFit>,
98) -> impl IntoView {
99    let ratio = ratio.unwrap_or(16.0 / 9.0);
100    let fit = fit.unwrap_or_default();
101
102    let class = merge_classes(vec![
103        "aspect-ratio-wrapper",
104        &fit.to_class(),
105        class.as_deref().unwrap_or(""),
106    ]);
107
108    let style = format!("aspect-ratio: {} / 1; {}", ratio, style.unwrap_or_default());
109
110    view! {
111        <div
112            class=class
113            style=style
114            role="img"
115            aria-label="Aspect ratio wrapper"
116            data-ratio=ratio
117            data-fit=fit.to_string()
118        >
119            {children.map(|c| c())}
120        </div>
121    }
122}
123
124/// Aspect Ratio Fit enum
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
126pub enum AspectRatioFit {
127    #[default]
128    Cover,
129    Contain,
130    Fill,
131    ScaleDown,
132    None,
133}
134
135impl AspectRatioFit {
136    pub fn to_class(&self) -> &'static str {
137        match self {
138            AspectRatioFit::Cover => "fit-cover",
139            AspectRatioFit::Contain => "fit-contain",
140            AspectRatioFit::Fill => "fit-fill",
141            AspectRatioFit::ScaleDown => "fit-scale-down",
142            AspectRatioFit::None => "fit-none",
143        }
144    }
145
146    pub fn to_string(&self) -> &'static str {
147        match self {
148            AspectRatioFit::Cover => "cover",
149            AspectRatioFit::Contain => "contain",
150            AspectRatioFit::Fill => "fill",
151            AspectRatioFit::ScaleDown => "scale-down",
152            AspectRatioFit::None => "none",
153        }
154    }
155}
156
157/// Helper function to merge CSS classes
158
159#[cfg(test)]
160mod tests {
161    use proptest::prelude::*;
162    use wasm_bindgen_test::*;
163
164    wasm_bindgen_test_configure!(run_in_browser);
165
166    // Unit Tests
167    #[test]
168    fn test_aspect_ratio_creation() {}
169    #[test]
170    fn test_aspect_ratio_with_class() {}
171    #[test]
172    fn test_aspect_ratio_with_style() {}
173    #[test]
174    fn test_aspect_ratio_default_ratio() {}
175    #[test]
176    fn test_aspect_ratio_custom_ratio() {}
177    #[test]
178    fn test_aspect_ratio_width_height() {}
179    #[test]
180    fn test_aspect_ratio_min_max_constraints() {}
181
182    // Aspect Ratio Container tests
183    #[test]
184    fn test_aspect_ratio_container_creation() {}
185    #[test]
186    fn test_aspect_ratio_container_with_class() {}
187    #[test]
188    fn test_aspect_ratio_container_custom_ratio() {}
189
190    // Aspect Ratio Wrapper tests
191    #[test]
192    fn test_aspect_ratio_wrapper_creation() {}
193    #[test]
194    fn test_aspect_ratio_wrapper_with_class() {}
195    #[test]
196    fn test_aspect_ratio_wrapper_custom_ratio() {}
197    #[test]
198    fn test_aspect_ratio_wrapper_fit_options() {}
199
200    // Aspect Ratio Fit tests
201    #[test]
202    fn test_aspect_ratio_fit_default() {}
203    #[test]
204    fn test_aspect_ratio_fit_cover() {}
205    #[test]
206    fn test_aspect_ratio_fit_contain() {}
207    #[test]
208    fn test_aspect_ratio_fit_fill() {}
209    #[test]
210    fn test_aspect_ratio_fit_scale_down() {}
211    #[test]
212    fn test_aspect_ratio_fit_none() {}
213
214    // Helper function tests
215    #[test]
216    fn test_merge_classes_empty() {}
217    #[test]
218    fn test_merge_classes_single() {}
219    #[test]
220    fn test_merge_classes_multiple() {}
221    #[test]
222    fn test_merge_classes_with_empty() {}
223
224    // Property-based Tests
225    #[test]
226    fn test_aspect_ratio_property_based() {
227        proptest!(|(____class in ".*", __style in ".*")| {
228
229        });
230    }
231
232    #[test]
233    fn test_aspect_ratio_validation() {
234        proptest!(|(____ratio in 0.1..10.0f64, __width in 10.0..1000.0f64)| {
235
236        });
237    }
238
239    #[test]
240    fn test_aspect_ratio_constraints_validation() {
241        proptest!(|(____min_width in 0.0..500.0f64, __max_width in 500.0..2000.0f64, __min_height in 0.0..500.0f64, __max_height in 500.0..2000.0f64)| {
242
243        });
244    }
245
246    // Integration Tests
247    #[test]
248    fn test_aspect_ratio_responsive_behavior() {}
249    #[test]
250    fn test_aspect_ratio_content_fitting() {}
251    #[test]
252    fn test_aspect_ratio_constraint_handling() {}
253    #[test]
254    fn test_aspect_ratio_nested_components() {}
255    #[test]
256    fn test_aspect_ratio_different_ratios() {}
257
258    // Performance Tests
259    #[test]
260    fn test_aspect_ratio_large_content() {}
261    #[test]
262    fn test_aspect_ratio_render_performance() {}
263    #[test]
264    fn test_aspect_ratio_memory_usage() {}
265    #[test]
266    fn test_aspect_ratio_resize_performance() {}
267}