1use crate::error::{Result, TailwindError};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum Breakpoint {
10 Base,
12 Sm,
14 Md,
16 Lg,
18 Xl,
20 Xl2,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub enum State {
27 Hover,
29 Focus,
31 Active,
33 Visited,
35 Disabled,
37 Checked,
39 Indeterminate,
41 Required,
43 Valid,
45 Invalid,
47 InRange,
49 OutOfRange,
51 ReadOnly,
53 ReadWrite,
55 Optional,
57 PlaceholderShown,
59 Autofill,
61 Default,
63 FirstChild,
65 LastChild,
67 OnlyChild,
69 FirstOfType,
71 LastOfType,
73 OnlyOfType,
75 Empty,
77 Target,
79 Root,
81 Not,
83 Where,
85 Is,
87 Has,
89 Before,
91 After,
93 FirstLetter,
95 FirstLine,
97 Selection,
99 Marker,
101 Placeholder,
103 File,
105 Backdrop,
107 AnyLink,
109 Link,
111 LocalLink,
113 Scope,
115 Current,
117 Past,
119 Future,
121 Playing,
123 Paused,
125 Seeking,
127 Buffering,
129 Stalled,
131 Muted,
133 VolumeLocked,
135 UserInvalid,
137 UserValid,
139 Modal,
141 PictureInPicture,
143 Fullscreen,
145 Resize,
147 Scroll,
149 Snap,
151 Touch,
153 UserSelect,
155 WillChange,
157 AccentColor,
159 Appearance,
161 Cursor,
163 Outline,
165}
166
167impl State {
168 pub fn prefix(&self) -> String {
170 match self {
171 State::Hover => "hover:".to_string(),
172 State::Focus => "focus:".to_string(),
173 State::Active => "active:".to_string(),
174 State::Visited => "visited:".to_string(),
175 State::Disabled => "disabled:".to_string(),
176 State::Checked => "checked:".to_string(),
177 State::Indeterminate => "indeterminate:".to_string(),
178 State::Required => "required:".to_string(),
179 State::Valid => "valid:".to_string(),
180 State::Invalid => "invalid:".to_string(),
181 State::InRange => "in-range:".to_string(),
182 State::OutOfRange => "out-of-range:".to_string(),
183 State::ReadOnly => "read-only:".to_string(),
184 State::ReadWrite => "read-write:".to_string(),
185 State::Optional => "optional:".to_string(),
186 State::PlaceholderShown => "placeholder-shown:".to_string(),
187 State::Autofill => "autofill:".to_string(),
188 State::Default => "default:".to_string(),
189 State::FirstChild => "first-child:".to_string(),
190 State::LastChild => "last-child:".to_string(),
191 State::OnlyChild => "only-child:".to_string(),
192 State::FirstOfType => "first-of-type:".to_string(),
193 State::LastOfType => "last-of-type:".to_string(),
194 State::OnlyOfType => "only-of-type:".to_string(),
195 State::Empty => "empty:".to_string(),
196 State::Target => "target:".to_string(),
197 State::Root => "root:".to_string(),
198 State::Not => "not:".to_string(),
199 State::Where => "where:".to_string(),
200 State::Is => "is:".to_string(),
201 State::Has => "has:".to_string(),
202 State::Before => "before:".to_string(),
203 State::After => "after:".to_string(),
204 State::FirstLetter => "first-letter:".to_string(),
205 State::FirstLine => "first-line:".to_string(),
206 State::Selection => "selection:".to_string(),
207 State::Marker => "marker:".to_string(),
208 State::Placeholder => "placeholder:".to_string(),
209 State::File => "file:".to_string(),
210 State::Backdrop => "backdrop:".to_string(),
211 State::AnyLink => "any-link:".to_string(),
212 State::Link => "link:".to_string(),
213 State::LocalLink => "local-link:".to_string(),
214 State::Scope => "scope:".to_string(),
215 State::Current => "current:".to_string(),
216 State::Past => "past:".to_string(),
217 State::Future => "future:".to_string(),
218 State::Playing => "playing:".to_string(),
219 State::Paused => "paused:".to_string(),
220 State::Seeking => "seeking:".to_string(),
221 State::Buffering => "buffering:".to_string(),
222 State::Stalled => "stalled:".to_string(),
223 State::Muted => "muted:".to_string(),
224 State::VolumeLocked => "volume-locked:".to_string(),
225 State::UserInvalid => "user-invalid:".to_string(),
226 State::UserValid => "user-valid:".to_string(),
227 State::Modal => "modal:".to_string(),
228 State::PictureInPicture => "picture-in-picture:".to_string(),
229 State::Fullscreen => "fullscreen:".to_string(),
230 State::Resize => "resize:".to_string(),
231 State::Scroll => "scroll:".to_string(),
232 State::Snap => "snap:".to_string(),
233 State::Touch => "touch:".to_string(),
234 State::UserSelect => "user-select:".to_string(),
235 State::WillChange => "will-change:".to_string(),
236 State::AccentColor => "accent-color:".to_string(),
237 State::Appearance => "appearance:".to_string(),
238 State::Cursor => "cursor:".to_string(),
239 State::Outline => "outline:".to_string(),
240 }
241 }
242
243 #[allow(clippy::should_implement_trait)]
245 pub fn from_str(s: &str) -> Result<Self> {
246 match s {
247 "hover" => Ok(State::Hover),
248 "focus" => Ok(State::Focus),
249 "active" => Ok(State::Active),
250 "visited" => Ok(State::Visited),
251 "disabled" => Ok(State::Disabled),
252 "checked" => Ok(State::Checked),
253 "indeterminate" => Ok(State::Indeterminate),
254 "required" => Ok(State::Required),
255 "valid" => Ok(State::Valid),
256 "invalid" => Ok(State::Invalid),
257 "in-range" => Ok(State::InRange),
258 "out-of-range" => Ok(State::OutOfRange),
259 "read-only" => Ok(State::ReadOnly),
260 "read-write" => Ok(State::ReadWrite),
261 "optional" => Ok(State::Optional),
262 "placeholder-shown" => Ok(State::PlaceholderShown),
263 "autofill" => Ok(State::Autofill),
264 "default" => Ok(State::Default),
265 "first-child" => Ok(State::FirstChild),
266 "last-child" => Ok(State::LastChild),
267 "only-child" => Ok(State::OnlyChild),
268 "first-of-type" => Ok(State::FirstOfType),
269 "last-of-type" => Ok(State::LastOfType),
270 "only-of-type" => Ok(State::OnlyOfType),
271 "empty" => Ok(State::Empty),
272 "target" => Ok(State::Target),
273 "root" => Ok(State::Root),
274 "not" => Ok(State::Not),
275 "where" => Ok(State::Where),
276 "is" => Ok(State::Is),
277 "has" => Ok(State::Has),
278 "before" => Ok(State::Before),
279 "after" => Ok(State::After),
280 "first-letter" => Ok(State::FirstLetter),
281 "first-line" => Ok(State::FirstLine),
282 "selection" => Ok(State::Selection),
283 "marker" => Ok(State::Marker),
284 "placeholder" => Ok(State::Placeholder),
285 "file" => Ok(State::File),
286 "backdrop" => Ok(State::Backdrop),
287 "any-link" => Ok(State::AnyLink),
288 "link" => Ok(State::Link),
289 "local-link" => Ok(State::LocalLink),
290 "scope" => Ok(State::Scope),
291 "current" => Ok(State::Current),
292 "past" => Ok(State::Past),
293 "future" => Ok(State::Future),
294 "playing" => Ok(State::Playing),
295 "paused" => Ok(State::Paused),
296 "seeking" => Ok(State::Seeking),
297 "buffering" => Ok(State::Buffering),
298 "stalled" => Ok(State::Stalled),
299 "muted" => Ok(State::Muted),
300 "volume-locked" => Ok(State::VolumeLocked),
301 "user-invalid" => Ok(State::UserInvalid),
302 "user-valid" => Ok(State::UserValid),
303 "modal" => Ok(State::Modal),
304 "picture-in-picture" => Ok(State::PictureInPicture),
305 "fullscreen" => Ok(State::Fullscreen),
306 "resize" => Ok(State::Resize),
307 "scroll" => Ok(State::Scroll),
308 "snap" => Ok(State::Snap),
309 "touch" => Ok(State::Touch),
310 "user-select" => Ok(State::UserSelect),
311 "will-change" => Ok(State::WillChange),
312 "accent-color" => Ok(State::AccentColor),
313 "appearance" => Ok(State::Appearance),
314 "cursor" => Ok(State::Cursor),
315 "outline" => Ok(State::Outline),
316 _ => Err(TailwindError::config(format!("Invalid state: {}", s))),
317 }
318 }
319}
320
321impl Breakpoint {
322 pub fn min_width(&self) -> u32 {
324 match self {
325 Breakpoint::Base => 0,
326 Breakpoint::Sm => 640,
327 Breakpoint::Md => 768,
328 Breakpoint::Lg => 1024,
329 Breakpoint::Xl => 1280,
330 Breakpoint::Xl2 => 1536,
331 }
332 }
333
334 pub fn media_query(&self) -> String {
336 match self {
337 Breakpoint::Base => String::new(),
338 _ => format!("(min-width: {}px)", self.min_width()),
339 }
340 }
341
342 pub fn prefix(&self) -> String {
344 match self {
345 Breakpoint::Base => String::new(),
346 Breakpoint::Sm => "sm:".to_string(),
347 Breakpoint::Md => "md:".to_string(),
348 Breakpoint::Lg => "lg:".to_string(),
349 Breakpoint::Xl => "xl:".to_string(),
350 Breakpoint::Xl2 => "2xl:".to_string(),
351 }
352 }
353
354 #[allow(clippy::should_implement_trait)]
356 pub fn from_str(s: &str) -> Result<Self> {
357 match s {
358 "" | "base" => Ok(Breakpoint::Base),
359 "sm" => Ok(Breakpoint::Sm),
360 "md" => Ok(Breakpoint::Md),
361 "lg" => Ok(Breakpoint::Lg),
362 "xl" => Ok(Breakpoint::Xl),
363 "2xl" => Ok(Breakpoint::Xl2),
364 _ => Err(TailwindError::config(format!("Invalid breakpoint: {}", s))),
365 }
366 }
367}
368
369#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
371pub struct ResponsiveValue<T> {
372 pub base: T,
373 pub sm: Option<T>,
374 pub md: Option<T>,
375 pub lg: Option<T>,
376 pub xl: Option<T>,
377 pub xl2: Option<T>,
378}
379
380impl<T> ResponsiveValue<T> {
381 pub fn new(base: T) -> Self {
383 Self {
384 base,
385 sm: None,
386 md: None,
387 lg: None,
388 xl: None,
389 xl2: None,
390 }
391 }
392
393 pub fn set_breakpoint(&mut self, breakpoint: Breakpoint, value: T) {
395 match breakpoint {
396 Breakpoint::Base => self.base = value,
397 Breakpoint::Sm => self.sm = Some(value),
398 Breakpoint::Md => self.md = Some(value),
399 Breakpoint::Lg => self.lg = Some(value),
400 Breakpoint::Xl => self.xl = Some(value),
401 Breakpoint::Xl2 => self.xl2 = Some(value),
402 }
403 }
404
405 pub fn get_breakpoint(&self, breakpoint: Breakpoint) -> &T {
407 match breakpoint {
408 Breakpoint::Base => &self.base,
409 Breakpoint::Sm => self.sm.as_ref().unwrap_or(&self.base),
410 Breakpoint::Md => self.md.as_ref().unwrap_or(&self.base),
411 Breakpoint::Lg => self.lg.as_ref().unwrap_or(&self.base),
412 Breakpoint::Xl => self.xl.as_ref().unwrap_or(&self.base),
413 Breakpoint::Xl2 => self.xl2.as_ref().unwrap_or(&self.base),
414 }
415 }
416
417 pub fn all_values(&self) -> Vec<(Breakpoint, &T)> {
419 let mut values = vec![(Breakpoint::Base, &self.base)];
420
421 if let Some(ref sm) = self.sm {
422 values.push((Breakpoint::Sm, sm));
423 }
424 if let Some(ref md) = self.md {
425 values.push((Breakpoint::Md, md));
426 }
427 if let Some(ref lg) = self.lg {
428 values.push((Breakpoint::Lg, lg));
429 }
430 if let Some(ref xl) = self.xl {
431 values.push((Breakpoint::Xl, xl));
432 }
433 if let Some(ref xl2) = self.xl2 {
434 values.push((Breakpoint::Xl2, xl2));
435 }
436
437 values
438 }
439}
440
441#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
443pub struct ResponsiveConfig {
444 pub breakpoints: HashMap<String, u32>,
445 pub container_centering: bool,
446 pub container_padding: ResponsiveValue<u32>,
447}
448
449impl ResponsiveConfig {
450 pub fn new() -> Self {
452 let mut breakpoints = HashMap::new();
453 breakpoints.insert("sm".to_string(), 640);
454 breakpoints.insert("md".to_string(), 768);
455 breakpoints.insert("lg".to_string(), 1024);
456 breakpoints.insert("xl".to_string(), 1280);
457 breakpoints.insert("2xl".to_string(), 1536);
458
459 Self {
460 breakpoints,
461 container_centering: true,
462 container_padding: ResponsiveValue::new(16),
463 }
464 }
465
466 pub fn get_breakpoint(&self, name: &str) -> Result<u32> {
468 self.breakpoints
469 .get(name)
470 .copied()
471 .ok_or_else(|| TailwindError::config(format!("Breakpoint '{}' not found", name)))
472 }
473
474 pub fn set_breakpoint(&mut self, name: String, value: u32) {
476 self.breakpoints.insert(name, value);
477 }
478}
479
480impl Default for ResponsiveConfig {
481 fn default() -> Self {
482 Self::new()
483 }
484}
485
486#[derive(Debug, Clone, PartialEq)]
488pub struct Responsive {
489 pub sm: Option<String>,
490 pub md: Option<String>,
491 pub lg: Option<String>,
492 pub xl: Option<String>,
493 pub xl2: Option<String>,
494}
495
496impl Responsive {
497 pub fn new() -> Self {
499 Self {
500 sm: None,
501 md: None,
502 lg: None,
503 xl: None,
504 xl2: None,
505 }
506 }
507
508 pub fn sm(mut self, classes: &str) -> Self {
510 self.sm = Some(classes.to_string());
511 self
512 }
513
514 pub fn md(mut self, classes: &str) -> Self {
516 self.md = Some(classes.to_string());
517 self
518 }
519
520 pub fn lg(mut self, classes: &str) -> Self {
522 self.lg = Some(classes.to_string());
523 self
524 }
525
526 pub fn xl(mut self, classes: &str) -> Self {
528 self.xl = Some(classes.to_string());
529 self
530 }
531
532 pub fn xl2(mut self, classes: &str) -> Self {
534 self.xl2 = Some(classes.to_string());
535 self
536 }
537
538 pub fn build(self) -> String {
540 let mut classes = Vec::new();
541
542 if let Some(sm) = self.sm {
543 classes.push(format!("sm:{}", sm));
544 }
545 if let Some(md) = self.md {
546 classes.push(format!("md:{}", md));
547 }
548 if let Some(lg) = self.lg {
549 classes.push(format!("lg:{}", lg));
550 }
551 if let Some(xl) = self.xl {
552 classes.push(format!("xl:{}", xl));
553 }
554 if let Some(xl2) = self.xl2 {
555 classes.push(format!("2xl:{}", xl2));
556 }
557
558 classes.join(" ")
559 }
560}
561
562impl Default for Responsive {
563 fn default() -> Self {
564 Self::new()
565 }
566}
567
568#[derive(Debug, Clone)]
570pub struct ResponsiveBuilder {
571 base_classes: Vec<String>,
572 breakpoint_classes: HashMap<Breakpoint, Vec<String>>,
573}
574
575impl ResponsiveBuilder {
576 pub fn new() -> Self {
578 Self {
579 base_classes: Vec::new(),
580 breakpoint_classes: HashMap::new(),
581 }
582 }
583
584 pub fn base(mut self, classes: &str) -> Self {
586 self.base_classes
587 .extend(classes.split_whitespace().map(|s| s.to_string()));
588 self
589 }
590
591 pub fn breakpoint(mut self, breakpoint: Breakpoint, classes: &str) -> Self {
593 let breakpoint_classes = self
594 .breakpoint_classes
595 .entry(breakpoint)
596 .or_default();
597 breakpoint_classes.extend(classes.split_whitespace().map(|s| s.to_string()));
598 self
599 }
600
601 pub fn build(self) -> String {
603 let mut classes = Vec::new();
604
605 classes.extend(self.base_classes);
607
608 let mut breakpoint_classes: Vec<(Breakpoint, Vec<String>)> =
610 self.breakpoint_classes.into_iter().collect();
611 breakpoint_classes.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
612
613 for (breakpoint, class_list) in breakpoint_classes {
614 for class in class_list {
615 classes.push(format!("{}{}", breakpoint.prefix(), class));
616 }
617 }
618
619 classes.join(" ")
620 }
621}
622
623impl Default for ResponsiveBuilder {
624 fn default() -> Self {
625 Self::new()
626 }
627}
628
629#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
631pub enum FlexDirection {
632 Row,
633 Column,
634 RowReverse,
635 ColumnReverse,
636}
637
638impl FlexDirection {
639 pub fn class(&self) -> &'static str {
641 match self {
642 FlexDirection::Row => "flex-row",
643 FlexDirection::Column => "flex-col",
644 FlexDirection::RowReverse => "flex-row-reverse",
645 FlexDirection::ColumnReverse => "flex-col-reverse",
646 }
647 }
648}
649
650#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
652pub enum FlexWrap {
653 NoWrap,
654 Wrap,
655 WrapReverse,
656}
657
658impl FlexWrap {
659 pub fn class(&self) -> &'static str {
661 match self {
662 FlexWrap::NoWrap => "flex-nowrap",
663 FlexWrap::Wrap => "flex-wrap",
664 FlexWrap::WrapReverse => "flex-wrap-reverse",
665 }
666 }
667}
668
669#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
671pub enum JustifyContent {
672 Start,
673 End,
674 Center,
675 SpaceBetween,
676 SpaceAround,
677 SpaceEvenly,
678}
679
680impl JustifyContent {
681 pub fn class(&self) -> &'static str {
683 match self {
684 JustifyContent::Start => "justify-start",
685 JustifyContent::End => "justify-end",
686 JustifyContent::Center => "justify-center",
687 JustifyContent::SpaceBetween => "justify-between",
688 JustifyContent::SpaceAround => "justify-around",
689 JustifyContent::SpaceEvenly => "justify-evenly",
690 }
691 }
692}
693
694#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
696pub enum AlignItems {
697 Start,
698 End,
699 Center,
700 Baseline,
701 Stretch,
702}
703
704impl AlignItems {
705 pub fn class(&self) -> &'static str {
707 match self {
708 AlignItems::Start => "items-start",
709 AlignItems::End => "items-end",
710 AlignItems::Center => "items-center",
711 AlignItems::Baseline => "items-baseline",
712 AlignItems::Stretch => "items-stretch",
713 }
714 }
715}
716
717#[derive(Debug, Clone)]
719pub struct ResponsiveGrid {
720 cols: HashMap<Breakpoint, u32>,
721 gap: HashMap<Breakpoint, String>,
722 auto_fit: bool,
723}
724
725impl ResponsiveGrid {
726 pub fn new() -> Self {
728 Self {
729 cols: HashMap::new(),
730 gap: HashMap::new(),
731 auto_fit: false,
732 }
733 }
734
735 pub fn cols(mut self, breakpoint: Breakpoint, count: u32) -> Self {
737 self.cols.insert(breakpoint, count);
738 self
739 }
740
741 pub fn gap(mut self, breakpoint: Breakpoint, gap: &str) -> Self {
743 self.gap.insert(breakpoint, gap.to_string());
744 self
745 }
746
747 pub fn auto_fit(mut self, enabled: bool) -> Self {
749 self.auto_fit = enabled;
750 self
751 }
752
753 pub fn build(self) -> String {
755 let mut classes = vec!["grid".to_string()];
756
757 let mut cols: Vec<(Breakpoint, u32)> = self.cols.into_iter().collect();
759 cols.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
760
761 for (breakpoint, count) in cols {
762 if breakpoint == Breakpoint::Base {
763 classes.push(format!("grid-cols-{}", count));
764 } else {
765 classes.push(format!("{}grid-cols-{}", breakpoint.prefix(), count));
766 }
767 }
768
769 let mut gaps: Vec<(Breakpoint, String)> = self.gap.into_iter().collect();
771 gaps.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
772
773 for (breakpoint, gap) in gaps {
774 if breakpoint == Breakpoint::Base {
775 classes.push(gap);
776 } else {
777 classes.push(format!("{}{}", breakpoint.prefix(), gap));
778 }
779 }
780
781 if self.auto_fit {
783 classes.push("grid-cols-auto-fit".to_string());
784 }
785
786 classes.join(" ")
787 }
788}
789
790impl Default for ResponsiveGrid {
791 fn default() -> Self {
792 Self::new()
793 }
794}
795
796#[derive(Debug, Clone)]
798pub struct ResponsiveFlex {
799 direction: HashMap<Breakpoint, FlexDirection>,
800 wrap: HashMap<Breakpoint, FlexWrap>,
801 justify: HashMap<Breakpoint, JustifyContent>,
802 align: HashMap<Breakpoint, AlignItems>,
803 gap: HashMap<Breakpoint, String>,
804}
805
806impl ResponsiveFlex {
807 pub fn new() -> Self {
809 Self {
810 direction: HashMap::new(),
811 wrap: HashMap::new(),
812 justify: HashMap::new(),
813 align: HashMap::new(),
814 gap: HashMap::new(),
815 }
816 }
817
818 pub fn direction(mut self, breakpoint: Breakpoint, direction: FlexDirection) -> Self {
820 self.direction.insert(breakpoint, direction);
821 self
822 }
823
824 pub fn wrap(mut self, breakpoint: Breakpoint, wrap: FlexWrap) -> Self {
826 self.wrap.insert(breakpoint, wrap);
827 self
828 }
829
830 pub fn justify(mut self, breakpoint: Breakpoint, justify: JustifyContent) -> Self {
832 self.justify.insert(breakpoint, justify);
833 self
834 }
835
836 pub fn align(mut self, breakpoint: Breakpoint, align: AlignItems) -> Self {
838 self.align.insert(breakpoint, align);
839 self
840 }
841
842 pub fn gap(mut self, breakpoint: Breakpoint, gap: &str) -> Self {
844 self.gap.insert(breakpoint, gap.to_string());
845 self
846 }
847
848 pub fn build(self) -> String {
850 let mut classes = vec!["flex".to_string()];
851
852 let mut directions: Vec<(Breakpoint, FlexDirection)> = self.direction.into_iter().collect();
854 directions.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
855
856 for (breakpoint, direction) in directions {
857 if breakpoint == Breakpoint::Base {
858 classes.push(direction.class().to_string());
859 } else {
860 classes.push(format!("{}{}", breakpoint.prefix(), direction.class()));
861 }
862 }
863
864 let mut wraps: Vec<(Breakpoint, FlexWrap)> = self.wrap.into_iter().collect();
866 wraps.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
867
868 for (breakpoint, wrap) in wraps {
869 if breakpoint == Breakpoint::Base {
870 classes.push(wrap.class().to_string());
871 } else {
872 classes.push(format!("{}{}", breakpoint.prefix(), wrap.class()));
873 }
874 }
875
876 let mut justifies: Vec<(Breakpoint, JustifyContent)> = self.justify.into_iter().collect();
878 justifies.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
879
880 for (breakpoint, justify) in justifies {
881 if breakpoint == Breakpoint::Base {
882 classes.push(justify.class().to_string());
883 } else {
884 classes.push(format!("{}{}", breakpoint.prefix(), justify.class()));
885 }
886 }
887
888 let mut aligns: Vec<(Breakpoint, AlignItems)> = self.align.into_iter().collect();
890 aligns.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
891
892 for (breakpoint, align) in aligns {
893 if breakpoint == Breakpoint::Base {
894 classes.push(align.class().to_string());
895 } else {
896 classes.push(format!("{}{}", breakpoint.prefix(), align.class()));
897 }
898 }
899
900 let mut gaps: Vec<(Breakpoint, String)> = self.gap.into_iter().collect();
902 gaps.sort_by(|a, b| a.0.min_width().cmp(&b.0.min_width()));
903
904 for (breakpoint, gap) in gaps {
905 if breakpoint == Breakpoint::Base {
906 classes.push(gap);
907 } else {
908 classes.push(format!("{}{}", breakpoint.prefix(), gap));
909 }
910 }
911
912 classes.join(" ")
913 }
914}
915
916impl Default for ResponsiveFlex {
917 fn default() -> Self {
918 Self::new()
919 }
920}
921
922#[allow(clippy::module_inception)]
924pub mod responsive {
925 use super::*;
926
927 pub fn base<T>(value: T) -> ResponsiveValue<T> {
929 ResponsiveValue::new(value)
930 }
931
932 pub fn sm<T>(base: T, sm: T) -> ResponsiveValue<T> {
934 let mut responsive = ResponsiveValue::new(base);
935 responsive.set_breakpoint(Breakpoint::Sm, sm);
936 responsive
937 }
938
939 pub fn md<T>(base: T, sm: T, md: T) -> ResponsiveValue<T> {
941 let mut responsive = ResponsiveValue::new(base);
942 responsive.set_breakpoint(Breakpoint::Sm, sm);
943 responsive.set_breakpoint(Breakpoint::Md, md);
944 responsive
945 }
946
947 pub fn lg<T>(base: T, sm: T, md: T, lg: T) -> ResponsiveValue<T> {
949 let mut responsive = ResponsiveValue::new(base);
950 responsive.set_breakpoint(Breakpoint::Sm, sm);
951 responsive.set_breakpoint(Breakpoint::Md, md);
952 responsive.set_breakpoint(Breakpoint::Lg, lg);
953 responsive
954 }
955
956 pub fn xl<T>(base: T, sm: T, md: T, lg: T, xl: T) -> ResponsiveValue<T> {
958 let mut responsive = ResponsiveValue::new(base);
959 responsive.set_breakpoint(Breakpoint::Sm, sm);
960 responsive.set_breakpoint(Breakpoint::Md, md);
961 responsive.set_breakpoint(Breakpoint::Lg, lg);
962 responsive.set_breakpoint(Breakpoint::Xl, xl);
963 responsive
964 }
965
966 pub fn xl2<T>(base: T, sm: T, md: T, lg: T, xl: T, xl2: T) -> ResponsiveValue<T> {
968 let mut responsive = ResponsiveValue::new(base);
969 responsive.set_breakpoint(Breakpoint::Sm, sm);
970 responsive.set_breakpoint(Breakpoint::Md, md);
971 responsive.set_breakpoint(Breakpoint::Lg, lg);
972 responsive.set_breakpoint(Breakpoint::Xl, xl);
973 responsive.set_breakpoint(Breakpoint::Xl2, xl2);
974 responsive
975 }
976}
977
978#[cfg(test)]
979mod tests {
980 use super::*;
981
982 #[test]
983 fn test_breakpoint_min_width() {
984 assert_eq!(Breakpoint::Base.min_width(), 0);
985 assert_eq!(Breakpoint::Sm.min_width(), 640);
986 assert_eq!(Breakpoint::Md.min_width(), 768);
987 assert_eq!(Breakpoint::Lg.min_width(), 1024);
988 assert_eq!(Breakpoint::Xl.min_width(), 1280);
989 assert_eq!(Breakpoint::Xl2.min_width(), 1536);
990 }
991
992 #[test]
993 fn test_breakpoint_media_query() {
994 assert_eq!(Breakpoint::Base.media_query(), "");
995 assert_eq!(Breakpoint::Sm.media_query(), "(min-width: 640px)");
996 assert_eq!(Breakpoint::Md.media_query(), "(min-width: 768px)");
997 assert_eq!(Breakpoint::Lg.media_query(), "(min-width: 1024px)");
998 assert_eq!(Breakpoint::Xl.media_query(), "(min-width: 1280px)");
999 assert_eq!(Breakpoint::Xl2.media_query(), "(min-width: 1536px)");
1000 }
1001
1002 #[test]
1003 fn test_breakpoint_prefix() {
1004 assert_eq!(Breakpoint::Base.prefix(), "");
1005 assert_eq!(Breakpoint::Sm.prefix(), "sm:");
1006 assert_eq!(Breakpoint::Md.prefix(), "md:");
1007 assert_eq!(Breakpoint::Lg.prefix(), "lg:");
1008 assert_eq!(Breakpoint::Xl.prefix(), "xl:");
1009 assert_eq!(Breakpoint::Xl2.prefix(), "2xl:");
1010 }
1011
1012 #[test]
1013 fn test_breakpoint_from_str() {
1014 assert_eq!(Breakpoint::from_str("").unwrap(), Breakpoint::Base);
1015 assert_eq!(Breakpoint::from_str("base").unwrap(), Breakpoint::Base);
1016 assert_eq!(Breakpoint::from_str("sm").unwrap(), Breakpoint::Sm);
1017 assert_eq!(Breakpoint::from_str("md").unwrap(), Breakpoint::Md);
1018 assert_eq!(Breakpoint::from_str("lg").unwrap(), Breakpoint::Lg);
1019 assert_eq!(Breakpoint::from_str("xl").unwrap(), Breakpoint::Xl);
1020 assert_eq!(Breakpoint::from_str("2xl").unwrap(), Breakpoint::Xl2);
1021
1022 assert!(Breakpoint::from_str("invalid").is_err());
1023 }
1024
1025 #[test]
1026 fn test_responsive_value_creation() {
1027 let responsive = ResponsiveValue::new(10);
1028 assert_eq!(responsive.base, 10);
1029 assert_eq!(responsive.sm, None);
1030 assert_eq!(responsive.md, None);
1031 }
1032
1033 #[test]
1034 fn test_responsive_value_set_get() {
1035 let mut responsive = ResponsiveValue::new(10);
1036 responsive.set_breakpoint(Breakpoint::Sm, 20);
1037 responsive.set_breakpoint(Breakpoint::Md, 30);
1038
1039 assert_eq!(*responsive.get_breakpoint(Breakpoint::Base), 10);
1040 assert_eq!(*responsive.get_breakpoint(Breakpoint::Sm), 20);
1041 assert_eq!(*responsive.get_breakpoint(Breakpoint::Md), 30);
1042 assert_eq!(*responsive.get_breakpoint(Breakpoint::Lg), 10); }
1044
1045 #[test]
1046 fn test_responsive_value_all_values() {
1047 let mut responsive = ResponsiveValue::new(10);
1048 responsive.set_breakpoint(Breakpoint::Sm, 20);
1049 responsive.set_breakpoint(Breakpoint::Md, 30);
1050
1051 let all_values = responsive.all_values();
1052 assert_eq!(all_values.len(), 3);
1053 assert_eq!(all_values[0], (Breakpoint::Base, &10));
1054 assert_eq!(all_values[1], (Breakpoint::Sm, &20));
1055 assert_eq!(all_values[2], (Breakpoint::Md, &30));
1056 }
1057
1058 #[test]
1059 fn test_responsive_config() {
1060 let config = ResponsiveConfig::new();
1061 assert_eq!(config.get_breakpoint("sm").unwrap(), 640);
1062 assert_eq!(config.get_breakpoint("md").unwrap(), 768);
1063 assert_eq!(config.get_breakpoint("lg").unwrap(), 1024);
1064 assert_eq!(config.get_breakpoint("xl").unwrap(), 1280);
1065 assert_eq!(config.get_breakpoint("2xl").unwrap(), 1536);
1066
1067 assert!(config.get_breakpoint("invalid").is_err());
1068 }
1069
1070 #[test]
1071 fn test_responsive_utility_functions() {
1072 let responsive = responsive::base(10);
1073 assert_eq!(responsive.base, 10);
1074
1075 let responsive = responsive::sm(10, 20);
1076 assert_eq!(responsive.base, 10);
1077 assert_eq!(responsive.sm, Some(20));
1078
1079 let responsive = responsive::md(10, 20, 30);
1080 assert_eq!(responsive.base, 10);
1081 assert_eq!(responsive.sm, Some(20));
1082 assert_eq!(responsive.md, Some(30));
1083
1084 let responsive = responsive::lg(10, 20, 30, 40);
1085 assert_eq!(responsive.base, 10);
1086 assert_eq!(responsive.sm, Some(20));
1087 assert_eq!(responsive.md, Some(30));
1088 assert_eq!(responsive.lg, Some(40));
1089
1090 let responsive = responsive::xl(10, 20, 30, 40, 50);
1091 assert_eq!(responsive.base, 10);
1092 assert_eq!(responsive.sm, Some(20));
1093 assert_eq!(responsive.md, Some(30));
1094 assert_eq!(responsive.lg, Some(40));
1095 assert_eq!(responsive.xl, Some(50));
1096
1097 let responsive = responsive::xl2(10, 20, 30, 40, 50, 60);
1098 assert_eq!(responsive.base, 10);
1099 assert_eq!(responsive.sm, Some(20));
1100 assert_eq!(responsive.md, Some(30));
1101 assert_eq!(responsive.lg, Some(40));
1102 assert_eq!(responsive.xl, Some(50));
1103 assert_eq!(responsive.xl2, Some(60));
1104 }
1105
1106 #[test]
1107 fn test_responsive_creation() {
1108 let responsive = Responsive::new();
1109 assert!(responsive.sm.is_none());
1110 assert!(responsive.md.is_none());
1111 }
1112
1113 #[test]
1114 fn test_responsive_build() {
1115 let responsive = Responsive::new()
1116 .sm("text-base")
1117 .md("text-lg")
1118 .lg("text-xl");
1119
1120 let classes = responsive.build();
1121 assert!(classes.contains("sm:text-base"));
1122 assert!(classes.contains("md:text-lg"));
1123 assert!(classes.contains("lg:text-xl"));
1124 }
1125
1126 #[test]
1127 fn test_responsive_builder() {
1128 let classes = ResponsiveBuilder::new()
1129 .base("text-sm")
1130 .breakpoint(Breakpoint::Sm, "text-base")
1131 .breakpoint(Breakpoint::Md, "text-lg")
1132 .build();
1133
1134 assert!(classes.contains("text-sm"));
1135 assert!(classes.contains("sm:text-base"));
1136 assert!(classes.contains("md:text-lg"));
1137 }
1138
1139 #[test]
1140 fn test_flex_direction() {
1141 assert_eq!(FlexDirection::Row.class(), "flex-row");
1142 assert_eq!(FlexDirection::Column.class(), "flex-col");
1143 assert_eq!(FlexDirection::RowReverse.class(), "flex-row-reverse");
1144 assert_eq!(FlexDirection::ColumnReverse.class(), "flex-col-reverse");
1145 }
1146
1147 #[test]
1148 fn test_flex_wrap() {
1149 assert_eq!(FlexWrap::NoWrap.class(), "flex-nowrap");
1150 assert_eq!(FlexWrap::Wrap.class(), "flex-wrap");
1151 assert_eq!(FlexWrap::WrapReverse.class(), "flex-wrap-reverse");
1152 }
1153
1154 #[test]
1155 fn test_justify_content() {
1156 assert_eq!(JustifyContent::Start.class(), "justify-start");
1157 assert_eq!(JustifyContent::End.class(), "justify-end");
1158 assert_eq!(JustifyContent::Center.class(), "justify-center");
1159 assert_eq!(JustifyContent::SpaceBetween.class(), "justify-between");
1160 assert_eq!(JustifyContent::SpaceAround.class(), "justify-around");
1161 assert_eq!(JustifyContent::SpaceEvenly.class(), "justify-evenly");
1162 }
1163
1164 #[test]
1165 fn test_align_items() {
1166 assert_eq!(AlignItems::Start.class(), "items-start");
1167 assert_eq!(AlignItems::End.class(), "items-end");
1168 assert_eq!(AlignItems::Center.class(), "items-center");
1169 assert_eq!(AlignItems::Baseline.class(), "items-baseline");
1170 assert_eq!(AlignItems::Stretch.class(), "items-stretch");
1171 }
1172
1173 #[test]
1174 fn test_responsive_grid() {
1175 let classes = ResponsiveGrid::new()
1176 .cols(Breakpoint::Sm, 2)
1177 .cols(Breakpoint::Md, 3)
1178 .gap(Breakpoint::Sm, "gap-4")
1179 .gap(Breakpoint::Md, "gap-6")
1180 .build();
1181
1182 assert!(classes.contains("grid"));
1183 assert!(classes.contains("sm:grid-cols-2"));
1184 assert!(classes.contains("md:grid-cols-3"));
1185 assert!(classes.contains("sm:gap-4"));
1186 assert!(classes.contains("md:gap-6"));
1187 }
1188
1189 #[test]
1190 fn test_responsive_flex() {
1191 let classes = ResponsiveFlex::new()
1192 .direction(Breakpoint::Sm, FlexDirection::Column)
1193 .direction(Breakpoint::Md, FlexDirection::Row)
1194 .justify(Breakpoint::Sm, JustifyContent::Center)
1195 .justify(Breakpoint::Md, JustifyContent::SpaceBetween)
1196 .build();
1197
1198 assert!(classes.contains("flex"));
1199 assert!(classes.contains("sm:flex-col"));
1200 assert!(classes.contains("md:flex-row"));
1201 assert!(classes.contains("sm:justify-center"));
1202 assert!(classes.contains("md:justify-between"));
1203 }
1204}