tailwind_rs_core/utilities/
logical_properties.rs

1//! CSS Logical Properties utilities for tailwind-rs
2//!
3//! This module provides utilities for CSS logical properties.
4//! It includes inline-start and inline-end properties for better internationalization support.
5
6use crate::classes::ClassBuilder;
7use crate::utilities::spacing::SpacingValue;
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11/// Logical direction values
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub enum LogicalDirection {
14    /// Inline start (left in LTR, right in RTL)
15    InlineStart,
16    /// Inline end (right in LTR, left in RTL)
17    InlineEnd,
18    /// Block start (top in horizontal writing modes)
19    BlockStart,
20    /// Block end (bottom in horizontal writing modes)
21    BlockEnd,
22}
23
24impl fmt::Display for LogicalDirection {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match self {
27            LogicalDirection::InlineStart => write!(f, "inline-start"),
28            LogicalDirection::InlineEnd => write!(f, "inline-end"),
29            LogicalDirection::BlockStart => write!(f, "block-start"),
30            LogicalDirection::BlockEnd => write!(f, "block-end"),
31        }
32    }
33}
34
35/// Trait for adding CSS logical properties to ClassBuilder
36pub trait LogicalPropertiesUtilities {
37    /// Set margin inline start
38    fn margin_inline_start(self, value: SpacingValue) -> Self;
39    /// Set margin inline end
40    fn margin_inline_end(self, value: SpacingValue) -> Self;
41    /// Set margin block start
42    fn margin_block_start(self, value: SpacingValue) -> Self;
43    /// Set margin block end
44    fn margin_block_end(self, value: SpacingValue) -> Self;
45    /// Set padding inline start
46    fn padding_inline_start(self, value: SpacingValue) -> Self;
47    /// Set padding inline end
48    fn padding_inline_end(self, value: SpacingValue) -> Self;
49    /// Set padding block start
50    fn padding_block_start(self, value: SpacingValue) -> Self;
51    /// Set padding block end
52    fn padding_block_end(self, value: SpacingValue) -> Self;
53    /// Set border inline start
54    fn border_inline_start(self, value: SpacingValue) -> Self;
55    /// Set border inline end
56    fn border_inline_end(self, value: SpacingValue) -> Self;
57    /// Set border block start
58    fn border_block_start(self, value: SpacingValue) -> Self;
59    /// Set border block end
60    fn border_block_end(self, value: SpacingValue) -> Self;
61    /// Set inset inline start
62    fn inset_inline_start(self, value: SpacingValue) -> Self;
63    /// Set inset inline end
64    fn inset_inline_end(self, value: SpacingValue) -> Self;
65    /// Set inset block start
66    fn inset_block_start(self, value: SpacingValue) -> Self;
67    /// Set inset block end
68    fn inset_block_end(self, value: SpacingValue) -> Self;
69}
70
71impl LogicalPropertiesUtilities for ClassBuilder {
72    fn margin_inline_start(self, value: SpacingValue) -> Self {
73        let class_name = format!("ms-{}", value);
74        self.class(class_name)
75    }
76
77    fn margin_inline_end(self, value: SpacingValue) -> Self {
78        let class_name = format!("me-{}", value);
79        self.class(class_name)
80    }
81
82    fn margin_block_start(self, value: SpacingValue) -> Self {
83        let class_name = format!("mt-{}", value);
84        self.class(class_name)
85    }
86
87    fn margin_block_end(self, value: SpacingValue) -> Self {
88        let class_name = format!("mb-{}", value);
89        self.class(class_name)
90    }
91
92    fn padding_inline_start(self, value: SpacingValue) -> Self {
93        let class_name = format!("ps-{}", value);
94        self.class(class_name)
95    }
96
97    fn padding_inline_end(self, value: SpacingValue) -> Self {
98        let class_name = format!("pe-{}", value);
99        self.class(class_name)
100    }
101
102    fn padding_block_start(self, value: SpacingValue) -> Self {
103        let class_name = format!("pt-{}", value);
104        self.class(class_name)
105    }
106
107    fn padding_block_end(self, value: SpacingValue) -> Self {
108        let class_name = format!("pb-{}", value);
109        self.class(class_name)
110    }
111
112    fn border_inline_start(self, value: SpacingValue) -> Self {
113        let class_name = format!("border-s-{}", value);
114        self.class(class_name)
115    }
116
117    fn border_inline_end(self, value: SpacingValue) -> Self {
118        let class_name = format!("border-e-{}", value);
119        self.class(class_name)
120    }
121
122    fn border_block_start(self, value: SpacingValue) -> Self {
123        let class_name = format!("border-t-{}", value);
124        self.class(class_name)
125    }
126
127    fn border_block_end(self, value: SpacingValue) -> Self {
128        let class_name = format!("border-b-{}", value);
129        self.class(class_name)
130    }
131
132    fn inset_inline_start(self, value: SpacingValue) -> Self {
133        let class_name = format!("start-{}", value);
134        self.class(class_name)
135    }
136
137    fn inset_inline_end(self, value: SpacingValue) -> Self {
138        let class_name = format!("end-{}", value);
139        self.class(class_name)
140    }
141
142    fn inset_block_start(self, value: SpacingValue) -> Self {
143        let class_name = format!("top-{}", value);
144        self.class(class_name)
145    }
146
147    fn inset_block_end(self, value: SpacingValue) -> Self {
148        let class_name = format!("bottom-{}", value);
149        self.class(class_name)
150    }
151}
152
153/// Convenience methods for common spacing values
154pub trait LogicalPropertiesConvenience {
155    /// Set margin inline start to 4
156    fn margin_inline_start_4(self) -> Self;
157    /// Set margin inline end to 4
158    fn margin_inline_end_4(self) -> Self;
159    /// Set padding inline start to 2
160    fn padding_inline_start_2(self) -> Self;
161    /// Set padding inline end to 2
162    fn padding_inline_end_2(self) -> Self;
163    /// Set padding inline start to 4
164    fn padding_inline_start_4(self) -> Self;
165    /// Set padding inline end to 4
166    fn padding_inline_end_4(self) -> Self;
167    /// Set border inline start to 1
168    fn border_inline_start_1(self) -> Self;
169    /// Set border inline end to 1
170    fn border_inline_end_1(self) -> Self;
171    /// Set border inline start to 2
172    fn border_inline_start_2(self) -> Self;
173    /// Set border inline end to 2
174    fn border_inline_end_2(self) -> Self;
175}
176
177impl LogicalPropertiesConvenience for ClassBuilder {
178    fn margin_inline_start_4(self) -> Self {
179        self.margin_inline_start(SpacingValue::Integer(4))
180    }
181
182    fn margin_inline_end_4(self) -> Self {
183        self.margin_inline_end(SpacingValue::Integer(4))
184    }
185
186    fn padding_inline_start_2(self) -> Self {
187        self.padding_inline_start(SpacingValue::Integer(2))
188    }
189
190    fn padding_inline_end_2(self) -> Self {
191        self.padding_inline_end(SpacingValue::Integer(2))
192    }
193
194    fn border_inline_start_1(self) -> Self {
195        self.border_inline_start(SpacingValue::Integer(1))
196    }
197
198    fn border_inline_end_1(self) -> Self {
199        self.border_inline_end(SpacingValue::Integer(1))
200    }
201
202    fn padding_inline_start_4(self) -> Self {
203        self.padding_inline_start(SpacingValue::Integer(4))
204    }
205
206    fn padding_inline_end_4(self) -> Self {
207        self.padding_inline_end(SpacingValue::Integer(4))
208    }
209
210    fn border_inline_start_2(self) -> Self {
211        self.border_inline_start(SpacingValue::Integer(2))
212    }
213
214    fn border_inline_end_2(self) -> Self {
215        self.border_inline_end(SpacingValue::Integer(2))
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    use crate::classes::ClassBuilder;
223    use crate::utilities::spacing::SpacingValue;
224
225    #[test]
226    fn test_logical_direction_enum_values() {
227        assert_eq!(LogicalDirection::InlineStart.to_string(), "inline-start");
228        assert_eq!(LogicalDirection::InlineEnd.to_string(), "inline-end");
229        assert_eq!(LogicalDirection::BlockStart.to_string(), "block-start");
230        assert_eq!(LogicalDirection::BlockEnd.to_string(), "block-end");
231    }
232
233    #[test]
234    fn test_logical_properties_utilities() {
235        let classes = ClassBuilder::new()
236            .margin_inline_start(SpacingValue::Integer(4))
237            .margin_inline_end(SpacingValue::Integer(4))
238            .padding_inline_start(SpacingValue::Integer(2))
239            .padding_inline_end(SpacingValue::Integer(2))
240            .border_inline_start(SpacingValue::Integer(1))
241            .border_inline_end(SpacingValue::Integer(1));
242
243        let result = classes.build();
244        assert!(result.classes.contains("ms-4"));
245        assert!(result.classes.contains("me-4"));
246        assert!(result.classes.contains("ps-2"));
247        assert!(result.classes.contains("pe-2"));
248        assert!(result.classes.contains("border-s-1"));
249        assert!(result.classes.contains("border-e-1"));
250    }
251
252    #[test]
253    fn test_logical_properties_convenience() {
254        let classes = ClassBuilder::new()
255            .margin_inline_start_4()
256            .margin_inline_end_4()
257            .padding_inline_start_4()
258            .padding_inline_end_4()
259            .border_inline_start_2()
260            .border_inline_end_2();
261
262        let result = classes.build();
263        assert!(result.classes.contains("ms-4"));
264        assert!(result.classes.contains("me-4"));
265        assert!(result.classes.contains("ps-4"));
266        assert!(result.classes.contains("pe-4"));
267        assert!(result.classes.contains("border-s-2"));
268        assert!(result.classes.contains("border-e-2"));
269    }
270
271    #[test]
272    fn test_logical_properties_serialization() {
273        let direction = LogicalDirection::InlineStart;
274        let serialized = serde_json::to_string(&direction).unwrap();
275        let deserialized: LogicalDirection = serde_json::from_str(&serialized).unwrap();
276        assert_eq!(direction, deserialized);
277    }
278
279    #[test]
280    fn test_logical_properties_comprehensive_usage() {
281        let classes = ClassBuilder::new()
282            .margin_inline_start_4()
283            .margin_inline_end_4()
284            .padding_inline_start_2()
285            .padding_inline_end_2()
286            .border_inline_start_1()
287            .border_inline_end_1();
288
289        let result = classes.build();
290        assert!(result.classes.contains("ms-4"));
291        assert!(result.classes.contains("me-4"));
292        assert!(result.classes.contains("ps-2"));
293        assert!(result.classes.contains("pe-2"));
294        assert!(result.classes.contains("border-s-1"));
295        assert!(result.classes.contains("border-e-1"));
296    }
297
298    #[test]
299    fn test_logical_properties_with_different_spacing_values() {
300        let classes = ClassBuilder::new()
301            .margin_inline_start(SpacingValue::Integer(8))
302            .margin_inline_end(SpacingValue::Integer(12))
303            .padding_inline_start(SpacingValue::Integer(6))
304            .padding_inline_end(SpacingValue::Integer(10));
305
306        let result = classes.build();
307        assert!(result.classes.contains("ms-8"));
308        assert!(result.classes.contains("me-12"));
309        assert!(result.classes.contains("ps-6"));
310        assert!(result.classes.contains("pe-10"));
311    }
312
313    #[test]
314    fn test_logical_properties_block_directions() {
315        let classes = ClassBuilder::new()
316            .margin_block_start(SpacingValue::Integer(4))
317            .margin_block_end(SpacingValue::Integer(4))
318            .padding_block_start(SpacingValue::Integer(2))
319            .padding_block_end(SpacingValue::Integer(2));
320
321        let result = classes.build();
322        assert!(result.classes.contains("mt-4"));
323        assert!(result.classes.contains("mb-4"));
324        assert!(result.classes.contains("pt-2"));
325        assert!(result.classes.contains("pb-2"));
326    }
327
328    #[test]
329    fn test_logical_properties_inset() {
330        let classes = ClassBuilder::new()
331            .inset_inline_start(SpacingValue::Integer(4))
332            .inset_inline_end(SpacingValue::Integer(4))
333            .inset_block_start(SpacingValue::Integer(2))
334            .inset_block_end(SpacingValue::Integer(2));
335
336        let result = classes.build();
337        assert!(result.classes.contains("start-4"));
338        assert!(result.classes.contains("end-4"));
339        assert!(result.classes.contains("top-2"));
340        assert!(result.classes.contains("bottom-2"));
341    }
342}