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 => {
73                "@media (prefers-reduced-motion: no-preference)".to_string()
74            }
75        }
76    }
77}
78
79impl ColorSchemeVariant {
80    pub fn to_class_name(&self) -> String {
81        match self {
82            ColorSchemeVariant::Light => "light".to_string(),
83            ColorSchemeVariant::Dark => "dark".to_string(),
84        }
85    }
86
87    pub fn to_media_query(&self) -> String {
88        match self {
89            ColorSchemeVariant::Light => "@media (prefers-color-scheme: light)".to_string(),
90            ColorSchemeVariant::Dark => "@media (prefers-color-scheme: dark)".to_string(),
91        }
92    }
93}
94
95impl fmt::Display for PointerVariant {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        write!(f, "{}", self.to_class_name())
98    }
99}
100
101impl fmt::Display for MotionVariant {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(f, "{}", self.to_class_name())
104    }
105}
106
107impl fmt::Display for ColorSchemeVariant {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        write!(f, "{}", self.to_class_name())
110    }
111}
112
113/// Trait for adding pointer variant utilities to a class builder
114pub trait PointerVariantUtilities {
115    /// Add pointer coarse variant
116    fn pointer_coarse(self) -> Self;
117    /// Add pointer fine variant
118    fn pointer_fine(self) -> Self;
119    /// Add any pointer coarse variant
120    fn any_pointer_coarse(self) -> Self;
121    /// Add any pointer fine variant
122    fn any_pointer_fine(self) -> Self;
123}
124
125impl PointerVariantUtilities for ClassBuilder {
126    fn pointer_coarse(self) -> Self {
127        self.class("pointer-coarse")
128    }
129
130    fn pointer_fine(self) -> Self {
131        self.class("pointer-fine")
132    }
133
134    fn any_pointer_coarse(self) -> Self {
135        self.class("any-pointer-coarse")
136    }
137
138    fn any_pointer_fine(self) -> Self {
139        self.class("any-pointer-fine")
140    }
141}
142
143/// Trait for adding motion variant utilities to a class builder
144pub trait MotionVariantUtilities {
145    /// Add motion reduce variant
146    fn motion_reduce(self) -> Self;
147    /// Add motion safe variant
148    fn motion_safe(self) -> Self;
149}
150
151impl MotionVariantUtilities for ClassBuilder {
152    fn motion_reduce(self) -> Self {
153        self.class("motion-reduce")
154    }
155
156    fn motion_safe(self) -> Self {
157        self.class("motion-safe")
158    }
159}
160
161/// Trait for adding color scheme variant utilities to a class builder
162pub trait ColorSchemeVariantUtilities {
163    /// Add light color scheme variant
164    fn light(self) -> Self;
165    /// Add dark color scheme variant
166    fn dark(self) -> Self;
167}
168
169impl ColorSchemeVariantUtilities for ClassBuilder {
170    fn light(self) -> Self {
171        self.class("light")
172    }
173
174    fn dark(self) -> Self {
175        self.class("dark")
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn test_pointer_variant_class_names() {
185        assert_eq!(PointerVariant::Coarse.to_class_name(), "pointer-coarse");
186        assert_eq!(PointerVariant::Fine.to_class_name(), "pointer-fine");
187        assert_eq!(
188            PointerVariant::AnyCoarse.to_class_name(),
189            "any-pointer-coarse"
190        );
191        assert_eq!(PointerVariant::AnyFine.to_class_name(), "any-pointer-fine");
192    }
193
194    #[test]
195    fn test_pointer_variant_media_queries() {
196        assert_eq!(
197            PointerVariant::Coarse.to_media_query(),
198            "@media (pointer: coarse)"
199        );
200        assert_eq!(
201            PointerVariant::Fine.to_media_query(),
202            "@media (pointer: fine)"
203        );
204        assert_eq!(
205            PointerVariant::AnyCoarse.to_media_query(),
206            "@media (any-pointer: coarse)"
207        );
208        assert_eq!(
209            PointerVariant::AnyFine.to_media_query(),
210            "@media (any-pointer: fine)"
211        );
212    }
213
214    #[test]
215    fn test_motion_variant_class_names() {
216        assert_eq!(MotionVariant::Reduced.to_class_name(), "motion-reduce");
217        assert_eq!(MotionVariant::NoPreference.to_class_name(), "motion-safe");
218    }
219
220    #[test]
221    fn test_motion_variant_media_queries() {
222        assert_eq!(
223            MotionVariant::Reduced.to_media_query(),
224            "@media (prefers-reduced-motion: reduce)"
225        );
226        assert_eq!(
227            MotionVariant::NoPreference.to_media_query(),
228            "@media (prefers-reduced-motion: no-preference)"
229        );
230    }
231
232    #[test]
233    fn test_color_scheme_variant_class_names() {
234        assert_eq!(ColorSchemeVariant::Light.to_class_name(), "light");
235        assert_eq!(ColorSchemeVariant::Dark.to_class_name(), "dark");
236    }
237
238    #[test]
239    fn test_color_scheme_variant_media_queries() {
240        assert_eq!(
241            ColorSchemeVariant::Light.to_media_query(),
242            "@media (prefers-color-scheme: light)"
243        );
244        assert_eq!(
245            ColorSchemeVariant::Dark.to_media_query(),
246            "@media (prefers-color-scheme: dark)"
247        );
248    }
249
250    #[test]
251    fn test_pointer_variant_utilities() {
252        let classes = ClassBuilder::new()
253            .pointer_coarse()
254            .pointer_fine()
255            .any_pointer_coarse()
256            .any_pointer_fine()
257            .build();
258
259        assert!(classes.classes.contains("pointer-coarse"));
260        assert!(classes.classes.contains("pointer-fine"));
261        assert!(classes.classes.contains("any-pointer-coarse"));
262        assert!(classes.classes.contains("any-pointer-fine"));
263    }
264
265    #[test]
266    fn test_motion_variant_utilities() {
267        let classes = ClassBuilder::new().motion_reduce().motion_safe().build();
268
269        assert!(classes.classes.contains("motion-reduce"));
270        assert!(classes.classes.contains("motion-safe"));
271    }
272
273    #[test]
274    fn test_color_scheme_variant_utilities() {
275        let classes = ClassBuilder::new().light().dark().build();
276
277        println!("Generated classes: {:?}", classes.classes);
278        assert!(classes.classes.contains("light"));
279        assert!(classes.classes.contains("dark"));
280    }
281
282    #[test]
283    fn test_device_variants_comprehensive() {
284        let classes = ClassBuilder::new()
285            .pointer_coarse()
286            .pointer_fine()
287            .motion_reduce()
288            .motion_safe()
289            .light()
290            .dark()
291            .build();
292
293        assert!(classes.classes.contains("pointer-coarse"));
294        assert!(classes.classes.contains("pointer-fine"));
295        assert!(classes.classes.contains("motion-reduce"));
296        assert!(classes.classes.contains("motion-safe"));
297        println!("Generated classes: {:?}", classes.classes);
298        assert!(classes.classes.contains("light"));
299        assert!(classes.classes.contains("dark"));
300    }
301}