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(
138        direction: FlexDirection,
139        wrap: FlexWrap,
140        justify: JustifyContent,
141        align: AlignItems,
142        gap: u32,
143    ) -> Self {
144        Self {
145            direction: ResponsiveValue::with_base(direction),
146            wrap: ResponsiveValue::with_base(wrap),
147            justify: ResponsiveValue::with_base(justify),
148            align: ResponsiveValue::with_base(align),
149            gap: ResponsiveValue::with_base(gap),
150        }
151    }
152
153    /// Set flex direction for a specific breakpoint
154    pub fn set_direction(&mut self, breakpoint: Breakpoint, direction: FlexDirection) {
155        self.direction.set_breakpoint(breakpoint, direction);
156    }
157
158    /// Set flex wrap for a specific breakpoint
159    pub fn set_wrap(&mut self, breakpoint: Breakpoint, wrap: FlexWrap) {
160        self.wrap.set_breakpoint(breakpoint, wrap);
161    }
162
163    /// Set justify content for a specific breakpoint
164    pub fn set_justify(&mut self, breakpoint: Breakpoint, justify: JustifyContent) {
165        self.justify.set_breakpoint(breakpoint, justify);
166    }
167
168    /// Set align items for a specific breakpoint
169    pub fn set_align(&mut self, breakpoint: Breakpoint, align: AlignItems) {
170        self.align.set_breakpoint(breakpoint, align);
171    }
172
173    /// Set gap for a specific breakpoint
174    pub fn set_gap(&mut self, breakpoint: Breakpoint, gap: u32) {
175        self.gap.set_breakpoint(breakpoint, gap);
176    }
177
178    /// Get flex direction for a specific breakpoint
179    pub fn get_direction(&self, breakpoint: Breakpoint) -> Option<FlexDirection> {
180        self.direction.get_breakpoint(breakpoint).copied()
181    }
182
183    /// Get flex wrap for a specific breakpoint
184    pub fn get_wrap(&self, breakpoint: Breakpoint) -> Option<FlexWrap> {
185        self.wrap.get_breakpoint(breakpoint).copied()
186    }
187
188    /// Get justify content for a specific breakpoint
189    pub fn get_justify(&self, breakpoint: Breakpoint) -> Option<JustifyContent> {
190        self.justify.get_breakpoint(breakpoint).copied()
191    }
192
193    /// Get align items for a specific breakpoint
194    pub fn get_align(&self, breakpoint: Breakpoint) -> Option<AlignItems> {
195        self.align.get_breakpoint(breakpoint).copied()
196    }
197
198    /// Get gap for a specific breakpoint
199    pub fn get_gap(&self, breakpoint: Breakpoint) -> Option<u32> {
200        self.gap.get_breakpoint(breakpoint).copied()
201    }
202
203    /// Generate CSS classes for all breakpoints
204    pub fn to_css_classes(&self) -> String {
205        let mut classes = Vec::new();
206
207        // Add flex direction classes
208        let direction_classes = self.direction.to_css_classes(|d| d.to_class().to_string());
209        if !direction_classes.is_empty() {
210            classes.push(direction_classes);
211        }
212
213        // Add flex wrap classes
214        let wrap_classes = self.wrap.to_css_classes(|w| w.to_class().to_string());
215        if !wrap_classes.is_empty() {
216            classes.push(wrap_classes);
217        }
218
219        // Add justify content classes
220        let justify_classes = self.justify.to_css_classes(|j| j.to_class().to_string());
221        if !justify_classes.is_empty() {
222            classes.push(justify_classes);
223        }
224
225        // Add align items classes
226        let align_classes = self.align.to_css_classes(|a| a.to_class().to_string());
227        if !align_classes.is_empty() {
228            classes.push(align_classes);
229        }
230
231        // Add gap classes
232        let gap_classes = self.gap.to_css_classes(|g| {
233            if *g == 0 {
234                "gap-0".to_string()
235            } else {
236                format!("gap-{}", g)
237            }
238        });
239        if !gap_classes.is_empty() {
240            classes.push(gap_classes);
241        }
242
243        classes.join(" ")
244    }
245
246    /// Generate CSS classes for a specific screen width
247    pub fn to_css_classes_for_width(&self, screen_width: u32) -> String {
248        let mut classes = Vec::new();
249
250        // Add flex direction classes
251        if let Some(direction) = self.direction.get_for_width(screen_width) {
252            classes.push(direction.to_class().to_string());
253        }
254
255        // Add flex wrap classes
256        if let Some(wrap) = self.wrap.get_for_width(screen_width) {
257            classes.push(wrap.to_class().to_string());
258        }
259
260        // Add justify content classes
261        if let Some(justify) = self.justify.get_for_width(screen_width) {
262            classes.push(justify.to_class().to_string());
263        }
264
265        // Add align items classes
266        if let Some(align) = self.align.get_for_width(screen_width) {
267            classes.push(align.to_class().to_string());
268        }
269
270        // Add gap classes
271        if let Some(gap) = self.gap.get_for_width(screen_width) {
272            if *gap == 0 {
273                classes.push("gap-0".to_string());
274            } else {
275                classes.push(format!("gap-{}", gap));
276            }
277        }
278
279        classes.join(" ")
280    }
281}
282
283impl Default for ResponsiveFlex {
284    fn default() -> Self {
285        Self {
286            direction: ResponsiveValue::with_base(FlexDirection::Row),
287            wrap: ResponsiveValue::with_base(FlexWrap::NoWrap),
288            justify: ResponsiveValue::with_base(JustifyContent::Start),
289            align: ResponsiveValue::with_base(AlignItems::Stretch),
290            gap: ResponsiveValue::with_base(0),
291        }
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298
299    #[test]
300    fn test_flex_direction_to_class() {
301        assert_eq!(FlexDirection::Row.to_class(), "flex-row");
302        assert_eq!(FlexDirection::RowReverse.to_class(), "flex-row-reverse");
303        assert_eq!(FlexDirection::Column.to_class(), "flex-col");
304        assert_eq!(FlexDirection::ColumnReverse.to_class(), "flex-col-reverse");
305    }
306
307    #[test]
308    fn test_flex_wrap_to_class() {
309        assert_eq!(FlexWrap::NoWrap.to_class(), "flex-nowrap");
310        assert_eq!(FlexWrap::Wrap.to_class(), "flex-wrap");
311        assert_eq!(FlexWrap::WrapReverse.to_class(), "flex-wrap-reverse");
312    }
313
314    #[test]
315    fn test_justify_content_to_class() {
316        assert_eq!(JustifyContent::Start.to_class(), "justify-start");
317        assert_eq!(JustifyContent::End.to_class(), "justify-end");
318        assert_eq!(JustifyContent::Center.to_class(), "justify-center");
319        assert_eq!(JustifyContent::Between.to_class(), "justify-between");
320        assert_eq!(JustifyContent::Around.to_class(), "justify-around");
321        assert_eq!(JustifyContent::Evenly.to_class(), "justify-evenly");
322    }
323
324    #[test]
325    fn test_align_items_to_class() {
326        assert_eq!(AlignItems::Start.to_class(), "items-start");
327        assert_eq!(AlignItems::End.to_class(), "items-end");
328        assert_eq!(AlignItems::Center.to_class(), "items-center");
329        assert_eq!(AlignItems::Baseline.to_class(), "items-baseline");
330        assert_eq!(AlignItems::Stretch.to_class(), "items-stretch");
331    }
332
333    #[test]
334    fn test_responsive_flex_new() {
335        let flex = ResponsiveFlex::new();
336        assert_eq!(
337            flex.get_direction(Breakpoint::Base),
338            Some(FlexDirection::Row)
339        );
340        assert_eq!(flex.get_wrap(Breakpoint::Base), Some(FlexWrap::NoWrap));
341        assert_eq!(
342            flex.get_justify(Breakpoint::Base),
343            Some(JustifyContent::Start)
344        );
345        assert_eq!(flex.get_align(Breakpoint::Base), Some(AlignItems::Stretch));
346        assert_eq!(flex.get_gap(Breakpoint::Base), Some(0));
347    }
348
349    #[test]
350    fn test_responsive_flex_with_base() {
351        let flex = ResponsiveFlex::with_base(
352            FlexDirection::Column,
353            FlexWrap::Wrap,
354            JustifyContent::Center,
355            AlignItems::Center,
356            4,
357        );
358
359        assert_eq!(
360            flex.get_direction(Breakpoint::Base),
361            Some(FlexDirection::Column)
362        );
363        assert_eq!(flex.get_wrap(Breakpoint::Base), Some(FlexWrap::Wrap));
364        assert_eq!(
365            flex.get_justify(Breakpoint::Base),
366            Some(JustifyContent::Center)
367        );
368        assert_eq!(flex.get_align(Breakpoint::Base), Some(AlignItems::Center));
369        assert_eq!(flex.get_gap(Breakpoint::Base), Some(4));
370    }
371
372    #[test]
373    fn test_responsive_flex_set_get() {
374        let mut flex = ResponsiveFlex::new();
375
376        flex.set_direction(Breakpoint::Sm, FlexDirection::Column);
377        flex.set_wrap(Breakpoint::Md, FlexWrap::Wrap);
378        flex.set_justify(Breakpoint::Lg, JustifyContent::Between);
379        flex.set_align(Breakpoint::Xl, AlignItems::Center);
380        flex.set_gap(Breakpoint::Xl2, 8);
381
382        assert_eq!(
383            flex.get_direction(Breakpoint::Sm),
384            Some(FlexDirection::Column)
385        );
386        assert_eq!(flex.get_wrap(Breakpoint::Md), Some(FlexWrap::Wrap));
387        assert_eq!(
388            flex.get_justify(Breakpoint::Lg),
389            Some(JustifyContent::Between)
390        );
391        assert_eq!(flex.get_align(Breakpoint::Xl), Some(AlignItems::Center));
392        assert_eq!(flex.get_gap(Breakpoint::Xl2), Some(8));
393    }
394
395    #[test]
396    fn test_responsive_flex_to_css_classes() {
397        let mut flex = ResponsiveFlex::new();
398        flex.set_direction(Breakpoint::Sm, FlexDirection::Column);
399        flex.set_justify(Breakpoint::Md, JustifyContent::Center);
400        flex.set_gap(Breakpoint::Lg, 4);
401
402        let classes = flex.to_css_classes();
403        assert!(classes.contains("flex-row"));
404        assert!(classes.contains("sm:flex-col"));
405        assert!(classes.contains("md:justify-center"));
406        assert!(classes.contains("lg:gap-4"));
407    }
408
409    #[test]
410    fn test_responsive_flex_to_css_classes_for_width() {
411        let mut flex = ResponsiveFlex::new();
412        flex.set_direction(Breakpoint::Sm, FlexDirection::Column);
413        flex.set_justify(Breakpoint::Md, JustifyContent::Center);
414        flex.set_gap(Breakpoint::Lg, 4);
415
416        // Test width 0 (base only)
417        let classes_0 = flex.to_css_classes_for_width(0);
418        assert!(classes_0.contains("flex-row"));
419        assert!(!classes_0.contains("flex-col"));
420        assert!(!classes_0.contains("justify-center"));
421        assert!(!classes_0.contains("gap-4"));
422
423        // Test width 640 (sm active)
424        let classes_640 = flex.to_css_classes_for_width(640);
425        assert!(classes_640.contains("flex-col"));
426        assert!(!classes_640.contains("justify-center"));
427        assert!(!classes_640.contains("gap-4"));
428
429        // Test width 768 (md active)
430        let classes_768 = flex.to_css_classes_for_width(768);
431        assert!(classes_768.contains("flex-col"));
432        assert!(classes_768.contains("justify-center"));
433        assert!(!classes_768.contains("gap-4"));
434
435        // Test width 1024 (lg active)
436        let classes_1024 = flex.to_css_classes_for_width(1024);
437        assert!(classes_1024.contains("flex-col"));
438        assert!(classes_1024.contains("justify-center"));
439        assert!(classes_1024.contains("gap-4"));
440    }
441}