tailwind_rs_core/utilities/
container_queries.rs

1//! Container query utilities for tailwind-rs
2//!
3//! This module provides support for CSS container queries including
4//! @container queries and container-based responsive utilities.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// Container query types
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum ContainerQuery {
12    /// @container (inline-size > 768px)
13    InlineSize(ContainerSize),
14    /// @container (block-size > 768px)
15    BlockSize(ContainerSize),
16    /// @container (width > 768px)
17    Width(ContainerSize),
18    /// @container (height > 768px)
19    Height(ContainerSize),
20    /// @container (aspect-ratio > 16/9)
21    AspectRatio(ContainerAspectRatio),
22    /// @container (orientation: landscape)
23    Orientation(ContainerOrientation),
24}
25
26/// Container size values
27#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
28pub enum ContainerSize {
29    /// 320px
30    Xs,
31    /// 640px
32    Sm,
33    /// 768px
34    Md,
35    /// 1024px
36    Lg,
37    /// 1280px
38    Xl,
39    /// 1536px
40    Xl2,
41    /// Custom size
42    Custom(String),
43}
44
45/// Container aspect ratio values
46#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
47pub enum ContainerAspectRatio {
48    /// 1/1 (square)
49    Square,
50    /// 4/3
51    Video,
52    /// 16/9
53    Widescreen,
54    /// 21/9
55    Ultrawide,
56    /// Custom aspect ratio
57    Custom(String),
58}
59
60/// Container orientation
61#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
62pub enum ContainerOrientation {
63    /// landscape
64    Landscape,
65    /// portrait
66    Portrait,
67}
68
69impl ContainerQuery {
70    /// Create a new inline-size container query
71    pub fn inline_size(size: ContainerSize) -> Self {
72        Self::InlineSize(size)
73    }
74
75    /// Create a new block-size container query
76    pub fn block_size(size: ContainerSize) -> Self {
77        Self::BlockSize(size)
78    }
79
80    /// Create a new width container query
81    pub fn width(size: ContainerSize) -> Self {
82        Self::Width(size)
83    }
84
85    /// Create a new height container query
86    pub fn height(size: ContainerSize) -> Self {
87        Self::Height(size)
88    }
89
90    /// Create a new aspect-ratio container query
91    pub fn aspect_ratio(ratio: ContainerAspectRatio) -> Self {
92        Self::AspectRatio(ratio)
93    }
94
95    /// Create a new orientation container query
96    pub fn orientation(orientation: ContainerOrientation) -> Self {
97        Self::Orientation(orientation)
98    }
99
100    /// Convert to CSS @container query
101    pub fn to_css_query(&self) -> String {
102        match self {
103            ContainerQuery::InlineSize(size) => {
104                format!("@container (inline-size > {})", size.to_css_value())
105            }
106            ContainerQuery::BlockSize(size) => {
107                format!("@container (block-size > {})", size.to_css_value())
108            }
109            ContainerQuery::Width(size) => {
110                format!("@container (width > {})", size.to_css_value())
111            }
112            ContainerQuery::Height(size) => {
113                format!("@container (height > {})", size.to_css_value())
114            }
115            ContainerQuery::AspectRatio(ratio) => {
116                format!("@container (aspect-ratio > {})", ratio.to_css_value())
117            }
118            ContainerQuery::Orientation(orientation) => {
119                format!("@container (orientation: {})", orientation.to_css_value())
120            }
121        }
122    }
123
124    /// Convert to class name
125    pub fn to_class_name(&self) -> String {
126        match self {
127            ContainerQuery::InlineSize(size) => {
128                format!("@container/inline-size:{}", size.to_class_name())
129            }
130            ContainerQuery::BlockSize(size) => {
131                format!("@container/block-size:{}", size.to_class_name())
132            }
133            ContainerQuery::Width(size) => {
134                format!("@container/width:{}", size.to_class_name())
135            }
136            ContainerQuery::Height(size) => {
137                format!("@container/height:{}", size.to_class_name())
138            }
139            ContainerQuery::AspectRatio(ratio) => {
140                format!("@container/aspect-ratio:{}", ratio.to_class_name())
141            }
142            ContainerQuery::Orientation(orientation) => {
143                format!("@container/orientation:{}", orientation.to_class_name())
144            }
145        }
146    }
147}
148
149impl ContainerSize {
150    /// Convert to CSS value
151    pub fn to_css_value(&self) -> String {
152        match self {
153            ContainerSize::Xs => "320px".to_string(),
154            ContainerSize::Sm => "640px".to_string(),
155            ContainerSize::Md => "768px".to_string(),
156            ContainerSize::Lg => "1024px".to_string(),
157            ContainerSize::Xl => "1280px".to_string(),
158            ContainerSize::Xl2 => "1536px".to_string(),
159            ContainerSize::Custom(size) => size.clone(),
160        }
161    }
162
163    /// Convert to class name
164    pub fn to_class_name(&self) -> String {
165        match self {
166            ContainerSize::Xs => "xs".to_string(),
167            ContainerSize::Sm => "sm".to_string(),
168            ContainerSize::Md => "md".to_string(),
169            ContainerSize::Lg => "lg".to_string(),
170            ContainerSize::Xl => "xl".to_string(),
171            ContainerSize::Xl2 => "2xl".to_string(),
172            ContainerSize::Custom(size) => size.clone(),
173        }
174    }
175}
176
177impl ContainerAspectRatio {
178    /// Convert to CSS value
179    pub fn to_css_value(&self) -> String {
180        match self {
181            ContainerAspectRatio::Square => "1/1".to_string(),
182            ContainerAspectRatio::Video => "4/3".to_string(),
183            ContainerAspectRatio::Widescreen => "16/9".to_string(),
184            ContainerAspectRatio::Ultrawide => "21/9".to_string(),
185            ContainerAspectRatio::Custom(ratio) => ratio.clone(),
186        }
187    }
188
189    /// Convert to class name
190    pub fn to_class_name(&self) -> String {
191        match self {
192            ContainerAspectRatio::Square => "square".to_string(),
193            ContainerAspectRatio::Video => "video".to_string(),
194            ContainerAspectRatio::Widescreen => "widescreen".to_string(),
195            ContainerAspectRatio::Ultrawide => "ultrawide".to_string(),
196            ContainerAspectRatio::Custom(ratio) => ratio.clone(),
197        }
198    }
199}
200
201impl ContainerOrientation {
202    /// Convert to CSS value
203    pub fn to_css_value(&self) -> String {
204        match self {
205            ContainerOrientation::Landscape => "landscape".to_string(),
206            ContainerOrientation::Portrait => "portrait".to_string(),
207        }
208    }
209
210    /// Convert to class name
211    pub fn to_class_name(&self) -> String {
212        match self {
213            ContainerOrientation::Landscape => "landscape".to_string(),
214            ContainerOrientation::Portrait => "portrait".to_string(),
215        }
216    }
217}
218
219impl fmt::Display for ContainerQuery {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        write!(f, "{}", self.to_css_query())
222    }
223}
224
225impl fmt::Display for ContainerSize {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        write!(f, "{}", self.to_css_value())
228    }
229}
230
231impl fmt::Display for ContainerAspectRatio {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        write!(f, "{}", self.to_css_value())
234    }
235}
236
237impl fmt::Display for ContainerOrientation {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        write!(f, "{}", self.to_css_value())
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_inline_size_container_query() {
249        let query = ContainerQuery::inline_size(ContainerSize::Md);
250        assert_eq!(query.to_css_query(), "@container (inline-size > 768px)");
251        assert_eq!(query.to_class_name(), "@container/inline-size:md");
252    }
253
254    #[test]
255    fn test_block_size_container_query() {
256        let query = ContainerQuery::block_size(ContainerSize::Lg);
257        assert_eq!(query.to_css_query(), "@container (block-size > 1024px)");
258        assert_eq!(query.to_class_name(), "@container/block-size:lg");
259    }
260
261    #[test]
262    fn test_width_container_query() {
263        let query = ContainerQuery::width(ContainerSize::Sm);
264        assert_eq!(query.to_css_query(), "@container (width > 640px)");
265        assert_eq!(query.to_class_name(), "@container/width:sm");
266    }
267
268    #[test]
269    fn test_height_container_query() {
270        let query = ContainerQuery::height(ContainerSize::Xl);
271        assert_eq!(query.to_css_query(), "@container (height > 1280px)");
272        assert_eq!(query.to_class_name(), "@container/height:xl");
273    }
274
275    #[test]
276    fn test_aspect_ratio_container_query() {
277        let query = ContainerQuery::aspect_ratio(ContainerAspectRatio::Widescreen);
278        assert_eq!(query.to_css_query(), "@container (aspect-ratio > 16/9)");
279        assert_eq!(query.to_class_name(), "@container/aspect-ratio:widescreen");
280    }
281
282    #[test]
283    fn test_orientation_container_query() {
284        let query = ContainerQuery::orientation(ContainerOrientation::Landscape);
285        assert_eq!(query.to_css_query(), "@container (orientation: landscape)");
286        assert_eq!(query.to_class_name(), "@container/orientation:landscape");
287    }
288
289    #[test]
290    fn test_custom_container_size() {
291        let size = ContainerSize::Custom("500px".to_string());
292        assert_eq!(size.to_css_value(), "500px");
293        assert_eq!(size.to_class_name(), "500px");
294    }
295
296    #[test]
297    fn test_custom_aspect_ratio() {
298        let ratio = ContainerAspectRatio::Custom("3/2".to_string());
299        assert_eq!(ratio.to_css_value(), "3/2");
300        assert_eq!(ratio.to_class_name(), "3/2");
301    }
302
303    #[test]
304    fn test_container_size_values() {
305        assert_eq!(ContainerSize::Xs.to_css_value(), "320px");
306        assert_eq!(ContainerSize::Sm.to_css_value(), "640px");
307        assert_eq!(ContainerSize::Md.to_css_value(), "768px");
308        assert_eq!(ContainerSize::Lg.to_css_value(), "1024px");
309        assert_eq!(ContainerSize::Xl.to_css_value(), "1280px");
310        assert_eq!(ContainerSize::Xl2.to_css_value(), "1536px");
311    }
312
313    #[test]
314    fn test_container_size_class_names() {
315        assert_eq!(ContainerSize::Xs.to_class_name(), "xs");
316        assert_eq!(ContainerSize::Sm.to_class_name(), "sm");
317        assert_eq!(ContainerSize::Md.to_class_name(), "md");
318        assert_eq!(ContainerSize::Lg.to_class_name(), "lg");
319        assert_eq!(ContainerSize::Xl.to_class_name(), "xl");
320        assert_eq!(ContainerSize::Xl2.to_class_name(), "2xl");
321    }
322
323    #[test]
324    fn test_aspect_ratio_values() {
325        assert_eq!(ContainerAspectRatio::Square.to_css_value(), "1/1");
326        assert_eq!(ContainerAspectRatio::Video.to_css_value(), "4/3");
327        assert_eq!(ContainerAspectRatio::Widescreen.to_css_value(), "16/9");
328        assert_eq!(ContainerAspectRatio::Ultrawide.to_css_value(), "21/9");
329    }
330
331    #[test]
332    fn test_aspect_ratio_class_names() {
333        assert_eq!(ContainerAspectRatio::Square.to_class_name(), "square");
334        assert_eq!(ContainerAspectRatio::Video.to_class_name(), "video");
335        assert_eq!(ContainerAspectRatio::Widescreen.to_class_name(), "widescreen");
336        assert_eq!(ContainerAspectRatio::Ultrawide.to_class_name(), "ultrawide");
337    }
338
339    #[test]
340    fn test_orientation_values() {
341        assert_eq!(ContainerOrientation::Landscape.to_css_value(), "landscape");
342        assert_eq!(ContainerOrientation::Portrait.to_css_value(), "portrait");
343    }
344
345    #[test]
346    fn test_orientation_class_names() {
347        assert_eq!(ContainerOrientation::Landscape.to_class_name(), "landscape");
348        assert_eq!(ContainerOrientation::Portrait.to_class_name(), "portrait");
349    }
350
351    #[test]
352    fn test_container_query_display() {
353        let query = ContainerQuery::inline_size(ContainerSize::Md);
354        assert_eq!(format!("{}", query), "@container (inline-size > 768px)");
355    }
356
357    #[test]
358    fn test_container_size_display() {
359        let size = ContainerSize::Lg;
360        assert_eq!(format!("{}", size), "1024px");
361    }
362
363    #[test]
364    fn test_aspect_ratio_display() {
365        let ratio = ContainerAspectRatio::Widescreen;
366        assert_eq!(format!("{}", ratio), "16/9");
367    }
368
369    #[test]
370    fn test_orientation_display() {
371        let orientation = ContainerOrientation::Landscape;
372        assert_eq!(format!("{}", orientation), "landscape");
373    }
374
375    #[test]
376    fn test_container_query_serialization() {
377        let query = ContainerQuery::inline_size(ContainerSize::Md);
378        let serialized = serde_json::to_string(&query).unwrap();
379        let deserialized: ContainerQuery = serde_json::from_str(&serialized).unwrap();
380        assert_eq!(query, deserialized);
381    }
382
383    #[test]
384    fn test_container_size_serialization() {
385        let size = ContainerSize::Lg;
386        let serialized = serde_json::to_string(&size).unwrap();
387        let deserialized: ContainerSize = serde_json::from_str(&serialized).unwrap();
388        assert_eq!(size, deserialized);
389    }
390
391    #[test]
392    fn test_aspect_ratio_serialization() {
393        let ratio = ContainerAspectRatio::Widescreen;
394        let serialized = serde_json::to_string(&ratio).unwrap();
395        let deserialized: ContainerAspectRatio = serde_json::from_str(&serialized).unwrap();
396        assert_eq!(ratio, deserialized);
397    }
398
399    #[test]
400    fn test_orientation_serialization() {
401        let orientation = ContainerOrientation::Landscape;
402        let serialized = serde_json::to_string(&orientation).unwrap();
403        let deserialized: ContainerOrientation = serde_json::from_str(&serialized).unwrap();
404        assert_eq!(orientation, deserialized);
405    }
406}