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.custom.contains_key("spacing"));
333        assert_eq!(result.custom.get("spacing"), Some(&"1rem".to_string()));
334        assert!(result.classes.contains("container-small"));
335        assert!(result.classes.contains("container-medium"));
336        assert!(result.classes.contains("container-large"));
337    }
338
339    #[test]
340    fn test_modern_css_features_css_values() {
341        assert_eq!(CascadeLayer::Base.to_css_value(), "base");
342        assert_eq!(CascadeLayer::Components.to_css_value(), "components");
343        assert_eq!(CascadeLayer::Utilities.to_css_value(), "utilities");
344        assert_eq!(CustomProperty::Color("red".to_string()).to_css_value(), "--color: red");
345        assert_eq!(CustomProperty::Spacing("1rem".to_string()).to_css_value(), "--spacing: 1rem");
346        assert_eq!(ModernContainerQuery::Small.to_css_value(), "small");
347        assert_eq!(ModernContainerQuery::Medium.to_css_value(), "medium");
348        assert_eq!(ModernContainerQuery::Large.to_css_value(), "large");
349    }
350
351    #[test]
352    fn test_modern_css_features_serialization() {
353        let layer = CascadeLayer::Base;
354        let serialized = serde_json::to_string(&layer).unwrap();
355        let deserialized: CascadeLayer = serde_json::from_str(&serialized).unwrap();
356        assert_eq!(layer, deserialized);
357
358        let property = CustomProperty::Color("red".to_string());
359        let serialized = serde_json::to_string(&property).unwrap();
360        let deserialized: CustomProperty = serde_json::from_str(&serialized).unwrap();
361        assert_eq!(property, deserialized);
362
363        let query = ModernContainerQuery::Small;
364        let serialized = serde_json::to_string(&query).unwrap();
365        let deserialized: ModernContainerQuery = serde_json::from_str(&serialized).unwrap();
366        assert_eq!(query, deserialized);
367    }
368
369    #[test]
370    fn test_modern_css_features_comprehensive_usage() {
371        let classes = ClassBuilder::new()
372            .layer_custom("theme")
373            .custom_property("primary-color", "#3b82f6")
374            .custom_property("secondary-color", "#64748b")
375            .container_custom("sidebar")
376            .container_custom("main");
377
378        let result = classes.build();
379        assert!(result.classes.contains("layer-theme"));
380        assert!(result.custom.contains_key("primary-color"));
381        assert_eq!(result.custom.get("primary-color"), Some(&"#3b82f6".to_string()));
382        assert!(result.custom.contains_key("secondary-color"));
383        assert_eq!(result.custom.get("secondary-color"), Some(&"#64748b".to_string()));
384        assert!(result.classes.contains("container-sidebar"));
385        assert!(result.classes.contains("container-main"));
386    }
387
388    #[test]
389    fn test_modern_css_features_custom_values() {
390        let classes = ClassBuilder::new()
391            .layer_custom_value(CascadeLayer::Custom("theme".to_string()))
392            .custom_property_value(CustomProperty::Color("blue".to_string()))
393            .custom_property_value(CustomProperty::Spacing("2rem".to_string()))
394            .container_custom_value(ModernContainerQuery::Custom("sidebar".to_string()));
395
396        let result = classes.build();
397        assert!(result.classes.contains("layer-theme"));
398        assert!(result.classes.contains("custom-color"));
399        assert!(result.classes.contains("custom-spacing"));
400        assert!(result.classes.contains("container-sidebar"));
401    }
402
403    #[test]
404    fn test_modern_css_features_all_variants() {
405        let classes = ClassBuilder::new()
406            .layer_base()
407            .layer_components()
408            .layer_utilities()
409            .layer_custom("theme")
410            .custom_property("color", "red")
411            .custom_property("spacing", "1rem")
412            .custom_property("font-size", "16px")
413            .custom_property("font-weight", "bold")
414            .custom_property("line-height", "1.5")
415            .custom_property("border-radius", "8px")
416            .custom_property("box-shadow", "0 4px 6px -1px rgb(0 0 0 / 0.1)")
417            .custom_property("z-index", "10")
418            .custom_property("opacity", "0.8")
419            .custom_property("transform", "rotate(45deg)")
420            .custom_property("animation", "fadeIn 0.5s ease-in-out")
421            .custom_property("transition", "all 0.3s ease")
422            .container_small()
423            .container_medium()
424            .container_large()
425            .container_extra_large()
426            .container_custom("sidebar");
427
428        let result = classes.build();
429        assert!(result.classes.contains("layer-base"));
430        assert!(result.classes.contains("layer-components"));
431        assert!(result.classes.contains("layer-utilities"));
432        assert!(result.classes.contains("layer-theme"));
433        // Check custom properties
434        assert!(result.custom.contains_key("color"));
435        assert_eq!(result.custom.get("color"), Some(&"red".to_string()));
436        assert!(result.custom.contains_key("spacing"));
437        assert_eq!(result.custom.get("spacing"), Some(&"1rem".to_string()));
438        assert!(result.custom.contains_key("font-size"));
439        assert_eq!(result.custom.get("font-size"), Some(&"16px".to_string()));
440        assert!(result.custom.contains_key("font-weight"));
441        assert_eq!(result.custom.get("font-weight"), Some(&"bold".to_string()));
442        assert!(result.custom.contains_key("line-height"));
443        assert_eq!(result.custom.get("line-height"), Some(&"1.5".to_string()));
444        assert!(result.custom.contains_key("border-radius"));
445        assert_eq!(result.custom.get("border-radius"), Some(&"8px".to_string()));
446        assert!(result.custom.contains_key("box-shadow"));
447        assert_eq!(result.custom.get("box-shadow"), Some(&"0 4px 6px -1px rgb(0 0 0 / 0.1)".to_string()));
448        assert!(result.custom.contains_key("z-index"));
449        assert_eq!(result.custom.get("z-index"), Some(&"10".to_string()));
450        assert!(result.custom.contains_key("opacity"));
451        assert_eq!(result.custom.get("opacity"), Some(&"0.8".to_string()));
452        assert!(result.custom.contains_key("transform"));
453        assert_eq!(result.custom.get("transform"), Some(&"rotate(45deg)".to_string()));
454        assert!(result.custom.contains_key("animation"));
455        assert_eq!(result.custom.get("animation"), Some(&"fadeIn 0.5s ease-in-out".to_string()));
456        assert!(result.custom.contains_key("transition"));
457        assert_eq!(result.custom.get("transition"), Some(&"all 0.3s ease".to_string()));
458        assert!(result.classes.contains("container-small"));
459        assert!(result.classes.contains("container-medium"));
460        assert!(result.classes.contains("container-large"));
461        assert!(result.classes.contains("container-extra-large"));
462        assert!(result.classes.contains("container-sidebar"));
463    }
464}