tailwind_rs_core/css_generator/
variants.rs

1//! Variant Parsing and Handling
2//! 
3//! This module handles the parsing and processing of CSS variants,
4//! including responsive, state, and custom variants.
5
6use crate::responsive::Breakpoint;
7
8/// Variant parser for handling CSS variants
9#[derive(Debug, Clone)]
10pub struct VariantParser {
11    /// Supported variants
12    variants: Vec<String>,
13    /// Responsive breakpoints
14    breakpoints: Vec<Breakpoint>,
15}
16
17impl VariantParser {
18    /// Create a new variant parser
19    pub fn new() -> Self {
20        Self {
21            variants: vec![
22                "dark".to_string(),
23                "hover".to_string(),
24                "focus".to_string(),
25                "active".to_string(),
26                "visited".to_string(),
27                "disabled".to_string(),
28                "group-hover".to_string(),
29                "group-focus".to_string(),
30                "group-active".to_string(),
31                "group-disabled".to_string(),
32                "peer-hover".to_string(),
33                "peer-focus".to_string(),
34                "peer-active".to_string(),
35                "peer-disabled".to_string(),
36                "first".to_string(),
37                "last".to_string(),
38                "odd".to_string(),
39                "even".to_string(),
40                "sm".to_string(),
41                "md".to_string(),
42                "lg".to_string(),
43                "xl".to_string(),
44                "2xl".to_string(),
45            ],
46            breakpoints: vec![
47                Breakpoint::Sm,
48                Breakpoint::Md,
49                Breakpoint::Lg,
50                Breakpoint::Xl,
51                Breakpoint::Xl2,
52            ],
53        }
54    }
55
56    /// Parse variants from a class string
57    pub fn parse_variants(&self, class: &str) -> (Vec<String>, String) {
58        let mut variants = Vec::new();
59        let mut remaining = class.to_string();
60        
61        // Parse variants in order of specificity (most specific first)
62        // Check for compound variants first
63        let compound_patterns = [
64            ("dark:hover:", vec!["dark", "hover"]),
65            ("dark:group-hover:", vec!["dark", "group-hover"]),
66            ("dark:focus:", vec!["dark", "focus"]),
67            ("dark:active:", vec!["dark", "active"]),
68        ];
69        
70        for (prefix, variant_list) in compound_patterns {
71            if remaining.starts_with(prefix) {
72                variants.extend(variant_list.iter().map(|v| v.to_string()));
73                remaining = remaining.strip_prefix(prefix).unwrap_or(&remaining).to_string();
74                break;
75            }
76        }
77        
78        // If no compound variant found, check individual variants
79        if variants.is_empty() {
80            let variant_patterns = [
81                ("dark:", "dark"),
82                ("hover:", "hover"),
83                ("focus:", "focus"),
84                ("active:", "active"),
85                ("visited:", "visited"),
86                ("disabled:", "disabled"),
87                ("group-hover:", "group-hover"),
88                ("group-focus:", "group-focus"),
89                ("group-active:", "group-active"),
90                ("group-disabled:", "group-disabled"),
91                ("peer-hover:", "peer-hover"),
92                ("peer-focus:", "peer-focus"),
93                ("peer-active:", "peer-active"),
94                ("peer-disabled:", "peer-disabled"),
95                ("first:", "first"),
96                ("last:", "last"),
97                ("odd:", "odd"),
98                ("even:", "even"),
99                // Device variants
100                ("pointer-coarse:", "pointer-coarse"),
101                ("pointer-fine:", "pointer-fine"),
102                ("motion-reduce:", "motion-reduce"),
103                ("motion-safe:", "motion-safe"),
104                ("light:", "light"),
105                // Responsive variants
106                ("sm:", "sm"),
107                ("md:", "md"),
108                ("lg:", "lg"),
109                ("xl:", "xl"),
110                ("2xl:", "2xl"),
111            ];
112            
113            for (prefix, variant) in variant_patterns {
114                if remaining.starts_with(prefix) {
115                    variants.push(variant.to_string());
116                    remaining = remaining.strip_prefix(prefix).unwrap_or(&remaining).to_string();
117                    break; // Only parse one variant at a time for now
118                }
119            }
120        }
121        
122        (variants, remaining)
123    }
124
125    /// Get the CSS selector for a variant
126    pub fn get_variant_selector(&self, variant: &str) -> String {
127        match variant {
128            "dark" => ".dark ".to_string(),
129            "hover" => ":hover".to_string(),
130            "focus" => ":focus".to_string(),
131            "active" => ":active".to_string(),
132            "visited" => ":visited".to_string(),
133            "disabled" => ":disabled".to_string(),
134            "group-hover" => ".group:hover ".to_string(),
135            "group-focus" => ".group:focus ".to_string(),
136            "group-active" => ".group:active ".to_string(),
137            "group-disabled" => ".group:disabled ".to_string(),
138            "peer-hover" => ".peer:hover ".to_string(),
139            "peer-focus" => ".peer:focus ".to_string(),
140            "peer-active" => ".peer:active ".to_string(),
141            "peer-disabled" => ".peer:disabled ".to_string(),
142            "first" => ":first-child".to_string(),
143            "last" => ":last-child".to_string(),
144            "odd" => ":nth-child(odd)".to_string(),
145            "even" => ":nth-child(even)".to_string(),
146            // Device variants use media queries, not selectors
147            "pointer-coarse" | "pointer-fine" | "motion-reduce" | "motion-safe" | "light" => String::new(),
148            _ => String::new(),
149        }
150    }
151
152    /// Get the media query for device variants
153    pub fn get_device_media_query(&self, variant: &str) -> Option<String> {
154        match variant {
155            "pointer-coarse" => Some("(pointer: coarse)".to_string()),
156            "pointer-fine" => Some("(pointer: fine)".to_string()),
157            "motion-reduce" => Some("(prefers-reduced-motion: reduce)".to_string()),
158            "motion-safe" => Some("(prefers-reduced-motion: no-preference)".to_string()),
159            "light" => Some("(prefers-color-scheme: light)".to_string()),
160            _ => None,
161        }
162    }
163
164    /// Get the media query for a responsive variant
165    pub fn get_responsive_media_query(&self, variant: &str) -> Option<String> {
166        match variant {
167            "sm" => Some("(min-width: 640px)".to_string()),
168            "md" => Some("(min-width: 768px)".to_string()),
169            "lg" => Some("(min-width: 1024px)".to_string()),
170            "xl" => Some("(min-width: 1280px)".to_string()),
171            "2xl" => Some("(min-width: 1536px)".to_string()),
172            _ => None,
173        }
174    }
175
176    /// Check if a variant is supported
177    pub fn is_supported_variant(&self, variant: &str) -> bool {
178        self.variants.contains(&variant.to_string())
179    }
180
181    /// Get all supported variants
182    pub fn get_supported_variants(&self) -> &[String] {
183        &self.variants
184    }
185
186    /// Get all supported breakpoints
187    pub fn get_supported_breakpoints(&self) -> &[Breakpoint] {
188        &self.breakpoints
189    }
190}
191
192impl Default for VariantParser {
193    fn default() -> Self {
194        Self::new()
195    }
196}