tailwind_rs_core/utilities/
device_variants.rs

1//! Device variant utilities for tailwind-rs
2//!
3//! This module provides utilities for device-specific media queries like pointer variants,
4//! motion preferences, and other device capabilities for better accessibility and device targeting.
5
6use crate::classes::ClassBuilder;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10/// Pointer capability variants
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub enum PointerVariant {
13    /// Coarse pointer (touch, stylus)
14    Coarse,
15    /// Fine pointer (mouse, trackpad)
16    Fine,
17    /// Any coarse pointer
18    AnyCoarse,
19    /// Any fine pointer
20    AnyFine,
21}
22
23/// Motion preference variants
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
25pub enum MotionVariant {
26    /// Reduced motion preference
27    Reduced,
28    /// No motion preference
29    NoPreference,
30}
31
32/// Color scheme variants
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34pub enum ColorSchemeVariant {
35    /// Light color scheme
36    Light,
37    /// Dark color scheme
38    Dark,
39}
40
41impl PointerVariant {
42    pub fn to_class_name(&self) -> String {
43        match self {
44            PointerVariant::Coarse => "pointer-coarse".to_string(),
45            PointerVariant::Fine => "pointer-fine".to_string(),
46            PointerVariant::AnyCoarse => "any-pointer-coarse".to_string(),
47            PointerVariant::AnyFine => "any-pointer-fine".to_string(),
48        }
49    }
50    
51    pub fn to_media_query(&self) -> String {
52        match self {
53            PointerVariant::Coarse => "@media (pointer: coarse)".to_string(),
54            PointerVariant::Fine => "@media (pointer: fine)".to_string(),
55            PointerVariant::AnyCoarse => "@media (any-pointer: coarse)".to_string(),
56            PointerVariant::AnyFine => "@media (any-pointer: fine)".to_string(),
57        }
58    }
59}
60
61impl MotionVariant {
62    pub fn to_class_name(&self) -> String {
63        match self {
64            MotionVariant::Reduced => "motion-reduce".to_string(),
65            MotionVariant::NoPreference => "motion-safe".to_string(),
66        }
67    }
68    
69    pub fn to_media_query(&self) -> String {
70        match self {
71            MotionVariant::Reduced => "@media (prefers-reduced-motion: reduce)".to_string(),
72            MotionVariant::NoPreference => "@media (prefers-reduced-motion: no-preference)".to_string(),
73        }
74    }
75}
76
77impl ColorSchemeVariant {
78    pub fn to_class_name(&self) -> String {
79        match self {
80            ColorSchemeVariant::Light => "light".to_string(),
81            ColorSchemeVariant::Dark => "dark".to_string(),
82        }
83    }
84    
85    pub fn to_media_query(&self) -> String {
86        match self {
87            ColorSchemeVariant::Light => "@media (prefers-color-scheme: light)".to_string(),
88            ColorSchemeVariant::Dark => "@media (prefers-color-scheme: dark)".to_string(),
89        }
90    }
91}
92
93impl fmt::Display for PointerVariant {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(f, "{}", self.to_class_name())
96    }
97}
98
99impl fmt::Display for MotionVariant {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        write!(f, "{}", self.to_class_name())
102    }
103}
104
105impl fmt::Display for ColorSchemeVariant {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        write!(f, "{}", self.to_class_name())
108    }
109}
110
111/// Trait for adding pointer variant utilities to a class builder
112pub trait PointerVariantUtilities {
113    /// Add pointer coarse variant
114    fn pointer_coarse(self) -> Self;
115    /// Add pointer fine variant
116    fn pointer_fine(self) -> Self;
117    /// Add any pointer coarse variant
118    fn any_pointer_coarse(self) -> Self;
119    /// Add any pointer fine variant
120    fn any_pointer_fine(self) -> Self;
121}
122
123impl PointerVariantUtilities for ClassBuilder {
124    fn pointer_coarse(self) -> Self {
125        self.class("pointer-coarse")
126    }
127    
128    fn pointer_fine(self) -> Self {
129        self.class("pointer-fine")
130    }
131    
132    fn any_pointer_coarse(self) -> Self {
133        self.class("any-pointer-coarse")
134    }
135    
136    fn any_pointer_fine(self) -> Self {
137        self.class("any-pointer-fine")
138    }
139}
140
141/// Trait for adding motion variant utilities to a class builder
142pub trait MotionVariantUtilities {
143    /// Add motion reduce variant
144    fn motion_reduce(self) -> Self;
145    /// Add motion safe variant
146    fn motion_safe(self) -> Self;
147}
148
149impl MotionVariantUtilities for ClassBuilder {
150    fn motion_reduce(self) -> Self {
151        self.class("motion-reduce")
152    }
153    
154    fn motion_safe(self) -> Self {
155        self.class("motion-safe")
156    }
157}
158
159/// Trait for adding color scheme variant utilities to a class builder
160pub trait ColorSchemeVariantUtilities {
161    /// Add light color scheme variant
162    fn light(self) -> Self;
163    /// Add dark color scheme variant
164    fn dark(self) -> Self;
165}
166
167impl ColorSchemeVariantUtilities for ClassBuilder {
168    fn light(self) -> Self {
169        self.class("light")
170    }
171    
172    fn dark(self) -> Self {
173        self.class("dark")
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn test_pointer_variant_class_names() {
183        assert_eq!(PointerVariant::Coarse.to_class_name(), "pointer-coarse");
184        assert_eq!(PointerVariant::Fine.to_class_name(), "pointer-fine");
185        assert_eq!(PointerVariant::AnyCoarse.to_class_name(), "any-pointer-coarse");
186        assert_eq!(PointerVariant::AnyFine.to_class_name(), "any-pointer-fine");
187    }
188
189    #[test]
190    fn test_pointer_variant_media_queries() {
191        assert_eq!(PointerVariant::Coarse.to_media_query(), "@media (pointer: coarse)");
192        assert_eq!(PointerVariant::Fine.to_media_query(), "@media (pointer: fine)");
193        assert_eq!(PointerVariant::AnyCoarse.to_media_query(), "@media (any-pointer: coarse)");
194        assert_eq!(PointerVariant::AnyFine.to_media_query(), "@media (any-pointer: fine)");
195    }
196
197    #[test]
198    fn test_motion_variant_class_names() {
199        assert_eq!(MotionVariant::Reduced.to_class_name(), "motion-reduce");
200        assert_eq!(MotionVariant::NoPreference.to_class_name(), "motion-safe");
201    }
202
203    #[test]
204    fn test_motion_variant_media_queries() {
205        assert_eq!(MotionVariant::Reduced.to_media_query(), "@media (prefers-reduced-motion: reduce)");
206        assert_eq!(MotionVariant::NoPreference.to_media_query(), "@media (prefers-reduced-motion: no-preference)");
207    }
208
209    #[test]
210    fn test_color_scheme_variant_class_names() {
211        assert_eq!(ColorSchemeVariant::Light.to_class_name(), "light");
212        assert_eq!(ColorSchemeVariant::Dark.to_class_name(), "dark");
213    }
214
215    #[test]
216    fn test_color_scheme_variant_media_queries() {
217        assert_eq!(ColorSchemeVariant::Light.to_media_query(), "@media (prefers-color-scheme: light)");
218        assert_eq!(ColorSchemeVariant::Dark.to_media_query(), "@media (prefers-color-scheme: dark)");
219    }
220
221    #[test]
222    fn test_pointer_variant_utilities() {
223        let classes = ClassBuilder::new()
224            .pointer_coarse()
225            .pointer_fine()
226            .any_pointer_coarse()
227            .any_pointer_fine()
228            .build();
229        
230        assert!(classes.classes.contains("pointer-coarse"));
231        assert!(classes.classes.contains("pointer-fine"));
232        assert!(classes.classes.contains("any-pointer-coarse"));
233        assert!(classes.classes.contains("any-pointer-fine"));
234    }
235
236    #[test]
237    fn test_motion_variant_utilities() {
238        let classes = ClassBuilder::new()
239            .motion_reduce()
240            .motion_safe()
241            .build();
242        
243        assert!(classes.classes.contains("motion-reduce"));
244        assert!(classes.classes.contains("motion-safe"));
245    }
246
247    #[test]
248    fn test_color_scheme_variant_utilities() {
249        let classes = ClassBuilder::new()
250            .light()
251            .dark()
252            .build();
253        
254        assert!(classes.classes.contains("light"));
255        assert!(classes.classes.contains("dark"));
256    }
257
258    #[test]
259    fn test_device_variants_comprehensive() {
260        let classes = ClassBuilder::new()
261            .pointer_coarse()
262            .pointer_fine()
263            .motion_reduce()
264            .motion_safe()
265            .light()
266            .dark()
267            .build();
268        
269        assert!(classes.classes.contains("pointer-coarse"));
270        assert!(classes.classes.contains("pointer-fine"));
271        assert!(classes.classes.contains("motion-reduce"));
272        assert!(classes.classes.contains("motion-safe"));
273        assert!(classes.classes.contains("light"));
274        assert!(classes.classes.contains("dark"));
275    }
276}