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        let class_name = format!("custom-{}", name);
227        self.class(class_name)
228    }
229
230    fn custom_property_value(self, property: CustomProperty) -> Self {
231        self.class(&property.to_class_name())
232    }
233
234    fn container_small(self) -> Self {
235        self.class("container-small")
236    }
237
238    fn container_medium(self) -> Self {
239        self.class("container-medium")
240    }
241
242    fn container_large(self) -> Self {
243        self.class("container-large")
244    }
245
246    fn container_extra_large(self) -> Self {
247        self.class("container-extra-large")
248    }
249
250    fn container_custom(self, size: &str) -> Self {
251        let class_name = format!("container-{}", size);
252        self.class(class_name)
253    }
254
255    fn container_custom_value(self, query: ModernContainerQuery) -> Self {
256        self.class(&query.to_class_name())
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263    use crate::classes::ClassBuilder;
264
265    #[test]
266    fn test_cascade_layer_enum_values() {
267        assert_eq!(CascadeLayer::Base.to_string(), "base");
268        assert_eq!(CascadeLayer::Components.to_string(), "components");
269        assert_eq!(CascadeLayer::Utilities.to_string(), "utilities");
270        assert_eq!(CascadeLayer::Custom("custom".to_string()).to_string(), "custom");
271    }
272
273    #[test]
274    fn test_cascade_layer_class_names() {
275        assert_eq!(CascadeLayer::Base.to_class_name(), "layer-base");
276        assert_eq!(CascadeLayer::Components.to_class_name(), "layer-components");
277        assert_eq!(CascadeLayer::Utilities.to_class_name(), "layer-utilities");
278        assert_eq!(CascadeLayer::Custom("custom".to_string()).to_class_name(), "layer-custom");
279    }
280
281    #[test]
282    fn test_custom_property_enum_values() {
283        assert_eq!(CustomProperty::Color("red".to_string()).to_string(), "--color: red");
284        assert_eq!(CustomProperty::Spacing("1rem".to_string()).to_string(), "--spacing: 1rem");
285        assert_eq!(CustomProperty::FontSize("16px".to_string()).to_string(), "--font-size: 16px");
286        assert_eq!(CustomProperty::Generic("custom".to_string(), "value".to_string()).to_string(), "--custom: value");
287    }
288
289    #[test]
290    fn test_custom_property_class_names() {
291        assert_eq!(CustomProperty::Color("red".to_string()).to_class_name(), "custom-color");
292        assert_eq!(CustomProperty::Spacing("1rem".to_string()).to_class_name(), "custom-spacing");
293        assert_eq!(CustomProperty::FontSize("16px".to_string()).to_class_name(), "custom-font-size");
294        assert_eq!(CustomProperty::Generic("custom".to_string(), "value".to_string()).to_class_name(), "custom-custom");
295    }
296
297    #[test]
298    fn test_container_query_enum_values() {
299        assert_eq!(ModernContainerQuery::Small.to_string(), "small");
300        assert_eq!(ModernContainerQuery::Medium.to_string(), "medium");
301        assert_eq!(ModernContainerQuery::Large.to_string(), "large");
302        assert_eq!(ModernContainerQuery::ExtraLarge.to_string(), "extra-large");
303        assert_eq!(ModernContainerQuery::Custom("custom".to_string()).to_string(), "custom");
304    }
305
306    #[test]
307    fn test_container_query_class_names() {
308        assert_eq!(ModernContainerQuery::Small.to_class_name(), "container-small");
309        assert_eq!(ModernContainerQuery::Medium.to_class_name(), "container-medium");
310        assert_eq!(ModernContainerQuery::Large.to_class_name(), "container-large");
311        assert_eq!(ModernContainerQuery::ExtraLarge.to_class_name(), "container-extra-large");
312        assert_eq!(ModernContainerQuery::Custom("custom".to_string()).to_class_name(), "container-custom");
313    }
314
315    #[test]
316    fn test_modern_css_features_utilities() {
317        let classes = ClassBuilder::new()
318            .layer_base()
319            .layer_components()
320            .layer_utilities()
321            .custom_property("color", "red")
322            .custom_property("spacing", "1rem")
323            .container_small()
324            .container_medium()
325            .container_large();
326
327        let result = classes.build();
328        assert!(result.contains("layer-base"));
329        assert!(result.contains("layer-components"));
330        assert!(result.contains("layer-utilities"));
331        assert!(result.contains("custom-color"));
332        assert!(result.contains("custom-spacing"));
333        assert!(result.contains("container-small"));
334        assert!(result.contains("container-medium"));
335        assert!(result.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.contains("layer-theme"));
379        assert!(result.contains("custom-primary-color"));
380        assert!(result.contains("custom-secondary-color"));
381        assert!(result.contains("container-sidebar"));
382        assert!(result.contains("container-main"));
383    }
384
385    #[test]
386    fn test_modern_css_features_custom_values() {
387        let classes = ClassBuilder::new()
388            .layer_custom_value(CascadeLayer::Custom("theme".to_string()))
389            .custom_property_value(CustomProperty::Color("blue".to_string()))
390            .custom_property_value(CustomProperty::Spacing("2rem".to_string()))
391            .container_custom_value(ModernContainerQuery::Custom("sidebar".to_string()));
392
393        let result = classes.build();
394        assert!(result.contains("layer-theme"));
395        assert!(result.contains("custom-color"));
396        assert!(result.contains("custom-spacing"));
397        assert!(result.contains("container-sidebar"));
398    }
399
400    #[test]
401    fn test_modern_css_features_all_variants() {
402        let classes = ClassBuilder::new()
403            .layer_base()
404            .layer_components()
405            .layer_utilities()
406            .layer_custom("theme")
407            .custom_property("color", "red")
408            .custom_property("spacing", "1rem")
409            .custom_property("font-size", "16px")
410            .custom_property("font-weight", "bold")
411            .custom_property("line-height", "1.5")
412            .custom_property("border-radius", "8px")
413            .custom_property("box-shadow", "0 4px 6px -1px rgb(0 0 0 / 0.1)")
414            .custom_property("z-index", "10")
415            .custom_property("opacity", "0.8")
416            .custom_property("transform", "rotate(45deg)")
417            .custom_property("animation", "fadeIn 0.5s ease-in-out")
418            .custom_property("transition", "all 0.3s ease")
419            .container_small()
420            .container_medium()
421            .container_large()
422            .container_extra_large()
423            .container_custom("sidebar");
424
425        let result = classes.build();
426        assert!(result.contains("layer-base"));
427        assert!(result.contains("layer-components"));
428        assert!(result.contains("layer-utilities"));
429        assert!(result.contains("layer-theme"));
430        assert!(result.contains("custom-color"));
431        assert!(result.contains("custom-spacing"));
432        assert!(result.contains("custom-font-size"));
433        assert!(result.contains("custom-font-weight"));
434        assert!(result.contains("custom-line-height"));
435        assert!(result.contains("custom-border-radius"));
436        assert!(result.contains("custom-box-shadow"));
437        assert!(result.contains("custom-z-index"));
438        assert!(result.contains("custom-opacity"));
439        assert!(result.contains("custom-transform"));
440        assert!(result.contains("custom-animation"));
441        assert!(result.contains("custom-transition"));
442        assert!(result.contains("container-small"));
443        assert!(result.contains("container-medium"));
444        assert!(result.contains("container-large"));
445        assert!(result.contains("container-extra-large"));
446        assert!(result.contains("container-sidebar"));
447    }
448}