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!(
270            CascadeLayer::Custom("custom".to_string()).to_string(),
271            "custom"
272        );
273    }
274
275    #[test]
276    fn test_cascade_layer_class_names() {
277        assert_eq!(CascadeLayer::Base.to_class_name(), "layer-base");
278        assert_eq!(CascadeLayer::Components.to_class_name(), "layer-components");
279        assert_eq!(CascadeLayer::Utilities.to_class_name(), "layer-utilities");
280        assert_eq!(
281            CascadeLayer::Custom("custom".to_string()).to_class_name(),
282            "layer-custom"
283        );
284    }
285
286    #[test]
287    fn test_custom_property_enum_values() {
288        assert_eq!(
289            CustomProperty::Color("red".to_string()).to_string(),
290            "--color: red"
291        );
292        assert_eq!(
293            CustomProperty::Spacing("1rem".to_string()).to_string(),
294            "--spacing: 1rem"
295        );
296        assert_eq!(
297            CustomProperty::FontSize("16px".to_string()).to_string(),
298            "--font-size: 16px"
299        );
300        assert_eq!(
301            CustomProperty::Generic("custom".to_string(), "value".to_string()).to_string(),
302            "--custom: value"
303        );
304    }
305
306    #[test]
307    fn test_custom_property_class_names() {
308        assert_eq!(
309            CustomProperty::Color("red".to_string()).to_class_name(),
310            "custom-color"
311        );
312        assert_eq!(
313            CustomProperty::Spacing("1rem".to_string()).to_class_name(),
314            "custom-spacing"
315        );
316        assert_eq!(
317            CustomProperty::FontSize("16px".to_string()).to_class_name(),
318            "custom-font-size"
319        );
320        assert_eq!(
321            CustomProperty::Generic("custom".to_string(), "value".to_string()).to_class_name(),
322            "custom-custom"
323        );
324    }
325
326    #[test]
327    fn test_container_query_enum_values() {
328        assert_eq!(ModernContainerQuery::Small.to_string(), "small");
329        assert_eq!(ModernContainerQuery::Medium.to_string(), "medium");
330        assert_eq!(ModernContainerQuery::Large.to_string(), "large");
331        assert_eq!(ModernContainerQuery::ExtraLarge.to_string(), "extra-large");
332        assert_eq!(
333            ModernContainerQuery::Custom("custom".to_string()).to_string(),
334            "custom"
335        );
336    }
337
338    #[test]
339    fn test_container_query_class_names() {
340        assert_eq!(
341            ModernContainerQuery::Small.to_class_name(),
342            "container-small"
343        );
344        assert_eq!(
345            ModernContainerQuery::Medium.to_class_name(),
346            "container-medium"
347        );
348        assert_eq!(
349            ModernContainerQuery::Large.to_class_name(),
350            "container-large"
351        );
352        assert_eq!(
353            ModernContainerQuery::ExtraLarge.to_class_name(),
354            "container-extra-large"
355        );
356        assert_eq!(
357            ModernContainerQuery::Custom("custom".to_string()).to_class_name(),
358            "container-custom"
359        );
360    }
361
362    #[test]
363    fn test_modern_css_features_utilities() {
364        let classes = ClassBuilder::new()
365            .layer_base()
366            .layer_components()
367            .layer_utilities()
368            .custom_property("color", "red")
369            .custom_property("spacing", "1rem")
370            .container_small()
371            .container_medium()
372            .container_large();
373
374        let result = classes.build();
375        assert!(result.classes.contains("layer-base"));
376        assert!(result.classes.contains("layer-components"));
377        assert!(result.classes.contains("layer-utilities"));
378        assert!(result.custom.contains_key("color"));
379        assert_eq!(result.custom.get("color"), Some(&"red".to_string()));
380        assert!(result.custom.contains_key("spacing"));
381        assert_eq!(result.custom.get("spacing"), Some(&"1rem".to_string()));
382        assert!(result.classes.contains("container-small"));
383        assert!(result.classes.contains("container-medium"));
384        assert!(result.classes.contains("container-large"));
385    }
386
387    #[test]
388    fn test_modern_css_features_css_values() {
389        assert_eq!(CascadeLayer::Base.to_css_value(), "base");
390        assert_eq!(CascadeLayer::Components.to_css_value(), "components");
391        assert_eq!(CascadeLayer::Utilities.to_css_value(), "utilities");
392        assert_eq!(
393            CustomProperty::Color("red".to_string()).to_css_value(),
394            "--color: red"
395        );
396        assert_eq!(
397            CustomProperty::Spacing("1rem".to_string()).to_css_value(),
398            "--spacing: 1rem"
399        );
400        assert_eq!(ModernContainerQuery::Small.to_css_value(), "small");
401        assert_eq!(ModernContainerQuery::Medium.to_css_value(), "medium");
402        assert_eq!(ModernContainerQuery::Large.to_css_value(), "large");
403    }
404
405    #[test]
406    fn test_modern_css_features_serialization() {
407        let layer = CascadeLayer::Base;
408        let serialized = serde_json::to_string(&layer).unwrap();
409        let deserialized: CascadeLayer = serde_json::from_str(&serialized).unwrap();
410        assert_eq!(layer, deserialized);
411
412        let property = CustomProperty::Color("red".to_string());
413        let serialized = serde_json::to_string(&property).unwrap();
414        let deserialized: CustomProperty = serde_json::from_str(&serialized).unwrap();
415        assert_eq!(property, deserialized);
416
417        let query = ModernContainerQuery::Small;
418        let serialized = serde_json::to_string(&query).unwrap();
419        let deserialized: ModernContainerQuery = serde_json::from_str(&serialized).unwrap();
420        assert_eq!(query, deserialized);
421    }
422
423    #[test]
424    fn test_modern_css_features_comprehensive_usage() {
425        let classes = ClassBuilder::new()
426            .layer_custom("theme")
427            .custom_property("primary-color", "#3b82f6")
428            .custom_property("secondary-color", "#64748b")
429            .container_custom("sidebar")
430            .container_custom("main");
431
432        let result = classes.build();
433        assert!(result.classes.contains("layer-theme"));
434        assert!(result.custom.contains_key("primary-color"));
435        assert_eq!(
436            result.custom.get("primary-color"),
437            Some(&"#3b82f6".to_string())
438        );
439        assert!(result.custom.contains_key("secondary-color"));
440        assert_eq!(
441            result.custom.get("secondary-color"),
442            Some(&"#64748b".to_string())
443        );
444        assert!(result.classes.contains("container-sidebar"));
445        assert!(result.classes.contains("container-main"));
446    }
447
448    #[test]
449    fn test_modern_css_features_custom_values() {
450        let classes = ClassBuilder::new()
451            .layer_custom_value(CascadeLayer::Custom("theme".to_string()))
452            .custom_property_value(CustomProperty::Color("blue".to_string()))
453            .custom_property_value(CustomProperty::Spacing("2rem".to_string()))
454            .container_custom_value(ModernContainerQuery::Custom("sidebar".to_string()));
455
456        let result = classes.build();
457        assert!(result.classes.contains("layer-theme"));
458        assert!(result.classes.contains("custom-color"));
459        assert!(result.classes.contains("custom-spacing"));
460        assert!(result.classes.contains("container-sidebar"));
461    }
462
463    #[test]
464    fn test_modern_css_features_all_variants() {
465        let classes = ClassBuilder::new()
466            .layer_base()
467            .layer_components()
468            .layer_utilities()
469            .layer_custom("theme")
470            .custom_property("color", "red")
471            .custom_property("spacing", "1rem")
472            .custom_property("font-size", "16px")
473            .custom_property("font-weight", "bold")
474            .custom_property("line-height", "1.5")
475            .custom_property("border-radius", "8px")
476            .custom_property("box-shadow", "0 4px 6px -1px rgb(0 0 0 / 0.1)")
477            .custom_property("z-index", "10")
478            .custom_property("opacity", "0.8")
479            .custom_property("transform", "rotate(45deg)")
480            .custom_property("animation", "fadeIn 0.5s ease-in-out")
481            .custom_property("transition", "all 0.3s ease")
482            .container_small()
483            .container_medium()
484            .container_large()
485            .container_extra_large()
486            .container_custom("sidebar");
487
488        let result = classes.build();
489        assert!(result.classes.contains("layer-base"));
490        assert!(result.classes.contains("layer-components"));
491        assert!(result.classes.contains("layer-utilities"));
492        assert!(result.classes.contains("layer-theme"));
493        // Check custom properties
494        assert!(result.custom.contains_key("color"));
495        assert_eq!(result.custom.get("color"), Some(&"red".to_string()));
496        assert!(result.custom.contains_key("spacing"));
497        assert_eq!(result.custom.get("spacing"), Some(&"1rem".to_string()));
498        assert!(result.custom.contains_key("font-size"));
499        assert_eq!(result.custom.get("font-size"), Some(&"16px".to_string()));
500        assert!(result.custom.contains_key("font-weight"));
501        assert_eq!(result.custom.get("font-weight"), Some(&"bold".to_string()));
502        assert!(result.custom.contains_key("line-height"));
503        assert_eq!(result.custom.get("line-height"), Some(&"1.5".to_string()));
504        assert!(result.custom.contains_key("border-radius"));
505        assert_eq!(result.custom.get("border-radius"), Some(&"8px".to_string()));
506        assert!(result.custom.contains_key("box-shadow"));
507        assert_eq!(
508            result.custom.get("box-shadow"),
509            Some(&"0 4px 6px -1px rgb(0 0 0 / 0.1)".to_string())
510        );
511        assert!(result.custom.contains_key("z-index"));
512        assert_eq!(result.custom.get("z-index"), Some(&"10".to_string()));
513        assert!(result.custom.contains_key("opacity"));
514        assert_eq!(result.custom.get("opacity"), Some(&"0.8".to_string()));
515        assert!(result.custom.contains_key("transform"));
516        assert_eq!(
517            result.custom.get("transform"),
518            Some(&"rotate(45deg)".to_string())
519        );
520        assert!(result.custom.contains_key("animation"));
521        assert_eq!(
522            result.custom.get("animation"),
523            Some(&"fadeIn 0.5s ease-in-out".to_string())
524        );
525        assert!(result.custom.contains_key("transition"));
526        assert_eq!(
527            result.custom.get("transition"),
528            Some(&"all 0.3s ease".to_string())
529        );
530        assert!(result.classes.contains("container-small"));
531        assert!(result.classes.contains("container-medium"));
532        assert!(result.classes.contains("container-large"));
533        assert!(result.classes.contains("container-extra-large"));
534        assert!(result.classes.contains("container-sidebar"));
535    }
536}