tailwind_rs_core/responsive/
flexbox.rs

1//! # Flexbox Responsive Utilities
2//!
3//! This module provides flexbox-specific responsive utilities.
4
5use super::breakpoints::Breakpoint;
6use super::responsive_values::ResponsiveValue;
7use serde::{Deserialize, Serialize};
8
9/// Flex direction options
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum FlexDirection {
12    /// Row direction (default)
13    Row,
14    /// Row reverse direction
15    RowReverse,
16    /// Column direction
17    Column,
18    /// Column reverse direction
19    ColumnReverse,
20}
21
22impl FlexDirection {
23    /// Get the CSS class for this flex direction
24    pub fn to_class(&self) -> &'static str {
25        match self {
26            FlexDirection::Row => "flex-row",
27            FlexDirection::RowReverse => "flex-row-reverse",
28            FlexDirection::Column => "flex-col",
29            FlexDirection::ColumnReverse => "flex-col-reverse",
30        }
31    }
32}
33
34/// Flex wrap options
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
36pub enum FlexWrap {
37    /// No wrap
38    NoWrap,
39    /// Wrap
40    Wrap,
41    /// Wrap reverse
42    WrapReverse,
43}
44
45impl FlexWrap {
46    /// Get the CSS class for this flex wrap
47    pub fn to_class(&self) -> &'static str {
48        match self {
49            FlexWrap::NoWrap => "flex-nowrap",
50            FlexWrap::Wrap => "flex-wrap",
51            FlexWrap::WrapReverse => "flex-wrap-reverse",
52        }
53    }
54}
55
56/// Justify content options
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
58pub enum JustifyContent {
59    /// Start alignment
60    Start,
61    /// End alignment
62    End,
63    /// Center alignment
64    Center,
65    /// Space between
66    Between,
67    /// Space around
68    Around,
69    /// Space evenly
70    Evenly,
71}
72
73impl JustifyContent {
74    /// Get the CSS class for this justify content
75    pub fn to_class(&self) -> &'static str {
76        match self {
77            JustifyContent::Start => "justify-start",
78            JustifyContent::End => "justify-end",
79            JustifyContent::Center => "justify-center",
80            JustifyContent::Between => "justify-between",
81            JustifyContent::Around => "justify-around",
82            JustifyContent::Evenly => "justify-evenly",
83        }
84    }
85}
86
87/// Align items options
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
89pub enum AlignItems {
90    /// Start alignment
91    Start,
92    /// End alignment
93    End,
94    /// Center alignment
95    Center,
96    /// Baseline alignment
97    Baseline,
98    /// Stretch alignment
99    Stretch,
100}
101
102impl AlignItems {
103    /// Get the CSS class for this align items
104    pub fn to_class(&self) -> &'static str {
105        match self {
106            AlignItems::Start => "items-start",
107            AlignItems::End => "items-end",
108            AlignItems::Center => "items-center",
109            AlignItems::Baseline => "items-baseline",
110            AlignItems::Stretch => "items-stretch",
111        }
112    }
113}
114
115/// Responsive flex container
116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
117pub struct ResponsiveFlex {
118    /// Flex direction for each breakpoint
119    pub direction: ResponsiveValue<FlexDirection>,
120    /// Flex wrap for each breakpoint
121    pub wrap: ResponsiveValue<FlexWrap>,
122    /// Justify content for each breakpoint
123    pub justify: ResponsiveValue<JustifyContent>,
124    /// Align items for each breakpoint
125    pub align: ResponsiveValue<AlignItems>,
126    /// Gap for each breakpoint
127    pub gap: ResponsiveValue<u32>,
128}
129
130impl ResponsiveFlex {
131    /// Create a new responsive flex container
132    pub fn new() -> Self {
133        Self::default()
134    }
135    
136    /// Create a responsive flex container with base values
137    pub fn with_base(direction: FlexDirection, wrap: FlexWrap, justify: JustifyContent, align: AlignItems, gap: u32) -> Self {
138        Self {
139            direction: ResponsiveValue::with_base(direction),
140            wrap: ResponsiveValue::with_base(wrap),
141            justify: ResponsiveValue::with_base(justify),
142            align: ResponsiveValue::with_base(align),
143            gap: ResponsiveValue::with_base(gap),
144        }
145    }
146    
147    /// Set flex direction for a specific breakpoint
148    pub fn set_direction(&mut self, breakpoint: Breakpoint, direction: FlexDirection) {
149        self.direction.set_breakpoint(breakpoint, direction);
150    }
151    
152    /// Set flex wrap for a specific breakpoint
153    pub fn set_wrap(&mut self, breakpoint: Breakpoint, wrap: FlexWrap) {
154        self.wrap.set_breakpoint(breakpoint, wrap);
155    }
156    
157    /// Set justify content for a specific breakpoint
158    pub fn set_justify(&mut self, breakpoint: Breakpoint, justify: JustifyContent) {
159        self.justify.set_breakpoint(breakpoint, justify);
160    }
161    
162    /// Set align items for a specific breakpoint
163    pub fn set_align(&mut self, breakpoint: Breakpoint, align: AlignItems) {
164        self.align.set_breakpoint(breakpoint, align);
165    }
166    
167    /// Set gap for a specific breakpoint
168    pub fn set_gap(&mut self, breakpoint: Breakpoint, gap: u32) {
169        self.gap.set_breakpoint(breakpoint, gap);
170    }
171    
172    /// Get flex direction for a specific breakpoint
173    pub fn get_direction(&self, breakpoint: Breakpoint) -> Option<FlexDirection> {
174        self.direction.get_breakpoint(breakpoint).copied()
175    }
176    
177    /// Get flex wrap for a specific breakpoint
178    pub fn get_wrap(&self, breakpoint: Breakpoint) -> Option<FlexWrap> {
179        self.wrap.get_breakpoint(breakpoint).copied()
180    }
181    
182    /// Get justify content for a specific breakpoint
183    pub fn get_justify(&self, breakpoint: Breakpoint) -> Option<JustifyContent> {
184        self.justify.get_breakpoint(breakpoint).copied()
185    }
186    
187    /// Get align items for a specific breakpoint
188    pub fn get_align(&self, breakpoint: Breakpoint) -> Option<AlignItems> {
189        self.align.get_breakpoint(breakpoint).copied()
190    }
191    
192    /// Get gap for a specific breakpoint
193    pub fn get_gap(&self, breakpoint: Breakpoint) -> Option<u32> {
194        self.gap.get_breakpoint(breakpoint).copied()
195    }
196    
197    /// Generate CSS classes for all breakpoints
198    pub fn to_css_classes(&self) -> String {
199        let mut classes = Vec::new();
200        
201        // Add flex direction classes
202        let direction_classes = self.direction.to_css_classes(|d| d.to_class().to_string());
203        if !direction_classes.is_empty() {
204            classes.push(direction_classes);
205        }
206        
207        // Add flex wrap classes
208        let wrap_classes = self.wrap.to_css_classes(|w| w.to_class().to_string());
209        if !wrap_classes.is_empty() {
210            classes.push(wrap_classes);
211        }
212        
213        // Add justify content classes
214        let justify_classes = self.justify.to_css_classes(|j| j.to_class().to_string());
215        if !justify_classes.is_empty() {
216            classes.push(justify_classes);
217        }
218        
219        // Add align items classes
220        let align_classes = self.align.to_css_classes(|a| a.to_class().to_string());
221        if !align_classes.is_empty() {
222            classes.push(align_classes);
223        }
224        
225        // Add gap classes
226        let gap_classes = self.gap.to_css_classes(|g| {
227            if *g == 0 {
228                "gap-0".to_string()
229            } else {
230                format!("gap-{}", g)
231            }
232        });
233        if !gap_classes.is_empty() {
234            classes.push(gap_classes);
235        }
236        
237        classes.join(" ")
238    }
239    
240    /// Generate CSS classes for a specific screen width
241    pub fn to_css_classes_for_width(&self, screen_width: u32) -> String {
242        let mut classes = Vec::new();
243        
244        // Add flex direction classes
245        if let Some(direction) = self.direction.get_for_width(screen_width) {
246            classes.push(direction.to_class().to_string());
247        }
248        
249        // Add flex wrap classes
250        if let Some(wrap) = self.wrap.get_for_width(screen_width) {
251            classes.push(wrap.to_class().to_string());
252        }
253        
254        // Add justify content classes
255        if let Some(justify) = self.justify.get_for_width(screen_width) {
256            classes.push(justify.to_class().to_string());
257        }
258        
259        // Add align items classes
260        if let Some(align) = self.align.get_for_width(screen_width) {
261            classes.push(align.to_class().to_string());
262        }
263        
264        // Add gap classes
265        if let Some(gap) = self.gap.get_for_width(screen_width) {
266            if *gap == 0 {
267                classes.push("gap-0".to_string());
268            } else {
269                classes.push(format!("gap-{}", gap));
270            }
271        }
272        
273        classes.join(" ")
274    }
275}
276
277impl Default for ResponsiveFlex {
278    fn default() -> Self {
279        Self {
280            direction: ResponsiveValue::with_base(FlexDirection::Row),
281            wrap: ResponsiveValue::with_base(FlexWrap::NoWrap),
282            justify: ResponsiveValue::with_base(JustifyContent::Start),
283            align: ResponsiveValue::with_base(AlignItems::Stretch),
284            gap: ResponsiveValue::with_base(0),
285        }
286    }
287}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292
293    #[test]
294    fn test_flex_direction_to_class() {
295        assert_eq!(FlexDirection::Row.to_class(), "flex-row");
296        assert_eq!(FlexDirection::RowReverse.to_class(), "flex-row-reverse");
297        assert_eq!(FlexDirection::Column.to_class(), "flex-col");
298        assert_eq!(FlexDirection::ColumnReverse.to_class(), "flex-col-reverse");
299    }
300
301    #[test]
302    fn test_flex_wrap_to_class() {
303        assert_eq!(FlexWrap::NoWrap.to_class(), "flex-nowrap");
304        assert_eq!(FlexWrap::Wrap.to_class(), "flex-wrap");
305        assert_eq!(FlexWrap::WrapReverse.to_class(), "flex-wrap-reverse");
306    }
307
308    #[test]
309    fn test_justify_content_to_class() {
310        assert_eq!(JustifyContent::Start.to_class(), "justify-start");
311        assert_eq!(JustifyContent::End.to_class(), "justify-end");
312        assert_eq!(JustifyContent::Center.to_class(), "justify-center");
313        assert_eq!(JustifyContent::Between.to_class(), "justify-between");
314        assert_eq!(JustifyContent::Around.to_class(), "justify-around");
315        assert_eq!(JustifyContent::Evenly.to_class(), "justify-evenly");
316    }
317
318    #[test]
319    fn test_align_items_to_class() {
320        assert_eq!(AlignItems::Start.to_class(), "items-start");
321        assert_eq!(AlignItems::End.to_class(), "items-end");
322        assert_eq!(AlignItems::Center.to_class(), "items-center");
323        assert_eq!(AlignItems::Baseline.to_class(), "items-baseline");
324        assert_eq!(AlignItems::Stretch.to_class(), "items-stretch");
325    }
326
327    #[test]
328    fn test_responsive_flex_new() {
329        let flex = ResponsiveFlex::new();
330        assert_eq!(flex.get_direction(Breakpoint::Base), Some(FlexDirection::Row));
331        assert_eq!(flex.get_wrap(Breakpoint::Base), Some(FlexWrap::NoWrap));
332        assert_eq!(flex.get_justify(Breakpoint::Base), Some(JustifyContent::Start));
333        assert_eq!(flex.get_align(Breakpoint::Base), Some(AlignItems::Stretch));
334        assert_eq!(flex.get_gap(Breakpoint::Base), Some(0));
335    }
336
337    #[test]
338    fn test_responsive_flex_with_base() {
339        let flex = ResponsiveFlex::with_base(
340            FlexDirection::Column,
341            FlexWrap::Wrap,
342            JustifyContent::Center,
343            AlignItems::Center,
344            4,
345        );
346        
347        assert_eq!(flex.get_direction(Breakpoint::Base), Some(FlexDirection::Column));
348        assert_eq!(flex.get_wrap(Breakpoint::Base), Some(FlexWrap::Wrap));
349        assert_eq!(flex.get_justify(Breakpoint::Base), Some(JustifyContent::Center));
350        assert_eq!(flex.get_align(Breakpoint::Base), Some(AlignItems::Center));
351        assert_eq!(flex.get_gap(Breakpoint::Base), Some(4));
352    }
353
354    #[test]
355    fn test_responsive_flex_set_get() {
356        let mut flex = ResponsiveFlex::new();
357        
358        flex.set_direction(Breakpoint::Sm, FlexDirection::Column);
359        flex.set_wrap(Breakpoint::Md, FlexWrap::Wrap);
360        flex.set_justify(Breakpoint::Lg, JustifyContent::Between);
361        flex.set_align(Breakpoint::Xl, AlignItems::Center);
362        flex.set_gap(Breakpoint::Xl2, 8);
363        
364        assert_eq!(flex.get_direction(Breakpoint::Sm), Some(FlexDirection::Column));
365        assert_eq!(flex.get_wrap(Breakpoint::Md), Some(FlexWrap::Wrap));
366        assert_eq!(flex.get_justify(Breakpoint::Lg), Some(JustifyContent::Between));
367        assert_eq!(flex.get_align(Breakpoint::Xl), Some(AlignItems::Center));
368        assert_eq!(flex.get_gap(Breakpoint::Xl2), Some(8));
369    }
370
371    #[test]
372    fn test_responsive_flex_to_css_classes() {
373        let mut flex = ResponsiveFlex::new();
374        flex.set_direction(Breakpoint::Sm, FlexDirection::Column);
375        flex.set_justify(Breakpoint::Md, JustifyContent::Center);
376        flex.set_gap(Breakpoint::Lg, 4);
377        
378        let classes = flex.to_css_classes();
379        assert!(classes.contains("flex-row"));
380        assert!(classes.contains("sm:flex-col"));
381        assert!(classes.contains("md:justify-center"));
382        assert!(classes.contains("lg:gap-4"));
383    }
384
385    #[test]
386    fn test_responsive_flex_to_css_classes_for_width() {
387        let mut flex = ResponsiveFlex::new();
388        flex.set_direction(Breakpoint::Sm, FlexDirection::Column);
389        flex.set_justify(Breakpoint::Md, JustifyContent::Center);
390        flex.set_gap(Breakpoint::Lg, 4);
391        
392        // Test width 0 (base only)
393        let classes_0 = flex.to_css_classes_for_width(0);
394        assert!(classes_0.contains("flex-row"));
395        assert!(!classes_0.contains("flex-col"));
396        assert!(!classes_0.contains("justify-center"));
397        assert!(!classes_0.contains("gap-4"));
398        
399        // Test width 640 (sm active)
400        let classes_640 = flex.to_css_classes_for_width(640);
401        assert!(classes_640.contains("flex-col"));
402        assert!(!classes_640.contains("justify-center"));
403        assert!(!classes_640.contains("gap-4"));
404        
405        // Test width 768 (md active)
406        let classes_768 = flex.to_css_classes_for_width(768);
407        assert!(classes_768.contains("flex-col"));
408        assert!(classes_768.contains("justify-center"));
409        assert!(!classes_768.contains("gap-4"));
410        
411        // Test width 1024 (lg active)
412        let classes_1024 = flex.to_css_classes_for_width(1024);
413        assert!(classes_1024.contains("flex-col"));
414        assert!(classes_1024.contains("justify-center"));
415        assert!(classes_1024.contains("gap-4"));
416    }
417}