tailwind_rs_core/responsive/
flexbox.rs1use super::breakpoints::Breakpoint;
6use super::responsive_values::ResponsiveValue;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum FlexDirection {
12 Row,
14 RowReverse,
16 Column,
18 ColumnReverse,
20}
21
22impl FlexDirection {
23 pub fn to_class(&self) -> &'static str {
25 match self {
26 FlexDirection::Row => "flex-row",
27 FlexDirection::RowReverse => "flex-row-reverse",
28 FlexDirection::Column => "flex-col",
29 FlexDirection::ColumnReverse => "flex-col-reverse",
30 }
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
36pub enum FlexWrap {
37 NoWrap,
39 Wrap,
41 WrapReverse,
43}
44
45impl FlexWrap {
46 pub fn to_class(&self) -> &'static str {
48 match self {
49 FlexWrap::NoWrap => "flex-nowrap",
50 FlexWrap::Wrap => "flex-wrap",
51 FlexWrap::WrapReverse => "flex-wrap-reverse",
52 }
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
58pub enum JustifyContent {
59 Start,
61 End,
63 Center,
65 Between,
67 Around,
69 Evenly,
71}
72
73impl JustifyContent {
74 pub fn to_class(&self) -> &'static str {
76 match self {
77 JustifyContent::Start => "justify-start",
78 JustifyContent::End => "justify-end",
79 JustifyContent::Center => "justify-center",
80 JustifyContent::Between => "justify-between",
81 JustifyContent::Around => "justify-around",
82 JustifyContent::Evenly => "justify-evenly",
83 }
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
89pub enum AlignItems {
90 Start,
92 End,
94 Center,
96 Baseline,
98 Stretch,
100}
101
102impl AlignItems {
103 pub fn to_class(&self) -> &'static str {
105 match self {
106 AlignItems::Start => "items-start",
107 AlignItems::End => "items-end",
108 AlignItems::Center => "items-center",
109 AlignItems::Baseline => "items-baseline",
110 AlignItems::Stretch => "items-stretch",
111 }
112 }
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
117pub struct ResponsiveFlex {
118 pub direction: ResponsiveValue<FlexDirection>,
120 pub wrap: ResponsiveValue<FlexWrap>,
122 pub justify: ResponsiveValue<JustifyContent>,
124 pub align: ResponsiveValue<AlignItems>,
126 pub gap: ResponsiveValue<u32>,
128}
129
130impl ResponsiveFlex {
131 pub fn new() -> Self {
133 Self::default()
134 }
135
136 pub fn with_base(
138 direction: FlexDirection,
139 wrap: FlexWrap,
140 justify: JustifyContent,
141 align: AlignItems,
142 gap: u32,
143 ) -> Self {
144 Self {
145 direction: ResponsiveValue::with_base(direction),
146 wrap: ResponsiveValue::with_base(wrap),
147 justify: ResponsiveValue::with_base(justify),
148 align: ResponsiveValue::with_base(align),
149 gap: ResponsiveValue::with_base(gap),
150 }
151 }
152
153 pub fn set_direction(&mut self, breakpoint: Breakpoint, direction: FlexDirection) {
155 self.direction.set_breakpoint(breakpoint, direction);
156 }
157
158 pub fn set_wrap(&mut self, breakpoint: Breakpoint, wrap: FlexWrap) {
160 self.wrap.set_breakpoint(breakpoint, wrap);
161 }
162
163 pub fn set_justify(&mut self, breakpoint: Breakpoint, justify: JustifyContent) {
165 self.justify.set_breakpoint(breakpoint, justify);
166 }
167
168 pub fn set_align(&mut self, breakpoint: Breakpoint, align: AlignItems) {
170 self.align.set_breakpoint(breakpoint, align);
171 }
172
173 pub fn set_gap(&mut self, breakpoint: Breakpoint, gap: u32) {
175 self.gap.set_breakpoint(breakpoint, gap);
176 }
177
178 pub fn get_direction(&self, breakpoint: Breakpoint) -> Option<FlexDirection> {
180 self.direction.get_breakpoint(breakpoint).copied()
181 }
182
183 pub fn get_wrap(&self, breakpoint: Breakpoint) -> Option<FlexWrap> {
185 self.wrap.get_breakpoint(breakpoint).copied()
186 }
187
188 pub fn get_justify(&self, breakpoint: Breakpoint) -> Option<JustifyContent> {
190 self.justify.get_breakpoint(breakpoint).copied()
191 }
192
193 pub fn get_align(&self, breakpoint: Breakpoint) -> Option<AlignItems> {
195 self.align.get_breakpoint(breakpoint).copied()
196 }
197
198 pub fn get_gap(&self, breakpoint: Breakpoint) -> Option<u32> {
200 self.gap.get_breakpoint(breakpoint).copied()
201 }
202
203 pub fn to_css_classes(&self) -> String {
205 let mut classes = Vec::new();
206
207 let direction_classes = self.direction.to_css_classes(|d| d.to_class().to_string());
209 if !direction_classes.is_empty() {
210 classes.push(direction_classes);
211 }
212
213 let wrap_classes = self.wrap.to_css_classes(|w| w.to_class().to_string());
215 if !wrap_classes.is_empty() {
216 classes.push(wrap_classes);
217 }
218
219 let justify_classes = self.justify.to_css_classes(|j| j.to_class().to_string());
221 if !justify_classes.is_empty() {
222 classes.push(justify_classes);
223 }
224
225 let align_classes = self.align.to_css_classes(|a| a.to_class().to_string());
227 if !align_classes.is_empty() {
228 classes.push(align_classes);
229 }
230
231 let gap_classes = self.gap.to_css_classes(|g| {
233 if *g == 0 {
234 "gap-0".to_string()
235 } else {
236 format!("gap-{}", g)
237 }
238 });
239 if !gap_classes.is_empty() {
240 classes.push(gap_classes);
241 }
242
243 classes.join(" ")
244 }
245
246 pub fn to_css_classes_for_width(&self, screen_width: u32) -> String {
248 let mut classes = Vec::new();
249
250 if let Some(direction) = self.direction.get_for_width(screen_width) {
252 classes.push(direction.to_class().to_string());
253 }
254
255 if let Some(wrap) = self.wrap.get_for_width(screen_width) {
257 classes.push(wrap.to_class().to_string());
258 }
259
260 if let Some(justify) = self.justify.get_for_width(screen_width) {
262 classes.push(justify.to_class().to_string());
263 }
264
265 if let Some(align) = self.align.get_for_width(screen_width) {
267 classes.push(align.to_class().to_string());
268 }
269
270 if let Some(gap) = self.gap.get_for_width(screen_width) {
272 if *gap == 0 {
273 classes.push("gap-0".to_string());
274 } else {
275 classes.push(format!("gap-{}", gap));
276 }
277 }
278
279 classes.join(" ")
280 }
281}
282
283impl Default for ResponsiveFlex {
284 fn default() -> Self {
285 Self {
286 direction: ResponsiveValue::with_base(FlexDirection::Row),
287 wrap: ResponsiveValue::with_base(FlexWrap::NoWrap),
288 justify: ResponsiveValue::with_base(JustifyContent::Start),
289 align: ResponsiveValue::with_base(AlignItems::Stretch),
290 gap: ResponsiveValue::with_base(0),
291 }
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn test_flex_direction_to_class() {
301 assert_eq!(FlexDirection::Row.to_class(), "flex-row");
302 assert_eq!(FlexDirection::RowReverse.to_class(), "flex-row-reverse");
303 assert_eq!(FlexDirection::Column.to_class(), "flex-col");
304 assert_eq!(FlexDirection::ColumnReverse.to_class(), "flex-col-reverse");
305 }
306
307 #[test]
308 fn test_flex_wrap_to_class() {
309 assert_eq!(FlexWrap::NoWrap.to_class(), "flex-nowrap");
310 assert_eq!(FlexWrap::Wrap.to_class(), "flex-wrap");
311 assert_eq!(FlexWrap::WrapReverse.to_class(), "flex-wrap-reverse");
312 }
313
314 #[test]
315 fn test_justify_content_to_class() {
316 assert_eq!(JustifyContent::Start.to_class(), "justify-start");
317 assert_eq!(JustifyContent::End.to_class(), "justify-end");
318 assert_eq!(JustifyContent::Center.to_class(), "justify-center");
319 assert_eq!(JustifyContent::Between.to_class(), "justify-between");
320 assert_eq!(JustifyContent::Around.to_class(), "justify-around");
321 assert_eq!(JustifyContent::Evenly.to_class(), "justify-evenly");
322 }
323
324 #[test]
325 fn test_align_items_to_class() {
326 assert_eq!(AlignItems::Start.to_class(), "items-start");
327 assert_eq!(AlignItems::End.to_class(), "items-end");
328 assert_eq!(AlignItems::Center.to_class(), "items-center");
329 assert_eq!(AlignItems::Baseline.to_class(), "items-baseline");
330 assert_eq!(AlignItems::Stretch.to_class(), "items-stretch");
331 }
332
333 #[test]
334 fn test_responsive_flex_new() {
335 let flex = ResponsiveFlex::new();
336 assert_eq!(
337 flex.get_direction(Breakpoint::Base),
338 Some(FlexDirection::Row)
339 );
340 assert_eq!(flex.get_wrap(Breakpoint::Base), Some(FlexWrap::NoWrap));
341 assert_eq!(
342 flex.get_justify(Breakpoint::Base),
343 Some(JustifyContent::Start)
344 );
345 assert_eq!(flex.get_align(Breakpoint::Base), Some(AlignItems::Stretch));
346 assert_eq!(flex.get_gap(Breakpoint::Base), Some(0));
347 }
348
349 #[test]
350 fn test_responsive_flex_with_base() {
351 let flex = ResponsiveFlex::with_base(
352 FlexDirection::Column,
353 FlexWrap::Wrap,
354 JustifyContent::Center,
355 AlignItems::Center,
356 4,
357 );
358
359 assert_eq!(
360 flex.get_direction(Breakpoint::Base),
361 Some(FlexDirection::Column)
362 );
363 assert_eq!(flex.get_wrap(Breakpoint::Base), Some(FlexWrap::Wrap));
364 assert_eq!(
365 flex.get_justify(Breakpoint::Base),
366 Some(JustifyContent::Center)
367 );
368 assert_eq!(flex.get_align(Breakpoint::Base), Some(AlignItems::Center));
369 assert_eq!(flex.get_gap(Breakpoint::Base), Some(4));
370 }
371
372 #[test]
373 fn test_responsive_flex_set_get() {
374 let mut flex = ResponsiveFlex::new();
375
376 flex.set_direction(Breakpoint::Sm, FlexDirection::Column);
377 flex.set_wrap(Breakpoint::Md, FlexWrap::Wrap);
378 flex.set_justify(Breakpoint::Lg, JustifyContent::Between);
379 flex.set_align(Breakpoint::Xl, AlignItems::Center);
380 flex.set_gap(Breakpoint::Xl2, 8);
381
382 assert_eq!(
383 flex.get_direction(Breakpoint::Sm),
384 Some(FlexDirection::Column)
385 );
386 assert_eq!(flex.get_wrap(Breakpoint::Md), Some(FlexWrap::Wrap));
387 assert_eq!(
388 flex.get_justify(Breakpoint::Lg),
389 Some(JustifyContent::Between)
390 );
391 assert_eq!(flex.get_align(Breakpoint::Xl), Some(AlignItems::Center));
392 assert_eq!(flex.get_gap(Breakpoint::Xl2), Some(8));
393 }
394
395 #[test]
396 fn test_responsive_flex_to_css_classes() {
397 let mut flex = ResponsiveFlex::new();
398 flex.set_direction(Breakpoint::Sm, FlexDirection::Column);
399 flex.set_justify(Breakpoint::Md, JustifyContent::Center);
400 flex.set_gap(Breakpoint::Lg, 4);
401
402 let classes = flex.to_css_classes();
403 assert!(classes.contains("flex-row"));
404 assert!(classes.contains("sm:flex-col"));
405 assert!(classes.contains("md:justify-center"));
406 assert!(classes.contains("lg:gap-4"));
407 }
408
409 #[test]
410 fn test_responsive_flex_to_css_classes_for_width() {
411 let mut flex = ResponsiveFlex::new();
412 flex.set_direction(Breakpoint::Sm, FlexDirection::Column);
413 flex.set_justify(Breakpoint::Md, JustifyContent::Center);
414 flex.set_gap(Breakpoint::Lg, 4);
415
416 let classes_0 = flex.to_css_classes_for_width(0);
418 assert!(classes_0.contains("flex-row"));
419 assert!(!classes_0.contains("flex-col"));
420 assert!(!classes_0.contains("justify-center"));
421 assert!(!classes_0.contains("gap-4"));
422
423 let classes_640 = flex.to_css_classes_for_width(640);
425 assert!(classes_640.contains("flex-col"));
426 assert!(!classes_640.contains("justify-center"));
427 assert!(!classes_640.contains("gap-4"));
428
429 let classes_768 = flex.to_css_classes_for_width(768);
431 assert!(classes_768.contains("flex-col"));
432 assert!(classes_768.contains("justify-center"));
433 assert!(!classes_768.contains("gap-4"));
434
435 let classes_1024 = flex.to_css_classes_for_width(1024);
437 assert!(classes_1024.contains("flex-col"));
438 assert!(classes_1024.contains("justify-center"));
439 assert!(classes_1024.contains("gap-4"));
440 }
441}