1use crate::classes::ClassBuilder;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub enum NestingSelector {
13 DirectChild,
15 Descendant,
17 AdjacentSibling,
19 GeneralSibling,
21 Custom(String),
23}
24
25impl fmt::Display for NestingSelector {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 match self {
28 NestingSelector::DirectChild => write!(f, ">"),
29 NestingSelector::Descendant => write!(f, " "),
30 NestingSelector::AdjacentSibling => write!(f, "+"),
31 NestingSelector::GeneralSibling => write!(f, "~"),
32 NestingSelector::Custom(selector) => write!(f, "{}", selector),
33 }
34 }
35}
36
37impl NestingSelector {
38 pub fn to_class_name(&self) -> String {
40 match self {
41 NestingSelector::DirectChild => "nest-child".to_string(),
42 NestingSelector::Descendant => "nest-descendant".to_string(),
43 NestingSelector::AdjacentSibling => "nest-adjacent".to_string(),
44 NestingSelector::GeneralSibling => "nest-sibling".to_string(),
45 NestingSelector::Custom(selector) => format!("nest-{}", selector.replace(" ", "-").replace(">", "child").replace("+", "adjacent").replace("~", "sibling")),
46 }
47 }
48
49 pub fn to_css_value(&self) -> String {
51 self.to_string()
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
57pub enum NestingPseudoClass {
58 Hover,
60 Focus,
62 Active,
64 Visited,
66 Link,
68 FirstChild,
70 LastChild,
72 NthChild(String),
74 Custom(String),
76}
77
78impl fmt::Display for NestingPseudoClass {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 match self {
81 NestingPseudoClass::Hover => write!(f, ":hover"),
82 NestingPseudoClass::Focus => write!(f, ":focus"),
83 NestingPseudoClass::Active => write!(f, ":active"),
84 NestingPseudoClass::Visited => write!(f, ":visited"),
85 NestingPseudoClass::Link => write!(f, ":link"),
86 NestingPseudoClass::FirstChild => write!(f, ":first-child"),
87 NestingPseudoClass::LastChild => write!(f, ":last-child"),
88 NestingPseudoClass::NthChild(n) => write!(f, ":nth-child({})", n),
89 NestingPseudoClass::Custom(pseudo) => write!(f, ":{}", pseudo),
90 }
91 }
92}
93
94impl NestingPseudoClass {
95 pub fn to_class_name(&self) -> String {
97 match self {
98 NestingPseudoClass::Hover => "nest-hover".to_string(),
99 NestingPseudoClass::Focus => "nest-focus".to_string(),
100 NestingPseudoClass::Active => "nest-active".to_string(),
101 NestingPseudoClass::Visited => "nest-visited".to_string(),
102 NestingPseudoClass::Link => "nest-link".to_string(),
103 NestingPseudoClass::FirstChild => "nest-first-child".to_string(),
104 NestingPseudoClass::LastChild => "nest-last-child".to_string(),
105 NestingPseudoClass::NthChild(n) => format!("nest-nth-child-{}", n.replace("n", "n").replace(" ", "-")),
106 NestingPseudoClass::Custom(pseudo) => format!("nest-{}", pseudo),
107 }
108 }
109
110 pub fn to_css_value(&self) -> String {
112 self.to_string()
113 }
114}
115
116#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
118pub enum NestingMediaQuery {
119 Small,
121 Medium,
123 Large,
125 ExtraLarge,
127 Dark,
129 Light,
131 Print,
133 Screen,
135 Custom(String),
137}
138
139impl fmt::Display for NestingMediaQuery {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 match self {
142 NestingMediaQuery::Small => write!(f, "(min-width: 640px)"),
143 NestingMediaQuery::Medium => write!(f, "(min-width: 768px)"),
144 NestingMediaQuery::Large => write!(f, "(min-width: 1024px)"),
145 NestingMediaQuery::ExtraLarge => write!(f, "(min-width: 1280px)"),
146 NestingMediaQuery::Dark => write!(f, "(prefers-color-scheme: dark)"),
147 NestingMediaQuery::Light => write!(f, "(prefers-color-scheme: light)"),
148 NestingMediaQuery::Print => write!(f, "print"),
149 NestingMediaQuery::Screen => write!(f, "screen"),
150 NestingMediaQuery::Custom(query) => write!(f, "{}", query),
151 }
152 }
153}
154
155impl NestingMediaQuery {
156 pub fn to_class_name(&self) -> String {
158 match self {
159 NestingMediaQuery::Small => "nest-sm".to_string(),
160 NestingMediaQuery::Medium => "nest-md".to_string(),
161 NestingMediaQuery::Large => "nest-lg".to_string(),
162 NestingMediaQuery::ExtraLarge => "nest-xl".to_string(),
163 NestingMediaQuery::Dark => "nest-dark".to_string(),
164 NestingMediaQuery::Light => "nest-light".to_string(),
165 NestingMediaQuery::Print => "nest-print".to_string(),
166 NestingMediaQuery::Screen => "nest-screen".to_string(),
167 NestingMediaQuery::Custom(query) => format!("nest-{}", query.replace("(", "").replace(")", "").replace(" ", "-").replace(":", "-").replace("--", "-")),
168 }
169 }
170
171 pub fn to_css_value(&self) -> String {
173 self.to_string()
174 }
175}
176
177pub trait CssNestingUtilities {
179 fn nesting_selector(self, selector: NestingSelector) -> Self;
181 fn nesting_pseudo_class(self, pseudo_class: NestingPseudoClass) -> Self;
183 fn nesting_media_query(self, media_query: NestingMediaQuery) -> Self;
185 fn nested_class(self, selector: NestingSelector, class: &str) -> Self;
187 fn nested_pseudo_class(self, pseudo_class: NestingPseudoClass, class: &str) -> Self;
189 fn nested_media_query(self, media_query: NestingMediaQuery, class: &str) -> Self;
191}
192
193impl CssNestingUtilities for ClassBuilder {
194 fn nesting_selector(self, selector: NestingSelector) -> Self {
195 self.class(&selector.to_class_name())
196 }
197
198 fn nesting_pseudo_class(self, pseudo_class: NestingPseudoClass) -> Self {
199 self.class(&pseudo_class.to_class_name())
200 }
201
202 fn nesting_media_query(self, media_query: NestingMediaQuery) -> Self {
203 self.class(&media_query.to_class_name())
204 }
205
206 fn nested_class(self, selector: NestingSelector, class: &str) -> Self {
207 let nested_class = format!("{}-{}", selector.to_class_name(), class);
208 self.class(&nested_class)
209 }
210
211 fn nested_pseudo_class(self, pseudo_class: NestingPseudoClass, class: &str) -> Self {
212 let nested_class = format!("{}-{}", pseudo_class.to_class_name(), class);
213 self.class(&nested_class)
214 }
215
216 fn nested_media_query(self, media_query: NestingMediaQuery, class: &str) -> Self {
217 let nested_class = format!("{}-{}", media_query.to_class_name(), class);
218 self.class(&nested_class)
219 }
220}
221
222pub trait CssNestingConvenience {
224 fn nested_hover(self, class: &str) -> Self;
226 fn nested_focus(self, class: &str) -> Self;
228 fn nested_active(self, class: &str) -> Self;
230 fn nested_first_child(self, class: &str) -> Self;
232 fn nested_last_child(self, class: &str) -> Self;
234 fn nested_sm(self, class: &str) -> Self;
236 fn nested_md(self, class: &str) -> Self;
238 fn nested_lg(self, class: &str) -> Self;
240 fn nested_dark(self, class: &str) -> Self;
242 fn nested_light(self, class: &str) -> Self;
244}
245
246impl CssNestingConvenience for ClassBuilder {
247 fn nested_hover(self, class: &str) -> Self {
248 self.nested_pseudo_class(NestingPseudoClass::Hover, class)
249 }
250
251 fn nested_focus(self, class: &str) -> Self {
252 self.nested_pseudo_class(NestingPseudoClass::Focus, class)
253 }
254
255 fn nested_active(self, class: &str) -> Self {
256 self.nested_pseudo_class(NestingPseudoClass::Active, class)
257 }
258
259 fn nested_first_child(self, class: &str) -> Self {
260 self.nested_pseudo_class(NestingPseudoClass::FirstChild, class)
261 }
262
263 fn nested_last_child(self, class: &str) -> Self {
264 self.nested_pseudo_class(NestingPseudoClass::LastChild, class)
265 }
266
267 fn nested_sm(self, class: &str) -> Self {
268 self.nested_media_query(NestingMediaQuery::Small, class)
269 }
270
271 fn nested_md(self, class: &str) -> Self {
272 self.nested_media_query(NestingMediaQuery::Medium, class)
273 }
274
275 fn nested_lg(self, class: &str) -> Self {
276 self.nested_media_query(NestingMediaQuery::Large, class)
277 }
278
279 fn nested_dark(self, class: &str) -> Self {
280 self.nested_media_query(NestingMediaQuery::Dark, class)
281 }
282
283 fn nested_light(self, class: &str) -> Self {
284 self.nested_media_query(NestingMediaQuery::Light, class)
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use crate::classes::ClassBuilder;
292
293 #[test]
294 fn test_nesting_selector_enum_values() {
295 assert_eq!(NestingSelector::DirectChild.to_string(), ">");
296 assert_eq!(NestingSelector::Descendant.to_string(), " ");
297 assert_eq!(NestingSelector::AdjacentSibling.to_string(), "+");
298 assert_eq!(NestingSelector::GeneralSibling.to_string(), "~");
299 assert_eq!(NestingSelector::Custom("div".to_string()).to_string(), "div");
300 }
301
302 #[test]
303 fn test_nesting_selector_class_names() {
304 assert_eq!(NestingSelector::DirectChild.to_class_name(), "nest-child");
305 assert_eq!(NestingSelector::Descendant.to_class_name(), "nest-descendant");
306 assert_eq!(NestingSelector::AdjacentSibling.to_class_name(), "nest-adjacent");
307 assert_eq!(NestingSelector::GeneralSibling.to_class_name(), "nest-sibling");
308 assert_eq!(NestingSelector::Custom("div".to_string()).to_class_name(), "nest-div");
309 }
310
311 #[test]
312 fn test_nesting_pseudo_class_enum_values() {
313 assert_eq!(NestingPseudoClass::Hover.to_string(), ":hover");
314 assert_eq!(NestingPseudoClass::Focus.to_string(), ":focus");
315 assert_eq!(NestingPseudoClass::Active.to_string(), ":active");
316 assert_eq!(NestingPseudoClass::FirstChild.to_string(), ":first-child");
317 assert_eq!(NestingPseudoClass::NthChild("2n".to_string()).to_string(), ":nth-child(2n)");
318 assert_eq!(NestingPseudoClass::Custom("custom".to_string()).to_string(), ":custom");
319 }
320
321 #[test]
322 fn test_nesting_pseudo_class_class_names() {
323 assert_eq!(NestingPseudoClass::Hover.to_class_name(), "nest-hover");
324 assert_eq!(NestingPseudoClass::Focus.to_class_name(), "nest-focus");
325 assert_eq!(NestingPseudoClass::Active.to_class_name(), "nest-active");
326 assert_eq!(NestingPseudoClass::FirstChild.to_class_name(), "nest-first-child");
327 assert_eq!(NestingPseudoClass::NthChild("2n".to_string()).to_class_name(), "nest-nth-child-2n");
328 assert_eq!(NestingPseudoClass::Custom("custom".to_string()).to_class_name(), "nest-custom");
329 }
330
331 #[test]
332 fn test_nesting_media_query_enum_values() {
333 assert_eq!(NestingMediaQuery::Small.to_string(), "(min-width: 640px)");
334 assert_eq!(NestingMediaQuery::Medium.to_string(), "(min-width: 768px)");
335 assert_eq!(NestingMediaQuery::Large.to_string(), "(min-width: 1024px)");
336 assert_eq!(NestingMediaQuery::Dark.to_string(), "(prefers-color-scheme: dark)");
337 assert_eq!(NestingMediaQuery::Print.to_string(), "print");
338 assert_eq!(NestingMediaQuery::Custom("(max-width: 600px)".to_string()).to_string(), "(max-width: 600px)");
339 }
340
341 #[test]
342 fn test_nesting_media_query_class_names() {
343 assert_eq!(NestingMediaQuery::Small.to_class_name(), "nest-sm");
344 assert_eq!(NestingMediaQuery::Medium.to_class_name(), "nest-md");
345 assert_eq!(NestingMediaQuery::Large.to_class_name(), "nest-lg");
346 assert_eq!(NestingMediaQuery::Dark.to_class_name(), "nest-dark");
347 assert_eq!(NestingMediaQuery::Print.to_class_name(), "nest-print");
348 assert_eq!(NestingMediaQuery::Custom("(max-width: 600px)".to_string()).to_class_name(), "nest-max-width-600px");
349 }
350
351 #[test]
352 fn test_css_nesting_utilities() {
353 let classes = ClassBuilder::new()
354 .nesting_selector(NestingSelector::DirectChild)
355 .nesting_pseudo_class(NestingPseudoClass::Hover)
356 .nesting_media_query(NestingMediaQuery::Small)
357 .nested_class(NestingSelector::Descendant, "text-blue-500")
358 .nested_pseudo_class(NestingPseudoClass::Focus, "text-red-500")
359 .nested_media_query(NestingMediaQuery::Medium, "text-green-500");
360
361 let result = classes.build();
362 assert!(result.classes.contains("nest-child"));
363 assert!(result.classes.contains("nest-hover"));
364 assert!(result.classes.contains("nest-sm"));
365 assert!(result.classes.contains("nest-descendant-text-blue-500"));
366 assert!(result.classes.contains("nest-focus-text-red-500"));
367 assert!(result.classes.contains("nest-md-text-green-500"));
368 }
369
370 #[test]
371 fn test_css_nesting_convenience() {
372 let classes = ClassBuilder::new()
373 .nested_hover("text-blue-500")
374 .nested_focus("text-red-500")
375 .nested_active("text-green-500")
376 .nested_first_child("text-yellow-500")
377 .nested_last_child("text-purple-500")
378 .nested_sm("text-pink-500")
379 .nested_md("text-indigo-500")
380 .nested_lg("text-cyan-500")
381 .nested_dark("text-gray-500")
382 .nested_light("text-white");
383
384 let result = classes.build();
385 assert!(result.classes.contains("nest-hover-text-blue-500"));
386 assert!(result.classes.contains("nest-focus-text-red-500"));
387 assert!(result.classes.contains("nest-active-text-green-500"));
388 assert!(result.classes.contains("nest-first-child-text-yellow-500"));
389 assert!(result.classes.contains("nest-last-child-text-purple-500"));
390 assert!(result.classes.contains("nest-sm-text-pink-500"));
391 assert!(result.classes.contains("nest-md-text-indigo-500"));
392 assert!(result.classes.contains("nest-lg-text-cyan-500"));
393 assert!(result.classes.contains("nest-dark-text-gray-500"));
394 assert!(result.classes.contains("nest-light-text-white"));
395 }
396
397 #[test]
398 fn test_css_nesting_serialization() {
399 let selector = NestingSelector::DirectChild;
400 let serialized = serde_json::to_string(&selector).unwrap();
401 let deserialized: NestingSelector = serde_json::from_str(&serialized).unwrap();
402 assert_eq!(selector, deserialized);
403
404 let pseudo_class = NestingPseudoClass::Hover;
405 let serialized = serde_json::to_string(&pseudo_class).unwrap();
406 let deserialized: NestingPseudoClass = serde_json::from_str(&serialized).unwrap();
407 assert_eq!(pseudo_class, deserialized);
408
409 let media_query = NestingMediaQuery::Small;
410 let serialized = serde_json::to_string(&media_query).unwrap();
411 let deserialized: NestingMediaQuery = serde_json::from_str(&serialized).unwrap();
412 assert_eq!(media_query, deserialized);
413 }
414
415 #[test]
416 fn test_css_nesting_comprehensive_usage() {
417 let classes = ClassBuilder::new()
418 .nesting_selector(NestingSelector::DirectChild)
419 .nesting_pseudo_class(NestingPseudoClass::Hover)
420 .nesting_media_query(NestingMediaQuery::Small)
421 .nested_class(NestingSelector::Descendant, "text-blue-500")
422 .nested_pseudo_class(NestingPseudoClass::Focus, "text-red-500")
423 .nested_media_query(NestingMediaQuery::Medium, "text-green-500")
424 .nested_hover("text-yellow-500")
425 .nested_focus("text-purple-500")
426 .nested_active("text-pink-500")
427 .nested_first_child("text-indigo-500")
428 .nested_last_child("text-cyan-500")
429 .nested_sm("text-gray-500")
430 .nested_md("text-white")
431 .nested_lg("text-black")
432 .nested_dark("text-gray-100")
433 .nested_light("text-gray-900");
434
435 let result = classes.build();
436 assert!(result.classes.contains("nest-child"));
437 assert!(result.classes.contains("nest-hover"));
438 assert!(result.classes.contains("nest-sm"));
439 assert!(result.classes.contains("nest-descendant-text-blue-500"));
440 assert!(result.classes.contains("nest-focus-text-red-500"));
441 assert!(result.classes.contains("nest-md-text-green-500"));
442 assert!(result.classes.contains("nest-hover-text-yellow-500"));
443 assert!(result.classes.contains("nest-focus-text-purple-500"));
444 assert!(result.classes.contains("nest-active-text-pink-500"));
445 assert!(result.classes.contains("nest-first-child-text-indigo-500"));
446 assert!(result.classes.contains("nest-last-child-text-cyan-500"));
447 assert!(result.classes.contains("nest-sm-text-gray-500"));
448 assert!(result.classes.contains("nest-md-text-white"));
449 assert!(result.classes.contains("nest-lg-text-black"));
450 assert!(result.classes.contains("nest-dark-text-gray-100"));
451 assert!(result.classes.contains("nest-light-text-gray-900"));
452 }
453}