tailwind_rs_core/utilities/
modern_css_features.rs

1//! Modern CSS features utilities for tailwind-rs
2//!
3//! This module provides utilities for modern CSS features.
4//! It includes cascade layers, custom properties, and other modern CSS capabilities.
5
6use crate::classes::ClassBuilder;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10/// Cascade layer values
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub enum CascadeLayer {
13    /// Base layer
14    Base,
15    /// Components layer
16    Components,
17    /// Utilities layer
18    Utilities,
19    /// Custom layer
20    Custom(String),
21}
22
23impl fmt::Display for CascadeLayer {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            CascadeLayer::Base => write!(f, "base"),
27            CascadeLayer::Components => write!(f, "components"),
28            CascadeLayer::Utilities => write!(f, "utilities"),
29            CascadeLayer::Custom(name) => write!(f, "{}", name),
30        }
31    }
32}
33
34impl CascadeLayer {
35    /// Get the CSS class name for this cascade layer
36    pub fn to_class_name(&self) -> String {
37        match self {
38            CascadeLayer::Base => "layer-base".to_string(),
39            CascadeLayer::Components => "layer-components".to_string(),
40            CascadeLayer::Utilities => "layer-utilities".to_string(),
41            CascadeLayer::Custom(name) => format!("layer-{}", name),
42        }
43    }
44
45    /// Get the CSS value for this cascade layer
46    pub fn to_css_value(&self) -> String {
47        self.to_string()
48    }
49}
50
51/// Custom property values
52#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
53pub enum CustomProperty {
54    /// Color custom property
55    Color(String),
56    /// Spacing custom property
57    Spacing(String),
58    /// Font size custom property
59    FontSize(String),
60    /// Font weight custom property
61    FontWeight(String),
62    /// Line height custom property
63    LineHeight(String),
64    /// Border radius custom property
65    BorderRadius(String),
66    /// Box shadow custom property
67    BoxShadow(String),
68    /// Z-index custom property
69    ZIndex(String),
70    /// Opacity custom property
71    Opacity(String),
72    /// Transform custom property
73    Transform(String),
74    /// Animation custom property
75    Animation(String),
76    /// Transition custom property
77    Transition(String),
78    /// Generic custom property
79    Generic(String, String),
80}
81
82impl fmt::Display for CustomProperty {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self {
85            CustomProperty::Color(value) => write!(f, "--color: {}", value),
86            CustomProperty::Spacing(value) => write!(f, "--spacing: {}", value),
87            CustomProperty::FontSize(value) => write!(f, "--font-size: {}", value),
88            CustomProperty::FontWeight(value) => write!(f, "--font-weight: {}", value),
89            CustomProperty::LineHeight(value) => write!(f, "--line-height: {}", value),
90            CustomProperty::BorderRadius(value) => write!(f, "--border-radius: {}", value),
91            CustomProperty::BoxShadow(value) => write!(f, "--box-shadow: {}", value),
92            CustomProperty::ZIndex(value) => write!(f, "--z-index: {}", value),
93            CustomProperty::Opacity(value) => write!(f, "--opacity: {}", value),
94            CustomProperty::Transform(value) => write!(f, "--transform: {}", value),
95            CustomProperty::Animation(value) => write!(f, "--animation: {}", value),
96            CustomProperty::Transition(value) => write!(f, "--transition: {}", value),
97            CustomProperty::Generic(name, value) => write!(f, "--{}: {}", name, value),
98        }
99    }
100}
101
102impl CustomProperty {
103    /// Get the CSS class name for this custom property
104    pub fn to_class_name(&self) -> String {
105        match self {
106            CustomProperty::Color(_) => "custom-color".to_string(),
107            CustomProperty::Spacing(_) => "custom-spacing".to_string(),
108            CustomProperty::FontSize(_) => "custom-font-size".to_string(),
109            CustomProperty::FontWeight(_) => "custom-font-weight".to_string(),
110            CustomProperty::LineHeight(_) => "custom-line-height".to_string(),
111            CustomProperty::BorderRadius(_) => "custom-border-radius".to_string(),
112            CustomProperty::BoxShadow(_) => "custom-box-shadow".to_string(),
113            CustomProperty::ZIndex(_) => "custom-z-index".to_string(),
114            CustomProperty::Opacity(_) => "custom-opacity".to_string(),
115            CustomProperty::Transform(_) => "custom-transform".to_string(),
116            CustomProperty::Animation(_) => "custom-animation".to_string(),
117            CustomProperty::Transition(_) => "custom-transition".to_string(),
118            CustomProperty::Generic(name, _) => format!("custom-{}", name),
119        }
120    }
121
122    /// Get the CSS value for this custom property
123    pub fn to_css_value(&self) -> String {
124        self.to_string()
125    }
126}
127
128/// Modern container query values
129#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
130pub enum ModernContainerQuery {
131    /// Small container
132    Small,
133    /// Medium container
134    Medium,
135    /// Large container
136    Large,
137    /// Extra large container
138    ExtraLarge,
139    /// Custom container size
140    Custom(String),
141}
142
143impl fmt::Display for ModernContainerQuery {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        match self {
146            ModernContainerQuery::Small => write!(f, "small"),
147            ModernContainerQuery::Medium => write!(f, "medium"),
148            ModernContainerQuery::Large => write!(f, "large"),
149            ModernContainerQuery::ExtraLarge => write!(f, "extra-large"),
150            ModernContainerQuery::Custom(size) => write!(f, "{}", size),
151        }
152    }
153}
154
155impl ModernContainerQuery {
156    /// Get the CSS class name for this container query
157    pub fn to_class_name(&self) -> String {
158        match self {
159            ModernContainerQuery::Small => "container-small".to_string(),
160            ModernContainerQuery::Medium => "container-medium".to_string(),
161            ModernContainerQuery::Large => "container-large".to_string(),
162            ModernContainerQuery::ExtraLarge => "container-extra-large".to_string(),
163            ModernContainerQuery::Custom(size) => format!("container-{}", size),
164        }
165    }
166
167    /// Get the CSS value for this container query
168    pub fn to_css_value(&self) -> String {
169        self.to_string()
170    }
171}
172
173/// Trait for adding modern CSS features to ClassBuilder
174pub trait ModernCssFeaturesUtilities {
175    /// Set cascade layer to base
176    fn layer_base(self) -> Self;
177    /// Set cascade layer to components
178    fn layer_components(self) -> Self;
179    /// Set cascade layer to utilities
180    fn layer_utilities(self) -> Self;
181    /// Set cascade layer with custom name
182    fn layer_custom(self, name: &str) -> Self;
183    /// Set cascade layer with custom value
184    fn layer_custom_value(self, layer: CascadeLayer) -> Self;
185    /// Set custom property with name and value
186    fn custom_property(self, name: &str, _value: &str) -> Self;
187    /// Set custom property with custom value
188    fn custom_property_value(self, property: CustomProperty) -> Self;
189    /// Set container query to small
190    fn container_small(self) -> Self;
191    /// Set container query to medium
192    fn container_medium(self) -> Self;
193    /// Set container query to large
194    fn container_large(self) -> Self;
195    /// Set container query to extra large
196    fn container_extra_large(self) -> Self;
197    /// Set container query with custom size
198    fn container_custom(self, size: &str) -> Self;
199    /// Set container query with custom value
200    fn container_custom_value(self, query: ModernContainerQuery) -> Self;
201}
202
203impl ModernCssFeaturesUtilities for ClassBuilder {
204    fn layer_base(self) -> Self {
205        self.class("layer-base")
206    }
207
208    fn layer_components(self) -> Self {
209        self.class("layer-components")
210    }
211
212    fn layer_utilities(self) -> Self {
213        self.class("layer-utilities")
214    }
215
216    fn layer_custom(self, name: &str) -> Self {
217        let class_name = format!("layer-{}", name);
218        self.class(class_name)
219    }
220
221    fn layer_custom_value(self, layer: CascadeLayer) -> Self {
222        self.class(&layer.to_class_name())
223    }
224
225    fn custom_property(self, name: &str, value: &str) -> Self {
226        self.custom(name, value)
227    }
228
229    fn custom_property_value(self, property: CustomProperty) -> Self {
230        self.class(&property.to_class_name())
231    }
232
233    fn container_small(self) -> Self {
234        self.class("container-small")
235    }
236
237    fn container_medium(self) -> Self {
238        self.class("container-medium")
239    }
240
241    fn container_large(self) -> Self {
242        self.class("container-large")
243    }
244
245    fn container_extra_large(self) -> Self {
246        self.class("container-extra-large")
247    }
248
249    fn container_custom(self, size: &str) -> Self {
250        let class_name = format!("container-{}", size);
251        self.class(class_name)
252    }
253
254    fn container_custom_value(self, query: ModernContainerQuery) -> Self {
255        self.class(&query.to_class_name())
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262    use crate::classes::ClassBuilder;
263
264    #[test]
265    fn test_cascade_layer_enum_values() {
266        assert_eq!(CascadeLayer::Base.to_string(), "base");
267        assert_eq!(CascadeLayer::Components.to_string(), "components");
268        assert_eq!(CascadeLayer::Utilities.to_string(), "utilities");
269        assert_eq!(CascadeLayer::Custom("custom".to_string()).to_string(), "custom");
270    }
271
272    #[test]
273    fn test_cascade_layer_class_names() {
274        assert_eq!(CascadeLayer::Base.to_class_name(), "layer-base");
275        assert_eq!(CascadeLayer::Components.to_class_name(), "layer-components");
276        assert_eq!(CascadeLayer::Utilities.to_class_name(), "layer-utilities");
277        assert_eq!(CascadeLayer::Custom("custom".to_string()).to_class_name(), "layer-custom");
278    }
279
280    #[test]
281    fn test_custom_property_enum_values() {
282        assert_eq!(CustomProperty::Color("red".to_string()).to_string(), "--color: red");
283        assert_eq!(CustomProperty::Spacing("1rem".to_string()).to_string(), "--spacing: 1rem");
284        assert_eq!(CustomProperty::FontSize("16px".to_string()).to_string(), "--font-size: 16px");
285        assert_eq!(CustomProperty::Generic("custom".to_string(), "value".to_string()).to_string(), "--custom: value");
286    }
287
288    #[test]
289    fn test_custom_property_class_names() {
290        assert_eq!(CustomProperty::Color("red".to_string()).to_class_name(), "custom-color");
291        assert_eq!(CustomProperty::Spacing("1rem".to_string()).to_class_name(), "custom-spacing");
292        assert_eq!(CustomProperty::FontSize("16px".to_string()).to_class_name(), "custom-font-size");
293        assert_eq!(CustomProperty::Generic("custom".to_string(), "value".to_string()).to_class_name(), "custom-custom");
294    }
295
296    #[test]
297    fn test_container_query_enum_values() {
298        assert_eq!(ModernContainerQuery::Small.to_string(), "small");
299        assert_eq!(ModernContainerQuery::Medium.to_string(), "medium");
300        assert_eq!(ModernContainerQuery::Large.to_string(), "large");
301        assert_eq!(ModernContainerQuery::ExtraLarge.to_string(), "extra-large");
302        assert_eq!(ModernContainerQuery::Custom("custom".to_string()).to_string(), "custom");
303    }
304
305    #[test]
306    fn test_container_query_class_names() {
307        assert_eq!(ModernContainerQuery::Small.to_class_name(), "container-small");
308        assert_eq!(ModernContainerQuery::Medium.to_class_name(), "container-medium");
309        assert_eq!(ModernContainerQuery::Large.to_class_name(), "container-large");
310        assert_eq!(ModernContainerQuery::ExtraLarge.to_class_name(), "container-extra-large");
311        assert_eq!(ModernContainerQuery::Custom("custom".to_string()).to_class_name(), "container-custom");
312    }
313
314    #[test]
315    fn test_modern_css_features_utilities() {
316        let classes = ClassBuilder::new()
317            .layer_base()
318            .layer_components()
319            .layer_utilities()
320            .custom_property("color", "red")
321            .custom_property("spacing", "1rem")
322            .container_small()
323            .container_medium()
324            .container_large();
325
326        let result = classes.build();
327        assert!(result.classes.contains("layer-base"));
328        assert!(result.classes.contains("layer-components"));
329        assert!(result.classes.contains("layer-utilities"));
330        assert!(result.custom.contains_key("--color"));
331        assert_eq!(result.custom.get("--color"), Some(&"red".to_string()));
332        assert!(result.classes.contains("custom-spacing"));
333        assert!(result.classes.contains("container-small"));
334        assert!(result.classes.contains("container-medium"));
335        assert!(result.classes.contains("container-large"));
336    }
337
338    #[test]
339    fn test_modern_css_features_css_values() {
340        assert_eq!(CascadeLayer::Base.to_css_value(), "base");
341        assert_eq!(CascadeLayer::Components.to_css_value(), "components");
342        assert_eq!(CascadeLayer::Utilities.to_css_value(), "utilities");
343        assert_eq!(CustomProperty::Color("red".to_string()).to_css_value(), "--color: red");
344        assert_eq!(CustomProperty::Spacing("1rem".to_string()).to_css_value(), "--spacing: 1rem");
345        assert_eq!(ModernContainerQuery::Small.to_css_value(), "small");
346        assert_eq!(ModernContainerQuery::Medium.to_css_value(), "medium");
347        assert_eq!(ModernContainerQuery::Large.to_css_value(), "large");
348    }
349
350    #[test]
351    fn test_modern_css_features_serialization() {
352        let layer = CascadeLayer::Base;
353        let serialized = serde_json::to_string(&layer).unwrap();
354        let deserialized: CascadeLayer = serde_json::from_str(&serialized).unwrap();
355        assert_eq!(layer, deserialized);
356
357        let property = CustomProperty::Color("red".to_string());
358        let serialized = serde_json::to_string(&property).unwrap();
359        let deserialized: CustomProperty = serde_json::from_str(&serialized).unwrap();
360        assert_eq!(property, deserialized);
361
362        let query = ModernContainerQuery::Small;
363        let serialized = serde_json::to_string(&query).unwrap();
364        let deserialized: ModernContainerQuery = serde_json::from_str(&serialized).unwrap();
365        assert_eq!(query, deserialized);
366    }
367
368    #[test]
369    fn test_modern_css_features_comprehensive_usage() {
370        let classes = ClassBuilder::new()
371            .layer_custom("theme")
372            .custom_property("primary-color", "#3b82f6")
373            .custom_property("secondary-color", "#64748b")
374            .container_custom("sidebar")
375            .container_custom("main");
376
377        let result = classes.build();
378        assert!(result.classes.contains("layer-theme"));
379        assert!(result.custom.contains_key("--primary-color"));
380        assert_eq!(result.custom.get("--primary-color"), Some(&"blue".to_string()));
381        assert!(result.classes.contains("custom-secondary-color"));
382        assert!(result.classes.contains("container-sidebar"));
383        assert!(result.classes.contains("container-main"));
384    }
385
386    #[test]
387    fn test_modern_css_features_custom_values() {
388        let classes = ClassBuilder::new()
389            .layer_custom_value(CascadeLayer::Custom("theme".to_string()))
390            .custom_property_value(CustomProperty::Color("blue".to_string()))
391            .custom_property_value(CustomProperty::Spacing("2rem".to_string()))
392            .container_custom_value(ModernContainerQuery::Custom("sidebar".to_string()));
393
394        let result = classes.build();
395        assert!(result.classes.contains("layer-theme"));
396        assert!(result.custom.contains_key("--color"));
397        assert_eq!(result.custom.get("--color"), Some(&"red".to_string()));
398        assert!(result.classes.contains("custom-spacing"));
399        assert!(result.classes.contains("container-sidebar"));
400    }
401
402    #[test]
403    fn test_modern_css_features_all_variants() {
404        let classes = ClassBuilder::new()
405            .layer_base()
406            .layer_components()
407            .layer_utilities()
408            .layer_custom("theme")
409            .custom_property("color", "red")
410            .custom_property("spacing", "1rem")
411            .custom_property("font-size", "16px")
412            .custom_property("font-weight", "bold")
413            .custom_property("line-height", "1.5")
414            .custom_property("border-radius", "8px")
415            .custom_property("box-shadow", "0 4px 6px -1px rgb(0 0 0 / 0.1)")
416            .custom_property("z-index", "10")
417            .custom_property("opacity", "0.8")
418            .custom_property("transform", "rotate(45deg)")
419            .custom_property("animation", "fadeIn 0.5s ease-in-out")
420            .custom_property("transition", "all 0.3s ease")
421            .container_small()
422            .container_medium()
423            .container_large()
424            .container_extra_large()
425            .container_custom("sidebar");
426
427        let result = classes.build();
428        assert!(result.classes.contains("layer-base"));
429        assert!(result.classes.contains("layer-components"));
430        assert!(result.classes.contains("layer-utilities"));
431        assert!(result.classes.contains("layer-theme"));
432        // Check custom properties
433        assert!(result.custom.contains_key("--color"));
434        assert_eq!(result.custom.get("--color"), Some(&"red".to_string()));
435        assert!(result.custom.contains_key("--spacing"));
436        assert_eq!(result.custom.get("--spacing"), Some(&"1rem".to_string()));
437        assert!(result.custom.contains_key("--font-size"));
438        assert_eq!(result.custom.get("--font-size"), Some(&"16px".to_string()));
439        assert!(result.custom.contains_key("--font-weight"));
440        assert_eq!(result.custom.get("--font-weight"), Some(&"bold".to_string()));
441        assert!(result.custom.contains_key("--line-height"));
442        assert_eq!(result.custom.get("--line-height"), Some(&"1.5".to_string()));
443        assert!(result.custom.contains_key("--border-radius"));
444        assert_eq!(result.custom.get("--border-radius"), Some(&"8px".to_string()));
445        assert!(result.custom.contains_key("--box-shadow"));
446        assert_eq!(result.custom.get("--box-shadow"), Some(&"0 4px 6px -1px rgb(0 0 0 / 0.1)".to_string()));
447        assert!(result.custom.contains_key("--z-index"));
448        assert_eq!(result.custom.get("--z-index"), Some(&"10".to_string()));
449        assert!(result.custom.contains_key("--opacity"));
450        assert_eq!(result.custom.get("--opacity"), Some(&"0.8".to_string()));
451        assert!(result.custom.contains_key("--transform"));
452        assert_eq!(result.custom.get("--transform"), Some(&"rotate(45deg)".to_string()));
453        assert!(result.custom.contains_key("--animation"));
454        assert_eq!(result.custom.get("--animation"), Some(&"fadeIn 0.5s ease-in-out".to_string()));
455        assert!(result.custom.contains_key("--transition"));
456        assert_eq!(result.custom.get("--transition"), Some(&"all 0.3s ease".to_string()));
457        assert!(result.classes.contains("container-small"));
458        assert!(result.classes.contains("container-medium"));
459        assert!(result.classes.contains("container-large"));
460        assert!(result.classes.contains("container-extra-large"));
461        assert!(result.classes.contains("container-sidebar"));
462    }
463}