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