1use crate::error::{Result, TailwindError};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum Breakpoint {
10    Base,
12    Sm,
14    Md,
16    Lg,
18    Xl,
20    Xl2,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub enum State {
27    Hover,
29    Focus,
31    Active,
33    Visited,
35    Disabled,
37    Checked,
39    Indeterminate,
41    Required,
43    Valid,
45    Invalid,
47    InRange,
49    OutOfRange,
51    ReadOnly,
53    ReadWrite,
55    Optional,
57    PlaceholderShown,
59    Autofill,
61    Default,
63    FirstChild,
65    LastChild,
67    OnlyChild,
69    FirstOfType,
71    LastOfType,
73    OnlyOfType,
75    Empty,
77    Target,
79    Root,
81    Not,
83    Where,
85    Is,
87    Has,
89    Before,
91    After,
93    FirstLetter,
95    FirstLine,
97    Selection,
99    Marker,
101    Placeholder,
103    File,
105    Backdrop,
107    AnyLink,
109    Link,
111    LocalLink,
113    Scope,
115    Current,
117    Past,
119    Future,
121    Playing,
123    Paused,
125    Seeking,
127    Buffering,
129    Stalled,
131    Muted,
133    VolumeLocked,
135    UserInvalid,
137    UserValid,
139    Modal,
141    PictureInPicture,
143    Fullscreen,
145    Resize,
147    Scroll,
149    Snap,
151    Touch,
153    UserSelect,
155    WillChange,
157    AccentColor,
159    Appearance,
161    Cursor,
163    Outline,
165}
166
167impl State {
168    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    #[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    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    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    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    #[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#[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    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    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    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    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#[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    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    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    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#[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    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    pub fn sm(mut self, classes: &str) -> Self {
510        self.sm = Some(classes.to_string());
511        self
512    }
513
514    pub fn md(mut self, classes: &str) -> Self {
516        self.md = Some(classes.to_string());
517        self
518    }
519
520    pub fn lg(mut self, classes: &str) -> Self {
522        self.lg = Some(classes.to_string());
523        self
524    }
525
526    pub fn xl(mut self, classes: &str) -> Self {
528        self.xl = Some(classes.to_string());
529        self
530    }
531
532    pub fn xl2(mut self, classes: &str) -> Self {
534        self.xl2 = Some(classes.to_string());
535        self
536    }
537
538    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#[derive(Debug, Clone)]
570pub struct ResponsiveBuilder {
571    base_classes: Vec<String>,
572    breakpoint_classes: HashMap<Breakpoint, Vec<String>>,
573}
574
575impl ResponsiveBuilder {
576    pub fn new() -> Self {
578        Self {
579            base_classes: Vec::new(),
580            breakpoint_classes: HashMap::new(),
581        }
582    }
583
584    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    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    pub fn build(self) -> String {
603        let mut classes = Vec::new();
604
605        classes.extend(self.base_classes);
607
608        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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
631pub enum FlexDirection {
632    Row,
633    Column,
634    RowReverse,
635    ColumnReverse,
636}
637
638impl FlexDirection {
639    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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
652pub enum FlexWrap {
653    NoWrap,
654    Wrap,
655    WrapReverse,
656}
657
658impl FlexWrap {
659    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#[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    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#[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    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#[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    pub fn new() -> Self {
728        Self {
729            cols: HashMap::new(),
730            gap: HashMap::new(),
731            auto_fit: false,
732        }
733    }
734
735    pub fn cols(mut self, breakpoint: Breakpoint, count: u32) -> Self {
737        self.cols.insert(breakpoint, count);
738        self
739    }
740
741    pub fn gap(mut self, breakpoint: Breakpoint, gap: &str) -> Self {
743        self.gap.insert(breakpoint, gap.to_string());
744        self
745    }
746
747    pub fn auto_fit(mut self, enabled: bool) -> Self {
749        self.auto_fit = enabled;
750        self
751    }
752
753    pub fn build(self) -> String {
755        let mut classes = vec!["grid".to_string()];
756
757        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        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        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#[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    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    pub fn direction(mut self, breakpoint: Breakpoint, direction: FlexDirection) -> Self {
820        self.direction.insert(breakpoint, direction);
821        self
822    }
823
824    pub fn wrap(mut self, breakpoint: Breakpoint, wrap: FlexWrap) -> Self {
826        self.wrap.insert(breakpoint, wrap);
827        self
828    }
829
830    pub fn justify(mut self, breakpoint: Breakpoint, justify: JustifyContent) -> Self {
832        self.justify.insert(breakpoint, justify);
833        self
834    }
835
836    pub fn align(mut self, breakpoint: Breakpoint, align: AlignItems) -> Self {
838        self.align.insert(breakpoint, align);
839        self
840    }
841
842    pub fn gap(mut self, breakpoint: Breakpoint, gap: &str) -> Self {
844        self.gap.insert(breakpoint, gap.to_string());
845        self
846    }
847
848    pub fn build(self) -> String {
850        let mut classes = vec!["flex".to_string()];
851
852        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        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        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        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        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#[allow(clippy::module_inception)]
924pub mod responsive {
925    use super::*;
926
927    pub fn base<T>(value: T) -> ResponsiveValue<T> {
929        ResponsiveValue::new(value)
930    }
931
932    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    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    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    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    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); }
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}