1use dashmap::DashMap;
2use lazy_static::lazy_static;
3
4lazy_static! {
5 static ref STATIC_RULES: DashMap<&'static str, &'static str> = {
6 let m = DashMap::new();
7 m.insert("flex", "display: flex;");
9 m.insert("items-center", "align-items: center;");
10 m.insert("justify-center", "justify-content: center;");
11 m.insert("block", "display: block;");
12 m.insert("inline-block", "display: inline-block;");
13 m.insert("hidden", "display: none;");
14
15 m.insert("bg-white", "background-color: #ffffff;");
17 m.insert("bg-black", "background-color: #000000;");
18 m.insert("bg-blue", "background-color: #0000ff;"); m.insert("text-white", "color: #ffffff;");
20 m.insert("text-black", "color: #000000;");
21
22 m.insert("font-bold", "font-weight: 700;");
24 m.insert("text-center", "text-align: center;");
25
26 m.insert("shadow-sm", "box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);");
28
29 m
30 };
31
32 static ref GENERATED_CACHE: DashMap<String, String> = DashMap::new();
33}
34
35pub struct TailwindEngine;
36
37impl TailwindEngine {
38 pub fn new() -> Self {
39 Self
40 }
41
42 pub fn generate_css(&self, classes: &str) -> String {
43 let mut css = String::with_capacity(classes.len() * 4);
44 let mut seen = std::collections::HashSet::new();
45
46 for class in classes.split_whitespace() {
47 if !seen.insert(class) {
48 continue;
49 }
50
51 if let Some(rules) = GENERATED_CACHE.get(class) {
53 css.push_str(&rules);
54 continue;
55 }
56
57 if let Some(rules) = STATIC_RULES.get(class) {
59 let rule_str = format!(".{} {{ {} }}\n", escape_class(class), *rules);
60 css.push_str(&rule_str);
61 GENERATED_CACHE.insert(class.to_string(), rule_str);
62 continue;
63 }
64
65 if let Some(rules) = self.match_dynamic(class) {
67 let rule_str = format!(".{} {{ {} }}\n", escape_class(class), rules);
68 css.push_str(&rule_str);
69 GENERATED_CACHE.insert(class.to_string(), rule_str);
70 continue;
71 }
72 }
73 css
74 }
75
76 fn match_dynamic(&self, class: &str) -> Option<String> {
77 if (class.starts_with('p') || class.starts_with('m')) && class.contains('-') {
79 let parts: Vec<&str> = class.split('-').collect();
80 if parts.len() == 2 {
81 let prefix_dir = parts[0];
82 let val_str = parts[1];
83
84 if prefix_dir.len() >= 1 && prefix_dir.len() <= 2 {
85 let prefix = &prefix_dir[0..1];
86 let dir = &prefix_dir[1..];
87
88 if let Ok(val_num) = val_str.parse::<f32>() {
89 let val = val_num * 0.25;
90 let prop = if prefix == "p" { "padding" } else { "margin" };
91 return Some(match dir {
92 "x" => format!("{}-left: {}rem; {}-right: {}rem;", prop, val, prop, val),
93 "y" => format!("{}-top: {}rem; {}-bottom: {}rem;", prop, val, prop, val),
94 "t" => format!("{}-top: {}rem;", prop, val),
95 "r" => format!("{}-right: {}rem;", prop, val),
96 "b" => format!("{}-bottom: {}rem;", prop, val),
97 "l" => format!("{}-left: {}rem;", prop, val),
98 "" => format!("{}: {}rem;", prop, val),
99 _ => return None,
100 });
101 }
102 }
103 }
104 }
105
106 if class.starts_with("text-") {
108 let size_part = &class[5..];
109 let size = match size_part {
110 "xs" => "0.75rem",
111 "sm" => "0.875rem",
112 "base" => "1rem",
113 "lg" => "1.125rem",
114 "xl" => "1.25rem",
115 "2xl" => "1.5rem",
116 "3xl" => "1.875rem",
117 "4xl" => "2.25rem",
118 _ => return None,
119 };
120 return Some(format!("font-size: {};", size));
121 }
122
123 if (class.starts_with("bg-") || class.starts_with("text-") || class.starts_with("border-")) && class.matches('-').count() == 2 {
125 let parts: Vec<&str> = class.split('-').collect();
126 if parts.len() == 3 {
127 let prop_prefix = parts[0];
128 let color = parts[1];
129 let shade = parts[2];
130
131 let prop = match prop_prefix {
132 "bg" => "background-color",
133 "text" => "color",
134 "border" => "border-color",
135 _ => return None,
136 };
137
138 let hex = match (color, shade) {
139 ("blue", "500") => "#3b82f6",
140 ("red", "500") => "#ef4444",
141 ("green", "500") => "#22c55e",
142 ("gray", "500") => "#6b7280",
143 ("yellow", "500") => "#eab308",
144 _ => return None,
145 };
146 return Some(format!("{}: {};", prop, hex));
147 }
148 }
149
150 if class.starts_with("rounded") {
152 let suffix = if class == "rounded" { "" } else { &class[7..] };
153 let radius = match suffix {
154 "-sm" => "0.125rem",
155 "-md" => "0.375rem",
156 "-lg" => "0.5rem",
157 "-full" => "9999px",
158 "" => "0.25rem",
159 _ => return None,
160 };
161 return Some(format!("border-radius: {};", radius));
162 }
163
164 None
165 }
166}
167
168fn escape_class(class: &str) -> String {
169 class.replace(':', "\\:").replace('[', "\\[").replace(']', "\\]").replace('.', "\\.")
171}