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}
71
72impl LogicalPropertiesUtilities for ClassBuilder {
73    fn margin_inline_start(self, value: SpacingValue) -> Self {
74        let class_name = format!("ms-{}", value.to_string());
75        self.class(class_name)
76    }
77
78    fn margin_inline_end(self, value: SpacingValue) -> Self {
79        let class_name = format!("me-{}", value.to_string());
80        self.class(class_name)
81    }
82
83    fn margin_block_start(self, value: SpacingValue) -> Self {
84        let class_name = format!("mt-{}", value.to_string());
85        self.class(class_name)
86    }
87
88    fn margin_block_end(self, value: SpacingValue) -> Self {
89        let class_name = format!("mb-{}", value.to_string());
90        self.class(class_name)
91    }
92
93    fn padding_inline_start(self, value: SpacingValue) -> Self {
94        let class_name = format!("ps-{}", value.to_string());
95        self.class(class_name)
96    }
97
98    fn padding_inline_end(self, value: SpacingValue) -> Self {
99        let class_name = format!("pe-{}", value.to_string());
100        self.class(class_name)
101    }
102
103    fn padding_block_start(self, value: SpacingValue) -> Self {
104        let class_name = format!("pt-{}", value.to_string());
105        self.class(class_name)
106    }
107
108    fn padding_block_end(self, value: SpacingValue) -> Self {
109        let class_name = format!("pb-{}", value.to_string());
110        self.class(class_name)
111    }
112
113    fn border_inline_start(self, value: SpacingValue) -> Self {
114        let class_name = format!("border-s-{}", value.to_string());
115        self.class(class_name)
116    }
117
118    fn border_inline_end(self, value: SpacingValue) -> Self {
119        let class_name = format!("border-e-{}", value.to_string());
120        self.class(class_name)
121    }
122
123    fn border_block_start(self, value: SpacingValue) -> Self {
124        let class_name = format!("border-t-{}", value.to_string());
125        self.class(class_name)
126    }
127
128    fn border_block_end(self, value: SpacingValue) -> Self {
129        let class_name = format!("border-b-{}", value.to_string());
130        self.class(class_name)
131    }
132
133    fn inset_inline_start(self, value: SpacingValue) -> Self {
134        let class_name = format!("start-{}", value.to_string());
135        self.class(class_name)
136    }
137
138    fn inset_inline_end(self, value: SpacingValue) -> Self {
139        let class_name = format!("end-{}", value.to_string());
140        self.class(class_name)
141    }
142
143    fn inset_block_start(self, value: SpacingValue) -> Self {
144        let class_name = format!("top-{}", value.to_string());
145        self.class(class_name)
146    }
147
148    fn inset_block_end(self, value: SpacingValue) -> Self {
149        let class_name = format!("bottom-{}", value.to_string());
150        self.class(class_name)
151    }
152
153}
154
155/// Convenience methods for common spacing values
156pub trait LogicalPropertiesConvenience {
157    /// Set margin inline start to 4
158    fn margin_inline_start_4(self) -> Self;
159    /// Set margin inline end to 4
160    fn margin_inline_end_4(self) -> Self;
161    /// Set padding inline start to 2
162    fn padding_inline_start_2(self) -> Self;
163    /// Set padding inline end to 2
164    fn padding_inline_end_2(self) -> Self;
165    /// Set padding inline start to 4
166    fn padding_inline_start_4(self) -> Self;
167    /// Set padding inline end to 4
168    fn padding_inline_end_4(self) -> Self;
169    /// Set border inline start to 1
170    fn border_inline_start_1(self) -> Self;
171    /// Set border inline end to 1
172    fn border_inline_end_1(self) -> Self;
173    /// Set border inline start to 2
174    fn border_inline_start_2(self) -> Self;
175    /// Set border inline end to 2
176    fn border_inline_end_2(self) -> Self;
177}
178
179impl LogicalPropertiesConvenience for ClassBuilder {
180    fn margin_inline_start_4(self) -> Self {
181        self.margin_inline_start(SpacingValue::Integer(4))
182    }
183
184    fn margin_inline_end_4(self) -> Self {
185        self.margin_inline_end(SpacingValue::Integer(4))
186    }
187
188    fn padding_inline_start_2(self) -> Self {
189        self.padding_inline_start(SpacingValue::Integer(2))
190    }
191
192    fn padding_inline_end_2(self) -> Self {
193        self.padding_inline_end(SpacingValue::Integer(2))
194    }
195
196    fn border_inline_start_1(self) -> Self {
197        self.border_inline_start(SpacingValue::Integer(1))
198    }
199
200    fn border_inline_end_1(self) -> Self {
201        self.border_inline_end(SpacingValue::Integer(1))
202    }
203
204    fn padding_inline_start_4(self) -> Self {
205        self.padding_inline_start(SpacingValue::Integer(4))
206    }
207
208    fn padding_inline_end_4(self) -> Self {
209        self.padding_inline_end(SpacingValue::Integer(4))
210    }
211
212    fn border_inline_start_2(self) -> Self {
213        self.border_inline_start(SpacingValue::Integer(2))
214    }
215
216    fn border_inline_end_2(self) -> Self {
217        self.border_inline_end(SpacingValue::Integer(2))
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224    use crate::classes::ClassBuilder;
225    use crate::utilities::spacing::SpacingValue;
226
227    #[test]
228    fn test_logical_direction_enum_values() {
229        assert_eq!(LogicalDirection::InlineStart.to_string(), "inline-start");
230        assert_eq!(LogicalDirection::InlineEnd.to_string(), "inline-end");
231        assert_eq!(LogicalDirection::BlockStart.to_string(), "block-start");
232        assert_eq!(LogicalDirection::BlockEnd.to_string(), "block-end");
233    }
234
235    #[test]
236    fn test_logical_properties_utilities() {
237        let classes = ClassBuilder::new()
238            .margin_inline_start(SpacingValue::Integer(4))
239            .margin_inline_end(SpacingValue::Integer(4))
240            .padding_inline_start(SpacingValue::Integer(2))
241            .padding_inline_end(SpacingValue::Integer(2))
242            .border_inline_start(SpacingValue::Integer(1))
243            .border_inline_end(SpacingValue::Integer(1));
244
245        let result = classes.build();
246        assert!(result.classes.contains("ms-4"));
247        assert!(result.classes.contains("me-4"));
248        assert!(result.classes.contains("ps-2"));
249        assert!(result.classes.contains("pe-2"));
250        assert!(result.classes.contains("border-s-1"));
251        assert!(result.classes.contains("border-e-1"));
252    }
253
254    #[test]
255    fn test_logical_properties_convenience() {
256        let classes = ClassBuilder::new()
257            .margin_inline_start_4()
258            .margin_inline_end_4()
259            .padding_inline_start_4()
260            .padding_inline_end_4()
261            .border_inline_start_2()
262            .border_inline_end_2();
263
264        let result = classes.build();
265        assert!(result.classes.contains("ms-4"));
266        assert!(result.classes.contains("me-4"));
267        assert!(result.classes.contains("ps-4"));
268        assert!(result.classes.contains("pe-4"));
269        assert!(result.classes.contains("border-s-2"));
270        assert!(result.classes.contains("border-e-2"));
271    }
272
273    #[test]
274    fn test_logical_properties_serialization() {
275        let direction = LogicalDirection::InlineStart;
276        let serialized = serde_json::to_string(&direction).unwrap();
277        let deserialized: LogicalDirection = serde_json::from_str(&serialized).unwrap();
278        assert_eq!(direction, deserialized);
279    }
280
281    #[test]
282    fn test_logical_properties_comprehensive_usage() {
283        let classes = ClassBuilder::new()
284            .margin_inline_start_4()
285            .margin_inline_end_4()
286            .padding_inline_start_2()
287            .padding_inline_end_2()
288            .border_inline_start_1()
289            .border_inline_end_1();
290
291        let result = classes.build();
292        assert!(result.classes.contains("ms-4"));
293        assert!(result.classes.contains("me-4"));
294        assert!(result.classes.contains("ps-2"));
295        assert!(result.classes.contains("pe-2"));
296        assert!(result.classes.contains("border-s-1"));
297        assert!(result.classes.contains("border-e-1"));
298    }
299
300    #[test]
301    fn test_logical_properties_with_different_spacing_values() {
302        let classes = ClassBuilder::new()
303            .margin_inline_start(SpacingValue::Integer(8))
304            .margin_inline_end(SpacingValue::Integer(12))
305            .padding_inline_start(SpacingValue::Integer(6))
306            .padding_inline_end(SpacingValue::Integer(10));
307
308        let result = classes.build();
309        assert!(result.classes.contains("ms-8"));
310        assert!(result.classes.contains("me-12"));
311        assert!(result.classes.contains("ps-6"));
312        assert!(result.classes.contains("pe-10"));
313    }
314
315    #[test]
316    fn test_logical_properties_block_directions() {
317        let classes = ClassBuilder::new()
318            .margin_block_start(SpacingValue::Integer(4))
319            .margin_block_end(SpacingValue::Integer(4))
320            .padding_block_start(SpacingValue::Integer(2))
321            .padding_block_end(SpacingValue::Integer(2));
322
323        let result = classes.build();
324        assert!(result.classes.contains("mt-4"));
325        assert!(result.classes.contains("mb-4"));
326        assert!(result.classes.contains("pt-2"));
327        assert!(result.classes.contains("pb-2"));
328    }
329
330    #[test]
331    fn test_logical_properties_inset() {
332        let classes = ClassBuilder::new()
333            .inset_inline_start(SpacingValue::Integer(4))
334            .inset_inline_end(SpacingValue::Integer(4))
335            .inset_block_start(SpacingValue::Integer(2))
336            .inset_block_end(SpacingValue::Integer(2));
337
338        let result = classes.build();
339        assert!(result.classes.contains("start-4"));
340        assert!(result.classes.contains("end-4"));
341        assert!(result.classes.contains("top-2"));
342        assert!(result.classes.contains("bottom-2"));
343    }
344}