1use crate::classes::ClassBuilder;
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct DarkModeVariant {
14 pub class: String,
16 pub is_hover: bool,
18 pub is_focus: bool,
20 pub is_active: bool,
22 pub is_disabled: bool,
24 pub is_checked: bool,
26 pub is_group_hover: bool,
28 pub is_group_focus: bool,
30}
31
32impl DarkModeVariant {
33 pub fn new(class: impl Into<String>) -> Self {
35 Self {
36 class: class.into(),
37 is_hover: false,
38 is_focus: false,
39 is_active: false,
40 is_disabled: false,
41 is_checked: false,
42 is_group_hover: false,
43 is_group_focus: false,
44 }
45 }
46
47 pub fn hover(class: impl Into<String>) -> Self {
49 Self {
50 class: class.into(),
51 is_hover: true,
52 is_focus: false,
53 is_active: false,
54 is_disabled: false,
55 is_checked: false,
56 is_group_hover: false,
57 is_group_focus: false,
58 }
59 }
60
61 pub fn focus(class: impl Into<String>) -> Self {
63 Self {
64 class: class.into(),
65 is_hover: false,
66 is_focus: true,
67 is_active: false,
68 is_disabled: false,
69 is_checked: false,
70 is_group_hover: false,
71 is_group_focus: false,
72 }
73 }
74
75 pub fn active(class: impl Into<String>) -> Self {
77 Self {
78 class: class.into(),
79 is_hover: false,
80 is_focus: false,
81 is_active: true,
82 is_disabled: false,
83 is_checked: false,
84 is_group_hover: false,
85 is_group_focus: false,
86 }
87 }
88
89 pub fn disabled(class: impl Into<String>) -> Self {
91 Self {
92 class: class.into(),
93 is_hover: false,
94 is_focus: false,
95 is_active: false,
96 is_disabled: true,
97 is_checked: false,
98 is_group_hover: false,
99 is_group_focus: false,
100 }
101 }
102
103 pub fn checked(class: impl Into<String>) -> Self {
105 Self {
106 class: class.into(),
107 is_hover: false,
108 is_focus: false,
109 is_active: false,
110 is_disabled: false,
111 is_checked: true,
112 is_group_hover: false,
113 is_group_focus: false,
114 }
115 }
116
117 pub fn group_hover(class: impl Into<String>) -> Self {
119 Self {
120 class: class.into(),
121 is_hover: false,
122 is_focus: false,
123 is_active: false,
124 is_disabled: false,
125 is_checked: false,
126 is_group_hover: true,
127 is_group_focus: false,
128 }
129 }
130
131 pub fn group_focus(class: impl Into<String>) -> Self {
133 Self {
134 class: class.into(),
135 is_hover: false,
136 is_focus: false,
137 is_active: false,
138 is_disabled: false,
139 is_checked: false,
140 is_group_hover: false,
141 is_group_focus: true,
142 }
143 }
144
145 pub fn to_class_name(&self) -> String {
147 let mut prefix = "dark:".to_string();
148
149 if self.is_group_hover {
150 prefix.push_str("group-hover:");
151 } else if self.is_group_focus {
152 prefix.push_str("group-focus:");
153 } else if self.is_hover {
154 prefix.push_str("hover:");
155 } else if self.is_focus {
156 prefix.push_str("focus:");
157 } else if self.is_active {
158 prefix.push_str("active:");
159 } else if self.is_disabled {
160 prefix.push_str("disabled:");
161 } else if self.is_checked {
162 prefix.push_str("checked:");
163 }
164
165 format!("{}{}", prefix, self.class)
166 }
167
168 pub fn validate(&self) -> Result<(), DarkModeVariantError> {
170 if self.class.is_empty() {
171 return Err(DarkModeVariantError::EmptyClass);
172 }
173
174 let state_count = [
176 self.is_hover,
177 self.is_focus,
178 self.is_active,
179 self.is_disabled,
180 self.is_checked,
181 self.is_group_hover,
182 self.is_group_focus,
183 ]
184 .iter()
185 .filter(|&&state| state)
186 .count();
187
188 if state_count > 1 {
189 return Err(DarkModeVariantError::ConflictingStates);
190 }
191
192 Ok(())
193 }
194}
195
196#[derive(Debug, thiserror::Error)]
198pub enum DarkModeVariantError {
199 #[error("Empty class name")]
200 EmptyClass,
201
202 #[error("Conflicting states: only one state modifier can be used at a time")]
203 ConflictingStates,
204}
205
206impl fmt::Display for DarkModeVariant {
207 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 write!(f, "{}", self.to_class_name())
209 }
210}
211
212pub trait DarkModeVariantUtilities {
214 fn dark_mode(self, class: impl Into<String>) -> Self;
216
217 fn dark_hover(self, class: impl Into<String>) -> Self;
219
220 fn dark_focus(self, class: impl Into<String>) -> Self;
222
223 fn dark_active(self, class: impl Into<String>) -> Self;
225
226 fn dark_disabled(self, class: impl Into<String>) -> Self;
228
229 fn dark_checked(self, class: impl Into<String>) -> Self;
231
232 fn dark_group_hover(self, class: impl Into<String>) -> Self;
234
235 fn dark_group_focus(self, class: impl Into<String>) -> Self;
237
238 fn dark_bg(self, color: impl Into<String>) -> Self;
240
241 fn dark_text(self, color: impl Into<String>) -> Self;
243
244 fn dark_border(self, color: impl Into<String>) -> Self;
246
247 fn dark_hover_bg(self, color: impl Into<String>) -> Self;
249
250 fn dark_hover_text(self, color: impl Into<String>) -> Self;
252
253 fn dark_focus_bg(self, color: impl Into<String>) -> Self;
255
256 fn dark_focus_text(self, color: impl Into<String>) -> Self;
258}
259
260impl DarkModeVariantUtilities for ClassBuilder {
261 fn dark_mode(self, class: impl Into<String>) -> Self {
262 let variant = DarkModeVariant::new(class);
263 self.class(variant.to_class_name())
264 }
265
266 fn dark_hover(self, class: impl Into<String>) -> Self {
267 let variant = DarkModeVariant::hover(class);
268 self.class(variant.to_class_name())
269 }
270
271 fn dark_focus(self, class: impl Into<String>) -> Self {
272 let variant = DarkModeVariant::focus(class);
273 self.class(variant.to_class_name())
274 }
275
276 fn dark_active(self, class: impl Into<String>) -> Self {
277 let variant = DarkModeVariant::active(class);
278 self.class(variant.to_class_name())
279 }
280
281 fn dark_disabled(self, class: impl Into<String>) -> Self {
282 let variant = DarkModeVariant::disabled(class);
283 self.class(variant.to_class_name())
284 }
285
286 fn dark_checked(self, class: impl Into<String>) -> Self {
287 let variant = DarkModeVariant::checked(class);
288 self.class(variant.to_class_name())
289 }
290
291 fn dark_group_hover(self, class: impl Into<String>) -> Self {
292 let variant = DarkModeVariant::group_hover(class);
293 self.class(variant.to_class_name())
294 }
295
296 fn dark_group_focus(self, class: impl Into<String>) -> Self {
297 let variant = DarkModeVariant::group_focus(class);
298 self.class(variant.to_class_name())
299 }
300
301 fn dark_bg(self, color: impl Into<String>) -> Self {
302 self.dark_mode(format!("bg-{}", color.into()))
303 }
304
305 fn dark_text(self, color: impl Into<String>) -> Self {
306 self.dark_mode(format!("text-{}", color.into()))
307 }
308
309 fn dark_border(self, color: impl Into<String>) -> Self {
310 self.dark_mode(format!("border-{}", color.into()))
311 }
312
313 fn dark_hover_bg(self, color: impl Into<String>) -> Self {
314 self.dark_hover(format!("bg-{}", color.into()))
315 }
316
317 fn dark_hover_text(self, color: impl Into<String>) -> Self {
318 self.dark_hover(format!("text-{}", color.into()))
319 }
320
321 fn dark_focus_bg(self, color: impl Into<String>) -> Self {
322 self.dark_focus(format!("bg-{}", color.into()))
323 }
324
325 fn dark_focus_text(self, color: impl Into<String>) -> Self {
326 self.dark_focus(format!("text-{}", color.into()))
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 #[test]
335 fn test_dark_mode_variant_creation() {
336 let variant = DarkModeVariant::new("bg-gray-800");
337 assert_eq!(variant.to_class_name(), "dark:bg-gray-800");
338 }
339
340 #[test]
341 fn test_dark_mode_hover_variant() {
342 let variant = DarkModeVariant::hover("bg-gray-700");
343 assert_eq!(variant.to_class_name(), "dark:hover:bg-gray-700");
344 }
345
346 #[test]
347 fn test_dark_mode_focus_variant() {
348 let variant = DarkModeVariant::focus("bg-gray-700");
349 assert_eq!(variant.to_class_name(), "dark:focus:bg-gray-700");
350 }
351
352 #[test]
353 fn test_dark_mode_active_variant() {
354 let variant = DarkModeVariant::active("bg-gray-700");
355 assert_eq!(variant.to_class_name(), "dark:active:bg-gray-700");
356 }
357
358 #[test]
359 fn test_dark_mode_disabled_variant() {
360 let variant = DarkModeVariant::disabled("bg-gray-700");
361 assert_eq!(variant.to_class_name(), "dark:disabled:bg-gray-700");
362 }
363
364 #[test]
365 fn test_dark_mode_checked_variant() {
366 let variant = DarkModeVariant::checked("bg-gray-700");
367 assert_eq!(variant.to_class_name(), "dark:checked:bg-gray-700");
368 }
369
370 #[test]
371 fn test_dark_mode_group_hover_variant() {
372 let variant = DarkModeVariant::group_hover("bg-gray-700");
373 assert_eq!(variant.to_class_name(), "dark:group-hover:bg-gray-700");
374 }
375
376 #[test]
377 fn test_dark_mode_group_focus_variant() {
378 let variant = DarkModeVariant::group_focus("bg-gray-700");
379 assert_eq!(variant.to_class_name(), "dark:group-focus:bg-gray-700");
380 }
381
382 #[test]
383 fn test_dark_mode_variant_validation() {
384 assert!(DarkModeVariant::new("bg-gray-800").validate().is_ok());
386 assert!(DarkModeVariant::hover("bg-gray-700").validate().is_ok());
387 assert!(DarkModeVariant::focus("bg-gray-700").validate().is_ok());
388
389 assert!(DarkModeVariant::new("").validate().is_err());
391 }
392
393 #[test]
394 fn test_dark_mode_variant_display() {
395 let variant = DarkModeVariant::new("bg-gray-800");
396 assert_eq!(format!("{}", variant), "dark:bg-gray-800");
397 }
398
399 #[test]
400 fn test_dark_mode_variant_utilities() {
401 let classes = ClassBuilder::new()
402 .dark_bg("gray-800")
403 .dark_text("white")
404 .dark_border("gray-700")
405 .dark_hover_bg("gray-700")
406 .dark_hover_text("gray-100")
407 .dark_focus_bg("gray-600")
408 .dark_focus_text("gray-50")
409 .build();
410
411 let css_classes = classes.to_css_classes();
412 assert!(css_classes.contains("dark:bg-gray-800"));
413 assert!(css_classes.contains("dark:text-white"));
414 assert!(css_classes.contains("dark:border-gray-700"));
415 assert!(css_classes.contains("dark:hover:bg-gray-700"));
416 assert!(css_classes.contains("dark:hover:text-gray-100"));
417 assert!(css_classes.contains("dark:focus:bg-gray-600"));
418 assert!(css_classes.contains("dark:focus:text-gray-50"));
419 }
420
421 #[test]
422 fn test_dark_mode_variant_utilities_advanced() {
423 let classes = ClassBuilder::new()
424 .dark_mode("bg-gray-800")
425 .dark_hover("bg-gray-700")
426 .dark_focus("bg-gray-600")
427 .dark_active("bg-gray-500")
428 .dark_disabled("bg-gray-400")
429 .dark_checked("bg-gray-300")
430 .dark_group_hover("bg-gray-200")
431 .dark_group_focus("bg-gray-100")
432 .build();
433
434 let css_classes = classes.to_css_classes();
435 assert!(css_classes.contains("dark:bg-gray-800"));
436 assert!(css_classes.contains("dark:hover:bg-gray-700"));
437 assert!(css_classes.contains("dark:focus:bg-gray-600"));
438 assert!(css_classes.contains("dark:active:bg-gray-500"));
439 assert!(css_classes.contains("dark:disabled:bg-gray-400"));
440 assert!(css_classes.contains("dark:checked:bg-gray-300"));
441 assert!(css_classes.contains("dark:group-hover:bg-gray-200"));
442 assert!(css_classes.contains("dark:group-focus:bg-gray-100"));
443 }
444}