tailwind_rs_core/utilities/
transitions.rs

1//! Transition utilities for tailwind-rs
2//!
3//! This module provides utilities for CSS transitions including duration,
4//! timing function, delay, and property-specific transitions.
5
6use crate::classes::ClassBuilder;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10/// Transition duration values
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub enum TransitionDuration {
13    /// 75ms duration
14    Duration75,
15    /// 100ms duration
16    Duration100,
17    /// 150ms duration
18    Duration150,
19    /// 200ms duration
20    Duration200,
21    /// 300ms duration
22    Duration300,
23    /// 500ms duration
24    Duration500,
25    /// 700ms duration
26    Duration700,
27    /// 1000ms duration
28    Duration1000,
29}
30
31/// Transition timing function values
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub enum TransitionTimingFunction {
34    /// Linear timing
35    Linear,
36    /// In timing
37    In,
38    /// Out timing
39    Out,
40    /// In-out timing
41    InOut,
42}
43
44/// Transition delay values
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
46pub enum TransitionDelay {
47    /// 75ms delay
48    Delay75,
49    /// 100ms delay
50    Delay100,
51    /// 150ms delay
52    Delay150,
53    /// 200ms delay
54    Delay200,
55    /// 300ms delay
56    Delay300,
57    /// 500ms delay
58    Delay500,
59    /// 700ms delay
60    Delay700,
61    /// 1000ms delay
62    Delay1000,
63}
64
65/// Transition property values
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
67pub enum TransitionProperty {
68    /// All properties
69    All,
70    /// No properties
71    None,
72    /// Default properties
73    Default,
74    /// Colors only
75    Colors,
76    /// Opacity only
77    Opacity,
78    /// Shadow only
79    Shadow,
80    /// Transform only
81    Transform,
82}
83
84impl TransitionDuration {
85    pub fn to_class_name(&self) -> String {
86        match self {
87            TransitionDuration::Duration75 => "75".to_string(),
88            TransitionDuration::Duration100 => "100".to_string(),
89            TransitionDuration::Duration150 => "150".to_string(),
90            TransitionDuration::Duration200 => "200".to_string(),
91            TransitionDuration::Duration300 => "300".to_string(),
92            TransitionDuration::Duration500 => "500".to_string(),
93            TransitionDuration::Duration700 => "700".to_string(),
94            TransitionDuration::Duration1000 => "1000".to_string(),
95        }
96    }
97    
98    pub fn to_css_value(&self) -> String {
99        match self {
100            TransitionDuration::Duration75 => "75ms".to_string(),
101            TransitionDuration::Duration100 => "100ms".to_string(),
102            TransitionDuration::Duration150 => "150ms".to_string(),
103            TransitionDuration::Duration200 => "200ms".to_string(),
104            TransitionDuration::Duration300 => "300ms".to_string(),
105            TransitionDuration::Duration500 => "500ms".to_string(),
106            TransitionDuration::Duration700 => "700ms".to_string(),
107            TransitionDuration::Duration1000 => "1000ms".to_string(),
108        }
109    }
110}
111
112impl TransitionTimingFunction {
113    pub fn to_class_name(&self) -> String {
114        match self {
115            TransitionTimingFunction::Linear => "linear".to_string(),
116            TransitionTimingFunction::In => "in".to_string(),
117            TransitionTimingFunction::Out => "out".to_string(),
118            TransitionTimingFunction::InOut => "in-out".to_string(),
119        }
120    }
121    
122    pub fn to_css_value(&self) -> String {
123        match self {
124            TransitionTimingFunction::Linear => "linear".to_string(),
125            TransitionTimingFunction::In => "cubic-bezier(0.4, 0, 1, 1)".to_string(),
126            TransitionTimingFunction::Out => "cubic-bezier(0, 0, 0.2, 1)".to_string(),
127            TransitionTimingFunction::InOut => "cubic-bezier(0.4, 0, 0.2, 1)".to_string(),
128        }
129    }
130}
131
132impl TransitionDelay {
133    pub fn to_class_name(&self) -> String {
134        match self {
135            TransitionDelay::Delay75 => "75".to_string(),
136            TransitionDelay::Delay100 => "100".to_string(),
137            TransitionDelay::Delay150 => "150".to_string(),
138            TransitionDelay::Delay200 => "200".to_string(),
139            TransitionDelay::Delay300 => "300".to_string(),
140            TransitionDelay::Delay500 => "500".to_string(),
141            TransitionDelay::Delay700 => "700".to_string(),
142            TransitionDelay::Delay1000 => "1000".to_string(),
143        }
144    }
145    
146    pub fn to_css_value(&self) -> String {
147        match self {
148            TransitionDelay::Delay75 => "75ms".to_string(),
149            TransitionDelay::Delay100 => "100ms".to_string(),
150            TransitionDelay::Delay150 => "150ms".to_string(),
151            TransitionDelay::Delay200 => "200ms".to_string(),
152            TransitionDelay::Delay300 => "300ms".to_string(),
153            TransitionDelay::Delay500 => "500ms".to_string(),
154            TransitionDelay::Delay700 => "700ms".to_string(),
155            TransitionDelay::Delay1000 => "1000ms".to_string(),
156        }
157    }
158}
159
160impl TransitionProperty {
161    pub fn to_class_name(&self) -> String {
162        match self {
163            TransitionProperty::All => "all".to_string(),
164            TransitionProperty::None => "none".to_string(),
165            TransitionProperty::Default => "default".to_string(),
166            TransitionProperty::Colors => "colors".to_string(),
167            TransitionProperty::Opacity => "opacity".to_string(),
168            TransitionProperty::Shadow => "shadow".to_string(),
169            TransitionProperty::Transform => "transform".to_string(),
170        }
171    }
172    
173    pub fn to_css_value(&self) -> String {
174        match self {
175            TransitionProperty::All => "all".to_string(),
176            TransitionProperty::None => "none".to_string(),
177            TransitionProperty::Default => "background-color, border-color, color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter".to_string(),
178            TransitionProperty::Colors => "background-color, border-color, color, fill, stroke".to_string(),
179            TransitionProperty::Opacity => "opacity".to_string(),
180            TransitionProperty::Shadow => "box-shadow".to_string(),
181            TransitionProperty::Transform => "transform".to_string(),
182        }
183    }
184}
185
186impl fmt::Display for TransitionDuration {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        write!(f, "{}", self.to_class_name())
189    }
190}
191
192impl fmt::Display for TransitionTimingFunction {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        write!(f, "{}", self.to_class_name())
195    }
196}
197
198impl fmt::Display for TransitionDelay {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        write!(f, "{}", self.to_class_name())
201    }
202}
203
204impl fmt::Display for TransitionProperty {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        write!(f, "{}", self.to_class_name())
207    }
208}
209
210/// Trait for adding transition duration utilities to a class builder
211pub trait TransitionDurationUtilities {
212    fn transition_duration(self, duration: TransitionDuration) -> Self;
213}
214
215impl TransitionDurationUtilities for ClassBuilder {
216    fn transition_duration(self, duration: TransitionDuration) -> Self {
217        self.class(format!("duration-{}", duration.to_class_name()))
218    }
219}
220
221/// Trait for adding transition timing function utilities to a class builder
222pub trait TransitionTimingFunctionUtilities {
223    fn transition_timing_function(self, timing: TransitionTimingFunction) -> Self;
224}
225
226impl TransitionTimingFunctionUtilities for ClassBuilder {
227    fn transition_timing_function(self, timing: TransitionTimingFunction) -> Self {
228        self.class(format!("ease-{}", timing.to_class_name()))
229    }
230}
231
232/// Trait for adding transition delay utilities to a class builder
233pub trait TransitionDelayUtilities {
234    fn transition_delay(self, delay: TransitionDelay) -> Self;
235}
236
237impl TransitionDelayUtilities for ClassBuilder {
238    fn transition_delay(self, delay: TransitionDelay) -> Self {
239        self.class(format!("delay-{}", delay.to_class_name()))
240    }
241}
242
243/// Trait for adding transition property utilities to a class builder
244pub trait TransitionPropertyUtilities {
245    fn transition_property(self, property: TransitionProperty) -> Self;
246}
247
248impl TransitionPropertyUtilities for ClassBuilder {
249    fn transition_property(self, property: TransitionProperty) -> Self {
250        self.class(format!("transition-{}", property.to_class_name()))
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257    
258    #[test]
259    fn test_transition_duration_utilities() {
260        let classes = ClassBuilder::new()
261            .transition_duration(TransitionDuration::Duration75)
262            .transition_duration(TransitionDuration::Duration100)
263            .transition_duration(TransitionDuration::Duration150)
264            .transition_duration(TransitionDuration::Duration200)
265            .transition_duration(TransitionDuration::Duration300)
266            .transition_duration(TransitionDuration::Duration500)
267            .transition_duration(TransitionDuration::Duration700)
268            .transition_duration(TransitionDuration::Duration1000)
269            .build();
270        
271        let css_classes = classes.to_css_classes();
272        assert!(css_classes.contains("duration-75"));
273        assert!(css_classes.contains("duration-100"));
274        assert!(css_classes.contains("duration-150"));
275        assert!(css_classes.contains("duration-200"));
276        assert!(css_classes.contains("duration-300"));
277        assert!(css_classes.contains("duration-500"));
278        assert!(css_classes.contains("duration-700"));
279        assert!(css_classes.contains("duration-1000"));
280    }
281    
282    #[test]
283    fn test_transition_timing_function_utilities() {
284        let classes = ClassBuilder::new()
285            .transition_timing_function(TransitionTimingFunction::Linear)
286            .transition_timing_function(TransitionTimingFunction::In)
287            .transition_timing_function(TransitionTimingFunction::Out)
288            .transition_timing_function(TransitionTimingFunction::InOut)
289            .build();
290        
291        let css_classes = classes.to_css_classes();
292        assert!(css_classes.contains("ease-linear"));
293        assert!(css_classes.contains("ease-in"));
294        assert!(css_classes.contains("ease-out"));
295        assert!(css_classes.contains("ease-in-out"));
296    }
297    
298    #[test]
299    fn test_transition_delay_utilities() {
300        let classes = ClassBuilder::new()
301            .transition_delay(TransitionDelay::Delay75)
302            .transition_delay(TransitionDelay::Delay100)
303            .transition_delay(TransitionDelay::Delay150)
304            .transition_delay(TransitionDelay::Delay200)
305            .transition_delay(TransitionDelay::Delay300)
306            .transition_delay(TransitionDelay::Delay500)
307            .transition_delay(TransitionDelay::Delay700)
308            .transition_delay(TransitionDelay::Delay1000)
309            .build();
310        
311        let css_classes = classes.to_css_classes();
312        assert!(css_classes.contains("delay-75"));
313        assert!(css_classes.contains("delay-100"));
314        assert!(css_classes.contains("delay-150"));
315        assert!(css_classes.contains("delay-200"));
316        assert!(css_classes.contains("delay-300"));
317        assert!(css_classes.contains("delay-500"));
318        assert!(css_classes.contains("delay-700"));
319        assert!(css_classes.contains("delay-1000"));
320    }
321    
322    #[test]
323    fn test_transition_property_utilities() {
324        let classes = ClassBuilder::new()
325            .transition_property(TransitionProperty::All)
326            .transition_property(TransitionProperty::None)
327            .transition_property(TransitionProperty::Default)
328            .transition_property(TransitionProperty::Colors)
329            .transition_property(TransitionProperty::Opacity)
330            .transition_property(TransitionProperty::Shadow)
331            .transition_property(TransitionProperty::Transform)
332            .build();
333        
334        let css_classes = classes.to_css_classes();
335        assert!(css_classes.contains("transition-all"));
336        assert!(css_classes.contains("transition-none"));
337        assert!(css_classes.contains("transition-default"));
338        assert!(css_classes.contains("transition-colors"));
339        assert!(css_classes.contains("transition-opacity"));
340        assert!(css_classes.contains("transition-shadow"));
341        assert!(css_classes.contains("transition-transform"));
342    }
343    
344    #[test]
345    fn test_complex_transitions_combination() {
346        let classes = ClassBuilder::new()
347            .transition_duration(TransitionDuration::Duration300)
348            .transition_timing_function(TransitionTimingFunction::InOut)
349            .transition_delay(TransitionDelay::Delay100)
350            .transition_property(TransitionProperty::Colors)
351            .build();
352        
353        let css_classes = classes.to_css_classes();
354        assert!(css_classes.contains("duration-300"));
355        assert!(css_classes.contains("ease-in-out"));
356        assert!(css_classes.contains("delay-100"));
357        assert!(css_classes.contains("transition-colors"));
358    }
359    
360    /// Test that all Week 12 transition utilities are implemented
361    #[test]
362    fn test_week12_transition_utilities() {
363        // Test all Week 12 transition utilities
364        let classes = ClassBuilder::new()
365            // Transition Properties
366            .transition_property(TransitionProperty::None)
367            .transition_property(TransitionProperty::All)
368            .transition_property(TransitionProperty::Default)
369            .transition_property(TransitionProperty::Colors)
370            .transition_property(TransitionProperty::Opacity)
371            .transition_property(TransitionProperty::Shadow)
372            .transition_property(TransitionProperty::Transform)
373            // Duration
374            .transition_duration(TransitionDuration::Duration75)
375            .transition_duration(TransitionDuration::Duration100)
376            .transition_duration(TransitionDuration::Duration150)
377            .transition_duration(TransitionDuration::Duration200)
378            .transition_duration(TransitionDuration::Duration300)
379            .transition_duration(TransitionDuration::Duration500)
380            .transition_duration(TransitionDuration::Duration700)
381            .transition_duration(TransitionDuration::Duration1000)
382            // Ease
383            .transition_timing_function(TransitionTimingFunction::Linear)
384            .transition_timing_function(TransitionTimingFunction::In)
385            .transition_timing_function(TransitionTimingFunction::Out)
386            .transition_timing_function(TransitionTimingFunction::InOut)
387            // Delay
388            .transition_delay(TransitionDelay::Delay75)
389            .transition_delay(TransitionDelay::Delay100)
390            .transition_delay(TransitionDelay::Delay150)
391            .transition_delay(TransitionDelay::Delay200)
392            .transition_delay(TransitionDelay::Delay300)
393            .transition_delay(TransitionDelay::Delay500)
394            .transition_delay(TransitionDelay::Delay700)
395            .transition_delay(TransitionDelay::Delay1000)
396            .build();
397        
398        let css_classes = classes.to_css_classes();
399        
400        // Transition Properties
401        assert!(css_classes.contains("transition-none"));
402        assert!(css_classes.contains("transition-all"));
403        assert!(css_classes.contains("transition"));
404        assert!(css_classes.contains("transition-colors"));
405        assert!(css_classes.contains("transition-opacity"));
406        assert!(css_classes.contains("transition-shadow"));
407        assert!(css_classes.contains("transition-transform"));
408        
409        // Duration
410        assert!(css_classes.contains("duration-75"));
411        assert!(css_classes.contains("duration-100"));
412        assert!(css_classes.contains("duration-150"));
413        assert!(css_classes.contains("duration-200"));
414        assert!(css_classes.contains("duration-300"));
415        assert!(css_classes.contains("duration-500"));
416        assert!(css_classes.contains("duration-700"));
417        assert!(css_classes.contains("duration-1000"));
418        
419        // Ease
420        assert!(css_classes.contains("ease-linear"));
421        assert!(css_classes.contains("ease-in"));
422        assert!(css_classes.contains("ease-out"));
423        assert!(css_classes.contains("ease-in-out"));
424        
425        // Delay
426        assert!(css_classes.contains("delay-75"));
427        assert!(css_classes.contains("delay-100"));
428        assert!(css_classes.contains("delay-150"));
429        assert!(css_classes.contains("delay-200"));
430        assert!(css_classes.contains("delay-300"));
431        assert!(css_classes.contains("delay-500"));
432        assert!(css_classes.contains("delay-700"));
433        assert!(css_classes.contains("delay-1000"));
434    }
435}