tailwind_rs_core/
responsive.rs

1//! Responsive design system for tailwind-rs
2
3use crate::error::{Result, TailwindError};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Breakpoint definitions for responsive design
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum Breakpoint {
10    /// Base breakpoint (no prefix)
11    Base,
12    /// Small screens (640px and up)
13    Sm,
14    /// Medium screens (768px and up)
15    Md,
16    /// Large screens (1024px and up)
17    Lg,
18    /// Extra large screens (1280px and up)
19    Xl,
20    /// 2X large screens (1536px and up)
21    Xl2,
22}
23
24/// State definitions for pseudo-classes
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub enum State {
27    /// Hover state
28    Hover,
29    /// Focus state
30    Focus,
31    /// Active state
32    Active,
33    /// Visited state
34    Visited,
35    /// Disabled state
36    Disabled,
37    /// Checked state
38    Checked,
39    /// Indeterminate state
40    Indeterminate,
41    /// Required state
42    Required,
43    /// Valid state
44    Valid,
45    /// Invalid state
46    Invalid,
47    /// In-range state
48    InRange,
49    /// Out-of-range state
50    OutOfRange,
51    /// Read-only state
52    ReadOnly,
53    /// Read-write state
54    ReadWrite,
55    /// Optional state
56    Optional,
57    /// Placeholder-shown state
58    PlaceholderShown,
59    /// Autofill state
60    Autofill,
61    /// Default state
62    Default,
63    /// First-child state
64    FirstChild,
65    /// Last-child state
66    LastChild,
67    /// Only-child state
68    OnlyChild,
69    /// First-of-type state
70    FirstOfType,
71    /// Last-of-type state
72    LastOfType,
73    /// Only-of-type state
74    OnlyOfType,
75    /// Empty state
76    Empty,
77    /// Target state
78    Target,
79    /// Root state
80    Root,
81    /// Not state
82    Not,
83    /// Where state
84    Where,
85    /// Is state
86    Is,
87    /// Has state
88    Has,
89    /// Before state
90    Before,
91    /// After state
92    After,
93    /// First-letter state
94    FirstLetter,
95    /// First-line state
96    FirstLine,
97    /// Selection state
98    Selection,
99    /// Marker state
100    Marker,
101    /// Placeholder state
102    Placeholder,
103    /// File state
104    File,
105    /// Backdrop state
106    Backdrop,
107    /// Any-link state
108    AnyLink,
109    /// Link state
110    Link,
111    /// Local-link state
112    LocalLink,
113    /// Scope state
114    Scope,
115    /// Current state
116    Current,
117    /// Past state
118    Past,
119    /// Future state
120    Future,
121    /// Playing state
122    Playing,
123    /// Paused state
124    Paused,
125    /// Seeking state
126    Seeking,
127    /// Buffering state
128    Buffering,
129    /// Stalled state
130    Stalled,
131    /// Muted state
132    Muted,
133    /// Volume-locked state
134    VolumeLocked,
135    /// User-invalid state
136    UserInvalid,
137    /// User-valid state
138    UserValid,
139    /// Modal state
140    Modal,
141    /// Picture-in-picture state
142    PictureInPicture,
143    /// Fullscreen state
144    Fullscreen,
145    /// Resize state
146    Resize,
147    /// Scroll state
148    Scroll,
149    /// Snap state
150    Snap,
151    /// Touch state
152    Touch,
153    /// User-select state
154    UserSelect,
155    /// Will-change state
156    WillChange,
157    /// Accent-color state
158    AccentColor,
159    /// Appearance state
160    Appearance,
161    /// Cursor state
162    Cursor,
163    /// Outline state
164    Outline,
165}
166
167impl State {
168    /// Get the CSS prefix for this state
169    pub fn prefix(&self) -> String {
170        match self {
171            State::Hover => "hover:".to_string(),
172            State::Focus => "focus:".to_string(),
173            State::Active => "active:".to_string(),
174            State::Visited => "visited:".to_string(),
175            State::Disabled => "disabled:".to_string(),
176            State::Checked => "checked:".to_string(),
177            State::Indeterminate => "indeterminate:".to_string(),
178            State::Required => "required:".to_string(),
179            State::Valid => "valid:".to_string(),
180            State::Invalid => "invalid:".to_string(),
181            State::InRange => "in-range:".to_string(),
182            State::OutOfRange => "out-of-range:".to_string(),
183            State::ReadOnly => "read-only:".to_string(),
184            State::ReadWrite => "read-write:".to_string(),
185            State::Optional => "optional:".to_string(),
186            State::PlaceholderShown => "placeholder-shown:".to_string(),
187            State::Autofill => "autofill:".to_string(),
188            State::Default => "default:".to_string(),
189            State::FirstChild => "first-child:".to_string(),
190            State::LastChild => "last-child:".to_string(),
191            State::OnlyChild => "only-child:".to_string(),
192            State::FirstOfType => "first-of-type:".to_string(),
193            State::LastOfType => "last-of-type:".to_string(),
194            State::OnlyOfType => "only-of-type:".to_string(),
195            State::Empty => "empty:".to_string(),
196            State::Target => "target:".to_string(),
197            State::Root => "root:".to_string(),
198            State::Not => "not:".to_string(),
199            State::Where => "where:".to_string(),
200            State::Is => "is:".to_string(),
201            State::Has => "has:".to_string(),
202            State::Before => "before:".to_string(),
203            State::After => "after:".to_string(),
204            State::FirstLetter => "first-letter:".to_string(),
205            State::FirstLine => "first-line:".to_string(),
206            State::Selection => "selection:".to_string(),
207            State::Marker => "marker:".to_string(),
208            State::Placeholder => "placeholder:".to_string(),
209            State::File => "file:".to_string(),
210            State::Backdrop => "backdrop:".to_string(),
211            State::AnyLink => "any-link:".to_string(),
212            State::Link => "link:".to_string(),
213            State::LocalLink => "local-link:".to_string(),
214            State::Scope => "scope:".to_string(),
215            State::Current => "current:".to_string(),
216            State::Past => "past:".to_string(),
217            State::Future => "future:".to_string(),
218            State::Playing => "playing:".to_string(),
219            State::Paused => "paused:".to_string(),
220            State::Seeking => "seeking:".to_string(),
221            State::Buffering => "buffering:".to_string(),
222            State::Stalled => "stalled:".to_string(),
223            State::Muted => "muted:".to_string(),
224            State::VolumeLocked => "volume-locked:".to_string(),
225            State::UserInvalid => "user-invalid:".to_string(),
226            State::UserValid => "user-valid:".to_string(),
227            State::Modal => "modal:".to_string(),
228            State::PictureInPicture => "picture-in-picture:".to_string(),
229            State::Fullscreen => "fullscreen:".to_string(),
230            State::Resize => "resize:".to_string(),
231            State::Scroll => "scroll:".to_string(),
232            State::Snap => "snap:".to_string(),
233            State::Touch => "touch:".to_string(),
234            State::UserSelect => "user-select:".to_string(),
235            State::WillChange => "will-change:".to_string(),
236            State::AccentColor => "accent-color:".to_string(),
237            State::Appearance => "appearance:".to_string(),
238            State::Cursor => "cursor:".to_string(),
239            State::Outline => "outline:".to_string(),
240        }
241    }
242
243    /// Parse a state from a string
244    #[allow(clippy::should_implement_trait)]
245    pub fn from_str(s: &str) -> Result<Self> {
246        match s {
247            "hover" => Ok(State::Hover),
248            "focus" => Ok(State::Focus),
249            "active" => Ok(State::Active),
250            "visited" => Ok(State::Visited),
251            "disabled" => Ok(State::Disabled),
252            "checked" => Ok(State::Checked),
253            "indeterminate" => Ok(State::Indeterminate),
254            "required" => Ok(State::Required),
255            "valid" => Ok(State::Valid),
256            "invalid" => Ok(State::Invalid),
257            "in-range" => Ok(State::InRange),
258            "out-of-range" => Ok(State::OutOfRange),
259            "read-only" => Ok(State::ReadOnly),
260            "read-write" => Ok(State::ReadWrite),
261            "optional" => Ok(State::Optional),
262            "placeholder-shown" => Ok(State::PlaceholderShown),
263            "autofill" => Ok(State::Autofill),
264            "default" => Ok(State::Default),
265            "first-child" => Ok(State::FirstChild),
266            "last-child" => Ok(State::LastChild),
267            "only-child" => Ok(State::OnlyChild),
268            "first-of-type" => Ok(State::FirstOfType),
269            "last-of-type" => Ok(State::LastOfType),
270            "only-of-type" => Ok(State::OnlyOfType),
271            "empty" => Ok(State::Empty),
272            "target" => Ok(State::Target),
273            "root" => Ok(State::Root),
274            "not" => Ok(State::Not),
275            "where" => Ok(State::Where),
276            "is" => Ok(State::Is),
277            "has" => Ok(State::Has),
278            "before" => Ok(State::Before),
279            "after" => Ok(State::After),
280            "first-letter" => Ok(State::FirstLetter),
281            "first-line" => Ok(State::FirstLine),
282            "selection" => Ok(State::Selection),
283            "marker" => Ok(State::Marker),
284            "placeholder" => Ok(State::Placeholder),
285            "file" => Ok(State::File),
286            "backdrop" => Ok(State::Backdrop),
287            "any-link" => Ok(State::AnyLink),
288            "link" => Ok(State::Link),
289            "local-link" => Ok(State::LocalLink),
290            "scope" => Ok(State::Scope),
291            "current" => Ok(State::Current),
292            "past" => Ok(State::Past),
293            "future" => Ok(State::Future),
294            "playing" => Ok(State::Playing),
295            "paused" => Ok(State::Paused),
296            "seeking" => Ok(State::Seeking),
297            "buffering" => Ok(State::Buffering),
298            "stalled" => Ok(State::Stalled),
299            "muted" => Ok(State::Muted),
300            "volume-locked" => Ok(State::VolumeLocked),
301            "user-invalid" => Ok(State::UserInvalid),
302            "user-valid" => Ok(State::UserValid),
303            "modal" => Ok(State::Modal),
304            "picture-in-picture" => Ok(State::PictureInPicture),
305            "fullscreen" => Ok(State::Fullscreen),
306            "resize" => Ok(State::Resize),
307            "scroll" => Ok(State::Scroll),
308            "snap" => Ok(State::Snap),
309            "touch" => Ok(State::Touch),
310            "user-select" => Ok(State::UserSelect),
311            "will-change" => Ok(State::WillChange),
312            "accent-color" => Ok(State::AccentColor),
313            "appearance" => Ok(State::Appearance),
314            "cursor" => Ok(State::Cursor),
315            "outline" => Ok(State::Outline),
316            _ => Err(TailwindError::config(format!("Invalid state: {}", s))),
317        }
318    }
319}
320
321impl Breakpoint {
322    /// Get the minimum width for this breakpoint in pixels
323    pub fn min_width(&self) -> u32 {
324        match self {
325            Breakpoint::Base => 0,
326            Breakpoint::Sm => 640,
327            Breakpoint::Md => 768,
328            Breakpoint::Lg => 1024,
329            Breakpoint::Xl => 1280,
330            Breakpoint::Xl2 => 1536,
331        }
332    }
333
334    /// Get the CSS media query for this breakpoint
335    pub fn media_query(&self) -> String {
336        match self {
337            Breakpoint::Base => String::new(),
338            _ => format!("(min-width: {}px)", self.min_width()),
339        }
340    }
341
342    /// Get the Tailwind CSS prefix for this breakpoint
343    pub fn prefix(&self) -> String {
344        match self {
345            Breakpoint::Base => String::new(),
346            Breakpoint::Sm => "sm:".to_string(),
347            Breakpoint::Md => "md:".to_string(),
348            Breakpoint::Lg => "lg:".to_string(),
349            Breakpoint::Xl => "xl:".to_string(),
350            Breakpoint::Xl2 => "2xl:".to_string(),
351        }
352    }
353
354    /// Parse a breakpoint from a string
355    #[allow(clippy::should_implement_trait)]
356    pub fn from_str(s: &str) -> Result<Self> {
357        match s {
358            "" | "base" => Ok(Breakpoint::Base),
359            "sm" => Ok(Breakpoint::Sm),
360            "md" => Ok(Breakpoint::Md),
361            "lg" => Ok(Breakpoint::Lg),
362            "xl" => Ok(Breakpoint::Xl),
363            "2xl" => Ok(Breakpoint::Xl2),
364            _ => Err(TailwindError::config(format!("Invalid breakpoint: {}", s))),
365        }
366    }
367}
368
369/// Represents a responsive value that can have different values at different breakpoints
370#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
371pub struct ResponsiveValue<T> {
372    pub base: T,
373    pub sm: Option<T>,
374    pub md: Option<T>,
375    pub lg: Option<T>,
376    pub xl: Option<T>,
377    pub xl2: Option<T>,
378}
379
380impl<T> ResponsiveValue<T> {
381    /// Create a new responsive value with only a base value
382    pub fn new(base: T) -> Self {
383        Self {
384            base,
385            sm: None,
386            md: None,
387            lg: None,
388            xl: None,
389            xl2: None,
390        }
391    }
392
393    /// Set a value for a specific breakpoint
394    pub fn set_breakpoint(&mut self, breakpoint: Breakpoint, value: T) {
395        match breakpoint {
396            Breakpoint::Base => self.base = value,
397            Breakpoint::Sm => self.sm = Some(value),
398            Breakpoint::Md => self.md = Some(value),
399            Breakpoint::Lg => self.lg = Some(value),
400            Breakpoint::Xl => self.xl = Some(value),
401            Breakpoint::Xl2 => self.xl2 = Some(value),
402        }
403    }
404
405    /// Get a value for a specific breakpoint
406    pub fn get_breakpoint(&self, breakpoint: Breakpoint) -> &T {
407        match breakpoint {
408            Breakpoint::Base => &self.base,
409            Breakpoint::Sm => self.sm.as_ref().unwrap_or(&self.base),
410            Breakpoint::Md => self.md.as_ref().unwrap_or(&self.base),
411            Breakpoint::Lg => self.lg.as_ref().unwrap_or(&self.base),
412            Breakpoint::Xl => self.xl.as_ref().unwrap_or(&self.base),
413            Breakpoint::Xl2 => self.xl2.as_ref().unwrap_or(&self.base),
414        }
415    }
416
417    /// Get all breakpoint values as a vector
418    pub fn all_values(&self) -> Vec<(Breakpoint, &T)> {
419        let mut values = vec![(Breakpoint::Base, &self.base)];
420
421        if let Some(ref sm) = self.sm {
422            values.push((Breakpoint::Sm, sm));
423        }
424        if let Some(ref md) = self.md {
425            values.push((Breakpoint::Md, md));
426        }
427        if let Some(ref lg) = self.lg {
428            values.push((Breakpoint::Lg, lg));
429        }
430        if let Some(ref xl) = self.xl {
431            values.push((Breakpoint::Xl, xl));
432        }
433        if let Some(ref xl2) = self.xl2 {
434            values.push((Breakpoint::Xl2, xl2));
435        }
436
437        values
438    }
439}
440
441/// Responsive configuration for the theme system
442#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
443pub struct ResponsiveConfig {
444    pub breakpoints: HashMap<String, u32>,
445    pub container_centering: bool,
446    pub container_padding: ResponsiveValue<u32>,
447}
448
449impl ResponsiveConfig {
450    /// Create a new responsive configuration with default breakpoints
451    pub fn new() -> Self {
452        let mut breakpoints = HashMap::new();
453        breakpoints.insert("sm".to_string(), 640);
454        breakpoints.insert("md".to_string(), 768);
455        breakpoints.insert("lg".to_string(), 1024);
456        breakpoints.insert("xl".to_string(), 1280);
457        breakpoints.insert("2xl".to_string(), 1536);
458
459        Self {
460            breakpoints,
461            container_centering: true,
462            container_padding: ResponsiveValue::new(16),
463        }
464    }
465
466    /// Get the breakpoint value for a given name
467    pub fn get_breakpoint(&self, name: &str) -> Result<u32> {
468        self.breakpoints
469            .get(name)
470            .copied()
471            .ok_or_else(|| TailwindError::config(format!("Breakpoint '{}' not found", name)))
472    }
473
474    /// Set a custom breakpoint value
475    pub fn set_breakpoint(&mut self, name: String, value: u32) {
476        self.breakpoints.insert(name, value);
477    }
478}
479
480impl Default for ResponsiveConfig {
481    fn default() -> Self {
482        Self::new()
483    }
484}
485
486/// The primary type for responsive class generation
487#[derive(Debug, Clone, PartialEq)]
488pub struct Responsive {
489    pub sm: Option<String>,
490    pub md: Option<String>,
491    pub lg: Option<String>,
492    pub xl: Option<String>,
493    pub xl2: Option<String>,
494}
495
496impl Responsive {
497    /// Create a new responsive configuration
498    pub fn new() -> Self {
499        Self {
500            sm: None,
501            md: None,
502            lg: None,
503            xl: None,
504            xl2: None,
505        }
506    }
507
508    /// Set classes for small screens (640px+)
509    pub fn sm(mut self, classes: &str) -> Self {
510        self.sm = Some(classes.to_string());
511        self
512    }
513
514    /// Set classes for medium screens (768px+)
515    pub fn md(mut self, classes: &str) -> Self {
516        self.md = Some(classes.to_string());
517        self
518    }
519
520    /// Set classes for large screens (1024px+)
521    pub fn lg(mut self, classes: &str) -> Self {
522        self.lg = Some(classes.to_string());
523        self
524    }
525
526    /// Set classes for extra large screens (1280px+)
527    pub fn xl(mut self, classes: &str) -> Self {
528        self.xl = Some(classes.to_string());
529        self
530    }
531
532    /// Set classes for extra extra large screens (1536px+)
533    pub fn xl2(mut self, classes: &str) -> Self {
534        self.xl2 = Some(classes.to_string());
535        self
536    }
537
538    /// Build the final responsive class string
539    pub fn build(self) -> String {
540        let mut classes = Vec::new();
541
542        if let Some(sm) = self.sm {
543            classes.push(format!("sm:{}", sm));
544        }
545        if let Some(md) = self.md {
546            classes.push(format!("md:{}", md));
547        }
548        if let Some(lg) = self.lg {
549            classes.push(format!("lg:{}", lg));
550        }
551        if let Some(xl) = self.xl {
552            classes.push(format!("xl:{}", xl));
553        }
554        if let Some(xl2) = self.xl2 {
555            classes.push(format!("2xl:{}", xl2));
556        }
557
558        classes.join(" ")
559    }
560}
561
562impl Default for Responsive {
563    fn default() -> Self {
564        Self::new()
565    }
566}
567
568/// Builder pattern for responsive class generation
569#[derive(Debug, Clone)]
570pub struct ResponsiveBuilder {
571    base_classes: Vec<String>,
572    breakpoint_classes: HashMap<Breakpoint, Vec<String>>,
573}
574
575impl ResponsiveBuilder {
576    /// Create a new responsive builder
577    pub fn new() -> Self {
578        Self {
579            base_classes: Vec::new(),
580            breakpoint_classes: HashMap::new(),
581        }
582    }
583
584    /// Add base classes
585    pub fn base(mut self, classes: &str) -> Self {
586        self.base_classes
587            .extend(classes.split_whitespace().map(|s| s.to_string()));
588        self
589    }
590
591    /// Add classes for a specific breakpoint
592    pub fn breakpoint(mut self, breakpoint: Breakpoint, classes: &str) -> Self {
593        let breakpoint_classes = self
594            .breakpoint_classes
595            .entry(breakpoint)
596            .or_default();
597        breakpoint_classes.extend(classes.split_whitespace().map(|s| s.to_string()));
598        self
599    }
600
601    /// Build the final responsive class string
602    pub fn build(self) -> String {
603        let mut classes = Vec::new();
604
605        // Add base classes
606        classes.extend(self.base_classes);
607
608        // Add responsive classes sorted by breakpoint
609        let mut breakpoint_classes: Vec<(Breakpoint, Vec<String>)> =
610            self.breakpoint_classes.into_iter().collect();
611        breakpoint_classes.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
612
613        for (breakpoint, class_list) in breakpoint_classes {
614            for class in class_list {
615                classes.push(format!("{}{}", breakpoint.prefix(), class));
616            }
617        }
618
619        classes.join(" ")
620    }
621}
622
623impl Default for ResponsiveBuilder {
624    fn default() -> Self {
625        Self::new()
626    }
627}
628
629/// Flex direction values
630#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
631pub enum FlexDirection {
632    Row,
633    Column,
634    RowReverse,
635    ColumnReverse,
636}
637
638impl FlexDirection {
639    /// Returns the CSS class for the flex direction
640    pub fn class(&self) -> &'static str {
641        match self {
642            FlexDirection::Row => "flex-row",
643            FlexDirection::Column => "flex-col",
644            FlexDirection::RowReverse => "flex-row-reverse",
645            FlexDirection::ColumnReverse => "flex-col-reverse",
646        }
647    }
648}
649
650/// Flex wrap values
651#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
652pub enum FlexWrap {
653    NoWrap,
654    Wrap,
655    WrapReverse,
656}
657
658impl FlexWrap {
659    /// Returns the CSS class for the flex wrap
660    pub fn class(&self) -> &'static str {
661        match self {
662            FlexWrap::NoWrap => "flex-nowrap",
663            FlexWrap::Wrap => "flex-wrap",
664            FlexWrap::WrapReverse => "flex-wrap-reverse",
665        }
666    }
667}
668
669/// Justify content values
670#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
671pub enum JustifyContent {
672    Start,
673    End,
674    Center,
675    SpaceBetween,
676    SpaceAround,
677    SpaceEvenly,
678}
679
680impl JustifyContent {
681    /// Returns the CSS class for justify content
682    pub fn class(&self) -> &'static str {
683        match self {
684            JustifyContent::Start => "justify-start",
685            JustifyContent::End => "justify-end",
686            JustifyContent::Center => "justify-center",
687            JustifyContent::SpaceBetween => "justify-between",
688            JustifyContent::SpaceAround => "justify-around",
689            JustifyContent::SpaceEvenly => "justify-evenly",
690        }
691    }
692}
693
694/// Align items values
695#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
696pub enum AlignItems {
697    Start,
698    End,
699    Center,
700    Baseline,
701    Stretch,
702}
703
704impl AlignItems {
705    /// Returns the CSS class for align items
706    pub fn class(&self) -> &'static str {
707        match self {
708            AlignItems::Start => "items-start",
709            AlignItems::End => "items-end",
710            AlignItems::Center => "items-center",
711            AlignItems::Baseline => "items-baseline",
712            AlignItems::Stretch => "items-stretch",
713        }
714    }
715}
716
717/// Specialized responsive grid system
718#[derive(Debug, Clone)]
719pub struct ResponsiveGrid {
720    cols: HashMap<Breakpoint, u32>,
721    gap: HashMap<Breakpoint, String>,
722    auto_fit: bool,
723}
724
725impl ResponsiveGrid {
726    /// Create a new responsive grid
727    pub fn new() -> Self {
728        Self {
729            cols: HashMap::new(),
730            gap: HashMap::new(),
731            auto_fit: false,
732        }
733    }
734
735    /// Set the number of columns for a breakpoint
736    pub fn cols(mut self, breakpoint: Breakpoint, count: u32) -> Self {
737        self.cols.insert(breakpoint, count);
738        self
739    }
740
741    /// Set the gap for a breakpoint
742    pub fn gap(mut self, breakpoint: Breakpoint, gap: &str) -> Self {
743        self.gap.insert(breakpoint, gap.to_string());
744        self
745    }
746
747    /// Enable or disable auto-fit columns
748    pub fn auto_fit(mut self, enabled: bool) -> Self {
749        self.auto_fit = enabled;
750        self
751    }
752
753    /// Build the final grid class string
754    pub fn build(self) -> String {
755        let mut classes = vec!["grid".to_string()];
756
757        // Add column classes
758        let mut cols: Vec<(Breakpoint, u32)> = self.cols.into_iter().collect();
759        cols.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
760
761        for (breakpoint, count) in cols {
762            if breakpoint == Breakpoint::Base {
763                classes.push(format!("grid-cols-{}", count));
764            } else {
765                classes.push(format!("{}grid-cols-{}", breakpoint.prefix(), count));
766            }
767        }
768
769        // Add gap classes
770        let mut gaps: Vec<(Breakpoint, String)> = self.gap.into_iter().collect();
771        gaps.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
772
773        for (breakpoint, gap) in gaps {
774            if breakpoint == Breakpoint::Base {
775                classes.push(gap);
776            } else {
777                classes.push(format!("{}{}", breakpoint.prefix(), gap));
778            }
779        }
780
781        // Add auto-fit if enabled
782        if self.auto_fit {
783            classes.push("grid-cols-auto-fit".to_string());
784        }
785
786        classes.join(" ")
787    }
788}
789
790impl Default for ResponsiveGrid {
791    fn default() -> Self {
792        Self::new()
793    }
794}
795
796/// Specialized responsive flexbox system
797#[derive(Debug, Clone)]
798pub struct ResponsiveFlex {
799    direction: HashMap<Breakpoint, FlexDirection>,
800    wrap: HashMap<Breakpoint, FlexWrap>,
801    justify: HashMap<Breakpoint, JustifyContent>,
802    align: HashMap<Breakpoint, AlignItems>,
803    gap: HashMap<Breakpoint, String>,
804}
805
806impl ResponsiveFlex {
807    /// Create a new responsive flex container
808    pub fn new() -> Self {
809        Self {
810            direction: HashMap::new(),
811            wrap: HashMap::new(),
812            justify: HashMap::new(),
813            align: HashMap::new(),
814            gap: HashMap::new(),
815        }
816    }
817
818    /// Set the flex direction for a breakpoint
819    pub fn direction(mut self, breakpoint: Breakpoint, direction: FlexDirection) -> Self {
820        self.direction.insert(breakpoint, direction);
821        self
822    }
823
824    /// Set the flex wrap for a breakpoint
825    pub fn wrap(mut self, breakpoint: Breakpoint, wrap: FlexWrap) -> Self {
826        self.wrap.insert(breakpoint, wrap);
827        self
828    }
829
830    /// Set the justify content for a breakpoint
831    pub fn justify(mut self, breakpoint: Breakpoint, justify: JustifyContent) -> Self {
832        self.justify.insert(breakpoint, justify);
833        self
834    }
835
836    /// Set the align items for a breakpoint
837    pub fn align(mut self, breakpoint: Breakpoint, align: AlignItems) -> Self {
838        self.align.insert(breakpoint, align);
839        self
840    }
841
842    /// Set the gap for a breakpoint
843    pub fn gap(mut self, breakpoint: Breakpoint, gap: &str) -> Self {
844        self.gap.insert(breakpoint, gap.to_string());
845        self
846    }
847
848    /// Build the final flex class string
849    pub fn build(self) -> String {
850        let mut classes = vec!["flex".to_string()];
851
852        // Add direction classes
853        let mut directions: Vec<(Breakpoint, FlexDirection)> = self.direction.into_iter().collect();
854        directions.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
855
856        for (breakpoint, direction) in directions {
857            if breakpoint == Breakpoint::Base {
858                classes.push(direction.class().to_string());
859            } else {
860                classes.push(format!("{}{}", breakpoint.prefix(), direction.class()));
861            }
862        }
863
864        // Add wrap classes
865        let mut wraps: Vec<(Breakpoint, FlexWrap)> = self.wrap.into_iter().collect();
866        wraps.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
867
868        for (breakpoint, wrap) in wraps {
869            if breakpoint == Breakpoint::Base {
870                classes.push(wrap.class().to_string());
871            } else {
872                classes.push(format!("{}{}", breakpoint.prefix(), wrap.class()));
873            }
874        }
875
876        // Add justify classes
877        let mut justifies: Vec<(Breakpoint, JustifyContent)> = self.justify.into_iter().collect();
878        justifies.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
879
880        for (breakpoint, justify) in justifies {
881            if breakpoint == Breakpoint::Base {
882                classes.push(justify.class().to_string());
883            } else {
884                classes.push(format!("{}{}", breakpoint.prefix(), justify.class()));
885            }
886        }
887
888        // Add align classes
889        let mut aligns: Vec<(Breakpoint, AlignItems)> = self.align.into_iter().collect();
890        aligns.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
891
892        for (breakpoint, align) in aligns {
893            if breakpoint == Breakpoint::Base {
894                classes.push(align.class().to_string());
895            } else {
896                classes.push(format!("{}{}", breakpoint.prefix(), align.class()));
897            }
898        }
899
900        // Add gap classes
901        let mut gaps: Vec<(Breakpoint, String)> = self.gap.into_iter().collect();
902        gaps.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
903
904        for (breakpoint, gap) in gaps {
905            if breakpoint == Breakpoint::Base {
906                classes.push(gap);
907            } else {
908                classes.push(format!("{}{}", breakpoint.prefix(), gap));
909            }
910        }
911
912        classes.join(" ")
913    }
914}
915
916impl Default for ResponsiveFlex {
917    fn default() -> Self {
918        Self::new()
919    }
920}
921
922/// Utility functions for responsive design
923#[allow(clippy::module_inception)]
924pub mod responsive {
925    use super::*;
926
927    /// Create a responsive value with a base value
928    pub fn base<T>(value: T) -> ResponsiveValue<T> {
929        ResponsiveValue::new(value)
930    }
931
932    /// Create a responsive value with base and small breakpoint values
933    pub fn sm<T>(base: T, sm: T) -> ResponsiveValue<T> {
934        let mut responsive = ResponsiveValue::new(base);
935        responsive.set_breakpoint(Breakpoint::Sm, sm);
936        responsive
937    }
938
939    /// Create a responsive value with base, small, and medium breakpoint values
940    pub fn md<T>(base: T, sm: T, md: T) -> ResponsiveValue<T> {
941        let mut responsive = ResponsiveValue::new(base);
942        responsive.set_breakpoint(Breakpoint::Sm, sm);
943        responsive.set_breakpoint(Breakpoint::Md, md);
944        responsive
945    }
946
947    /// Create a responsive value with base, small, medium, and large breakpoint values
948    pub fn lg<T>(base: T, sm: T, md: T, lg: T) -> ResponsiveValue<T> {
949        let mut responsive = ResponsiveValue::new(base);
950        responsive.set_breakpoint(Breakpoint::Sm, sm);
951        responsive.set_breakpoint(Breakpoint::Md, md);
952        responsive.set_breakpoint(Breakpoint::Lg, lg);
953        responsive
954    }
955
956    /// Create a responsive value with all breakpoint values
957    pub fn xl<T>(base: T, sm: T, md: T, lg: T, xl: T) -> ResponsiveValue<T> {
958        let mut responsive = ResponsiveValue::new(base);
959        responsive.set_breakpoint(Breakpoint::Sm, sm);
960        responsive.set_breakpoint(Breakpoint::Md, md);
961        responsive.set_breakpoint(Breakpoint::Lg, lg);
962        responsive.set_breakpoint(Breakpoint::Xl, xl);
963        responsive
964    }
965
966    /// Create a responsive value with all breakpoint values including 2xl
967    pub fn xl2<T>(base: T, sm: T, md: T, lg: T, xl: T, xl2: T) -> ResponsiveValue<T> {
968        let mut responsive = ResponsiveValue::new(base);
969        responsive.set_breakpoint(Breakpoint::Sm, sm);
970        responsive.set_breakpoint(Breakpoint::Md, md);
971        responsive.set_breakpoint(Breakpoint::Lg, lg);
972        responsive.set_breakpoint(Breakpoint::Xl, xl);
973        responsive.set_breakpoint(Breakpoint::Xl2, xl2);
974        responsive
975    }
976}
977
978#[cfg(test)]
979mod tests {
980    use super::*;
981
982    #[test]
983    fn test_breakpoint_min_width() {
984        assert_eq!(Breakpoint::Base.min_width(), 0);
985        assert_eq!(Breakpoint::Sm.min_width(), 640);
986        assert_eq!(Breakpoint::Md.min_width(), 768);
987        assert_eq!(Breakpoint::Lg.min_width(), 1024);
988        assert_eq!(Breakpoint::Xl.min_width(), 1280);
989        assert_eq!(Breakpoint::Xl2.min_width(), 1536);
990    }
991
992    #[test]
993    fn test_breakpoint_media_query() {
994        assert_eq!(Breakpoint::Base.media_query(), "");
995        assert_eq!(Breakpoint::Sm.media_query(), "(min-width: 640px)");
996        assert_eq!(Breakpoint::Md.media_query(), "(min-width: 768px)");
997        assert_eq!(Breakpoint::Lg.media_query(), "(min-width: 1024px)");
998        assert_eq!(Breakpoint::Xl.media_query(), "(min-width: 1280px)");
999        assert_eq!(Breakpoint::Xl2.media_query(), "(min-width: 1536px)");
1000    }
1001
1002    #[test]
1003    fn test_breakpoint_prefix() {
1004        assert_eq!(Breakpoint::Base.prefix(), "");
1005        assert_eq!(Breakpoint::Sm.prefix(), "sm:");
1006        assert_eq!(Breakpoint::Md.prefix(), "md:");
1007        assert_eq!(Breakpoint::Lg.prefix(), "lg:");
1008        assert_eq!(Breakpoint::Xl.prefix(), "xl:");
1009        assert_eq!(Breakpoint::Xl2.prefix(), "2xl:");
1010    }
1011
1012    #[test]
1013    fn test_breakpoint_from_str() {
1014        assert_eq!(Breakpoint::from_str("").unwrap(), Breakpoint::Base);
1015        assert_eq!(Breakpoint::from_str("base").unwrap(), Breakpoint::Base);
1016        assert_eq!(Breakpoint::from_str("sm").unwrap(), Breakpoint::Sm);
1017        assert_eq!(Breakpoint::from_str("md").unwrap(), Breakpoint::Md);
1018        assert_eq!(Breakpoint::from_str("lg").unwrap(), Breakpoint::Lg);
1019        assert_eq!(Breakpoint::from_str("xl").unwrap(), Breakpoint::Xl);
1020        assert_eq!(Breakpoint::from_str("2xl").unwrap(), Breakpoint::Xl2);
1021
1022        assert!(Breakpoint::from_str("invalid").is_err());
1023    }
1024
1025    #[test]
1026    fn test_responsive_value_creation() {
1027        let responsive = ResponsiveValue::new(10);
1028        assert_eq!(responsive.base, 10);
1029        assert_eq!(responsive.sm, None);
1030        assert_eq!(responsive.md, None);
1031    }
1032
1033    #[test]
1034    fn test_responsive_value_set_get() {
1035        let mut responsive = ResponsiveValue::new(10);
1036        responsive.set_breakpoint(Breakpoint::Sm, 20);
1037        responsive.set_breakpoint(Breakpoint::Md, 30);
1038
1039        assert_eq!(*responsive.get_breakpoint(Breakpoint::Base), 10);
1040        assert_eq!(*responsive.get_breakpoint(Breakpoint::Sm), 20);
1041        assert_eq!(*responsive.get_breakpoint(Breakpoint::Md), 30);
1042        assert_eq!(*responsive.get_breakpoint(Breakpoint::Lg), 10); // Falls back to base
1043    }
1044
1045    #[test]
1046    fn test_responsive_value_all_values() {
1047        let mut responsive = ResponsiveValue::new(10);
1048        responsive.set_breakpoint(Breakpoint::Sm, 20);
1049        responsive.set_breakpoint(Breakpoint::Md, 30);
1050
1051        let all_values = responsive.all_values();
1052        assert_eq!(all_values.len(), 3);
1053        assert_eq!(all_values[0], (Breakpoint::Base, &10));
1054        assert_eq!(all_values[1], (Breakpoint::Sm, &20));
1055        assert_eq!(all_values[2], (Breakpoint::Md, &30));
1056    }
1057
1058    #[test]
1059    fn test_responsive_config() {
1060        let config = ResponsiveConfig::new();
1061        assert_eq!(config.get_breakpoint("sm").unwrap(), 640);
1062        assert_eq!(config.get_breakpoint("md").unwrap(), 768);
1063        assert_eq!(config.get_breakpoint("lg").unwrap(), 1024);
1064        assert_eq!(config.get_breakpoint("xl").unwrap(), 1280);
1065        assert_eq!(config.get_breakpoint("2xl").unwrap(), 1536);
1066
1067        assert!(config.get_breakpoint("invalid").is_err());
1068    }
1069
1070    #[test]
1071    fn test_responsive_utility_functions() {
1072        let responsive = responsive::base(10);
1073        assert_eq!(responsive.base, 10);
1074
1075        let responsive = responsive::sm(10, 20);
1076        assert_eq!(responsive.base, 10);
1077        assert_eq!(responsive.sm, Some(20));
1078
1079        let responsive = responsive::md(10, 20, 30);
1080        assert_eq!(responsive.base, 10);
1081        assert_eq!(responsive.sm, Some(20));
1082        assert_eq!(responsive.md, Some(30));
1083
1084        let responsive = responsive::lg(10, 20, 30, 40);
1085        assert_eq!(responsive.base, 10);
1086        assert_eq!(responsive.sm, Some(20));
1087        assert_eq!(responsive.md, Some(30));
1088        assert_eq!(responsive.lg, Some(40));
1089
1090        let responsive = responsive::xl(10, 20, 30, 40, 50);
1091        assert_eq!(responsive.base, 10);
1092        assert_eq!(responsive.sm, Some(20));
1093        assert_eq!(responsive.md, Some(30));
1094        assert_eq!(responsive.lg, Some(40));
1095        assert_eq!(responsive.xl, Some(50));
1096
1097        let responsive = responsive::xl2(10, 20, 30, 40, 50, 60);
1098        assert_eq!(responsive.base, 10);
1099        assert_eq!(responsive.sm, Some(20));
1100        assert_eq!(responsive.md, Some(30));
1101        assert_eq!(responsive.lg, Some(40));
1102        assert_eq!(responsive.xl, Some(50));
1103        assert_eq!(responsive.xl2, Some(60));
1104    }
1105
1106    #[test]
1107    fn test_responsive_creation() {
1108        let responsive = Responsive::new();
1109        assert!(responsive.sm.is_none());
1110        assert!(responsive.md.is_none());
1111    }
1112
1113    #[test]
1114    fn test_responsive_build() {
1115        let responsive = Responsive::new()
1116            .sm("text-base")
1117            .md("text-lg")
1118            .lg("text-xl");
1119
1120        let classes = responsive.build();
1121        assert!(classes.contains("sm:text-base"));
1122        assert!(classes.contains("md:text-lg"));
1123        assert!(classes.contains("lg:text-xl"));
1124    }
1125
1126    #[test]
1127    fn test_responsive_builder() {
1128        let classes = ResponsiveBuilder::new()
1129            .base("text-sm")
1130            .breakpoint(Breakpoint::Sm, "text-base")
1131            .breakpoint(Breakpoint::Md, "text-lg")
1132            .build();
1133
1134        assert!(classes.contains("text-sm"));
1135        assert!(classes.contains("sm:text-base"));
1136        assert!(classes.contains("md:text-lg"));
1137    }
1138
1139    #[test]
1140    fn test_flex_direction() {
1141        assert_eq!(FlexDirection::Row.class(), "flex-row");
1142        assert_eq!(FlexDirection::Column.class(), "flex-col");
1143        assert_eq!(FlexDirection::RowReverse.class(), "flex-row-reverse");
1144        assert_eq!(FlexDirection::ColumnReverse.class(), "flex-col-reverse");
1145    }
1146
1147    #[test]
1148    fn test_flex_wrap() {
1149        assert_eq!(FlexWrap::NoWrap.class(), "flex-nowrap");
1150        assert_eq!(FlexWrap::Wrap.class(), "flex-wrap");
1151        assert_eq!(FlexWrap::WrapReverse.class(), "flex-wrap-reverse");
1152    }
1153
1154    #[test]
1155    fn test_justify_content() {
1156        assert_eq!(JustifyContent::Start.class(), "justify-start");
1157        assert_eq!(JustifyContent::End.class(), "justify-end");
1158        assert_eq!(JustifyContent::Center.class(), "justify-center");
1159        assert_eq!(JustifyContent::SpaceBetween.class(), "justify-between");
1160        assert_eq!(JustifyContent::SpaceAround.class(), "justify-around");
1161        assert_eq!(JustifyContent::SpaceEvenly.class(), "justify-evenly");
1162    }
1163
1164    #[test]
1165    fn test_align_items() {
1166        assert_eq!(AlignItems::Start.class(), "items-start");
1167        assert_eq!(AlignItems::End.class(), "items-end");
1168        assert_eq!(AlignItems::Center.class(), "items-center");
1169        assert_eq!(AlignItems::Baseline.class(), "items-baseline");
1170        assert_eq!(AlignItems::Stretch.class(), "items-stretch");
1171    }
1172
1173    #[test]
1174    fn test_responsive_grid() {
1175        let classes = ResponsiveGrid::new()
1176            .cols(Breakpoint::Sm, 2)
1177            .cols(Breakpoint::Md, 3)
1178            .gap(Breakpoint::Sm, "gap-4")
1179            .gap(Breakpoint::Md, "gap-6")
1180            .build();
1181
1182        assert!(classes.contains("grid"));
1183        assert!(classes.contains("sm:grid-cols-2"));
1184        assert!(classes.contains("md:grid-cols-3"));
1185        assert!(classes.contains("sm:gap-4"));
1186        assert!(classes.contains("md:gap-6"));
1187    }
1188
1189    #[test]
1190    fn test_responsive_flex() {
1191        let classes = ResponsiveFlex::new()
1192            .direction(Breakpoint::Sm, FlexDirection::Column)
1193            .direction(Breakpoint::Md, FlexDirection::Row)
1194            .justify(Breakpoint::Sm, JustifyContent::Center)
1195            .justify(Breakpoint::Md, JustifyContent::SpaceBetween)
1196            .build();
1197
1198        assert!(classes.contains("flex"));
1199        assert!(classes.contains("sm:flex-col"));
1200        assert!(classes.contains("md:flex-row"));
1201        assert!(classes.contains("sm:justify-center"));
1202        assert!(classes.contains("md:justify-between"));
1203    }
1204}