1use crate::classes::ClassBuilder;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub enum BoxShadow {
13 None,
15 Sm,
17 Default,
19 Md,
21 Lg,
23 Xl,
25 Xl2,
27 Inner,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub enum DropShadow {
34 None,
36 Sm,
38 Default,
40 Md,
42 Lg,
44 Xl,
46 Xl2,
48 Xl3,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
54pub enum Opacity {
55 Zero,
57 Five,
59 Ten,
61 Twenty,
63 TwentyFive,
65 Thirty,
67 Forty,
69 Fifty,
71 Sixty,
73 Seventy,
75 SeventyFive,
77 Eighty,
79 Ninety,
81 NinetyFive,
83 Hundred,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
89pub enum MixBlendMode {
90 Normal,
92 Multiply,
94 Screen,
96 Overlay,
98 Darken,
100 Lighten,
102 ColorDodge,
104 ColorBurn,
106 HardLight,
108 SoftLight,
110 Difference,
112 Exclusion,
114 Hue,
116 Saturation,
118 Color,
120 Luminosity,
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
126pub enum BackgroundBlendMode {
127 Normal,
129 Multiply,
131 Screen,
133 Overlay,
135 Darken,
137 Lighten,
139 ColorDodge,
141 ColorBurn,
143 HardLight,
145 SoftLight,
147 Difference,
149 Exclusion,
151 Hue,
153 Saturation,
155 Color,
157 Luminosity,
159}
160
161impl BoxShadow {
162 pub fn to_class_name(&self) -> String {
163 match self {
164 BoxShadow::None => "none".to_string(),
165 BoxShadow::Sm => "sm".to_string(),
166 BoxShadow::Default => "default".to_string(),
167 BoxShadow::Md => "md".to_string(),
168 BoxShadow::Lg => "lg".to_string(),
169 BoxShadow::Xl => "xl".to_string(),
170 BoxShadow::Xl2 => "2xl".to_string(),
171 BoxShadow::Inner => "inner".to_string(),
172 }
173 }
174
175 pub fn to_css_value(&self) -> String {
176 match self {
177 BoxShadow::None => "none".to_string(),
178 BoxShadow::Sm => "0 1px 2px 0 rgb(0 0 0 / 0.05)".to_string(),
179 BoxShadow::Default => "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)".to_string(),
180 BoxShadow::Md => "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)".to_string(),
181 BoxShadow::Lg => "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)".to_string(),
182 BoxShadow::Xl => "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)".to_string(),
183 BoxShadow::Xl2 => "0 25px 50px -12px rgb(0 0 0 / 0.25)".to_string(),
184 BoxShadow::Inner => "inset 0 2px 4px 0 rgb(0 0 0 / 0.05)".to_string(),
185 }
186 }
187}
188
189impl DropShadow {
190 pub fn to_class_name(&self) -> String {
191 match self {
192 DropShadow::None => "none".to_string(),
193 DropShadow::Sm => "sm".to_string(),
194 DropShadow::Default => "default".to_string(),
195 DropShadow::Md => "md".to_string(),
196 DropShadow::Lg => "lg".to_string(),
197 DropShadow::Xl => "xl".to_string(),
198 DropShadow::Xl2 => "2xl".to_string(),
199 DropShadow::Xl3 => "3xl".to_string(),
200 }
201 }
202
203 pub fn to_css_value(&self) -> String {
204 match self {
205 DropShadow::None => "none".to_string(),
206 DropShadow::Sm => "0 1px 2px rgb(0 0 0 / 0.05)".to_string(),
207 DropShadow::Default => "0 1px 3px rgb(0 0 0 / 0.1), 0 1px 2px rgb(0 0 0 / 0.06)".to_string(),
208 DropShadow::Md => "0 4px 6px rgb(0 0 0 / 0.07), 0 2px 4px rgb(0 0 0 / 0.06)".to_string(),
209 DropShadow::Lg => "0 10px 15px rgb(0 0 0 / 0.1), 0 4px 6px rgb(0 0 0 / 0.05)".to_string(),
210 DropShadow::Xl => "0 20px 25px rgb(0 0 0 / 0.1), 0 8px 10px rgb(0 0 0 / 0.04)".to_string(),
211 DropShadow::Xl2 => "0 25px 50px rgb(0 0 0 / 0.25)".to_string(),
212 DropShadow::Xl3 => "0 35px 60px rgb(0 0 0 / 0.3)".to_string(),
213 }
214 }
215}
216
217impl Opacity {
218 pub fn to_class_name(&self) -> String {
219 match self {
220 Opacity::Zero => "0".to_string(),
221 Opacity::Five => "5".to_string(),
222 Opacity::Ten => "10".to_string(),
223 Opacity::Twenty => "20".to_string(),
224 Opacity::TwentyFive => "25".to_string(),
225 Opacity::Thirty => "30".to_string(),
226 Opacity::Forty => "40".to_string(),
227 Opacity::Fifty => "50".to_string(),
228 Opacity::Sixty => "60".to_string(),
229 Opacity::Seventy => "70".to_string(),
230 Opacity::SeventyFive => "75".to_string(),
231 Opacity::Eighty => "80".to_string(),
232 Opacity::Ninety => "90".to_string(),
233 Opacity::NinetyFive => "95".to_string(),
234 Opacity::Hundred => "100".to_string(),
235 }
236 }
237
238 pub fn to_css_value(&self) -> String {
239 match self {
240 Opacity::Zero => "0".to_string(),
241 Opacity::Five => "0.05".to_string(),
242 Opacity::Ten => "0.1".to_string(),
243 Opacity::Twenty => "0.2".to_string(),
244 Opacity::TwentyFive => "0.25".to_string(),
245 Opacity::Thirty => "0.3".to_string(),
246 Opacity::Forty => "0.4".to_string(),
247 Opacity::Fifty => "0.5".to_string(),
248 Opacity::Sixty => "0.6".to_string(),
249 Opacity::Seventy => "0.7".to_string(),
250 Opacity::SeventyFive => "0.75".to_string(),
251 Opacity::Eighty => "0.8".to_string(),
252 Opacity::Ninety => "0.9".to_string(),
253 Opacity::NinetyFive => "0.95".to_string(),
254 Opacity::Hundred => "1".to_string(),
255 }
256 }
257}
258
259impl MixBlendMode {
260 pub fn to_class_name(&self) -> String {
261 match self {
262 MixBlendMode::Normal => "normal".to_string(),
263 MixBlendMode::Multiply => "multiply".to_string(),
264 MixBlendMode::Screen => "screen".to_string(),
265 MixBlendMode::Overlay => "overlay".to_string(),
266 MixBlendMode::Darken => "darken".to_string(),
267 MixBlendMode::Lighten => "lighten".to_string(),
268 MixBlendMode::ColorDodge => "color-dodge".to_string(),
269 MixBlendMode::ColorBurn => "color-burn".to_string(),
270 MixBlendMode::HardLight => "hard-light".to_string(),
271 MixBlendMode::SoftLight => "soft-light".to_string(),
272 MixBlendMode::Difference => "difference".to_string(),
273 MixBlendMode::Exclusion => "exclusion".to_string(),
274 MixBlendMode::Hue => "hue".to_string(),
275 MixBlendMode::Saturation => "saturation".to_string(),
276 MixBlendMode::Color => "color".to_string(),
277 MixBlendMode::Luminosity => "luminosity".to_string(),
278 }
279 }
280
281 pub fn to_css_value(&self) -> String {
282 match self {
283 MixBlendMode::Normal => "normal".to_string(),
284 MixBlendMode::Multiply => "multiply".to_string(),
285 MixBlendMode::Screen => "screen".to_string(),
286 MixBlendMode::Overlay => "overlay".to_string(),
287 MixBlendMode::Darken => "darken".to_string(),
288 MixBlendMode::Lighten => "lighten".to_string(),
289 MixBlendMode::ColorDodge => "color-dodge".to_string(),
290 MixBlendMode::ColorBurn => "color-burn".to_string(),
291 MixBlendMode::HardLight => "hard-light".to_string(),
292 MixBlendMode::SoftLight => "soft-light".to_string(),
293 MixBlendMode::Difference => "difference".to_string(),
294 MixBlendMode::Exclusion => "exclusion".to_string(),
295 MixBlendMode::Hue => "hue".to_string(),
296 MixBlendMode::Saturation => "saturation".to_string(),
297 MixBlendMode::Color => "color".to_string(),
298 MixBlendMode::Luminosity => "luminosity".to_string(),
299 }
300 }
301}
302
303impl BackgroundBlendMode {
304 pub fn to_class_name(&self) -> String {
305 match self {
306 BackgroundBlendMode::Normal => "normal".to_string(),
307 BackgroundBlendMode::Multiply => "multiply".to_string(),
308 BackgroundBlendMode::Screen => "screen".to_string(),
309 BackgroundBlendMode::Overlay => "overlay".to_string(),
310 BackgroundBlendMode::Darken => "darken".to_string(),
311 BackgroundBlendMode::Lighten => "lighten".to_string(),
312 BackgroundBlendMode::ColorDodge => "color-dodge".to_string(),
313 BackgroundBlendMode::ColorBurn => "color-burn".to_string(),
314 BackgroundBlendMode::HardLight => "hard-light".to_string(),
315 BackgroundBlendMode::SoftLight => "soft-light".to_string(),
316 BackgroundBlendMode::Difference => "difference".to_string(),
317 BackgroundBlendMode::Exclusion => "exclusion".to_string(),
318 BackgroundBlendMode::Hue => "hue".to_string(),
319 BackgroundBlendMode::Saturation => "saturation".to_string(),
320 BackgroundBlendMode::Color => "color".to_string(),
321 BackgroundBlendMode::Luminosity => "luminosity".to_string(),
322 }
323 }
324
325 pub fn to_css_value(&self) -> String {
326 match self {
327 BackgroundBlendMode::Normal => "normal".to_string(),
328 BackgroundBlendMode::Multiply => "multiply".to_string(),
329 BackgroundBlendMode::Screen => "screen".to_string(),
330 BackgroundBlendMode::Overlay => "overlay".to_string(),
331 BackgroundBlendMode::Darken => "darken".to_string(),
332 BackgroundBlendMode::Lighten => "lighten".to_string(),
333 BackgroundBlendMode::ColorDodge => "color-dodge".to_string(),
334 BackgroundBlendMode::ColorBurn => "color-burn".to_string(),
335 BackgroundBlendMode::HardLight => "hard-light".to_string(),
336 BackgroundBlendMode::SoftLight => "soft-light".to_string(),
337 BackgroundBlendMode::Difference => "difference".to_string(),
338 BackgroundBlendMode::Exclusion => "exclusion".to_string(),
339 BackgroundBlendMode::Hue => "hue".to_string(),
340 BackgroundBlendMode::Saturation => "saturation".to_string(),
341 BackgroundBlendMode::Color => "color".to_string(),
342 BackgroundBlendMode::Luminosity => "luminosity".to_string(),
343 }
344 }
345}
346
347impl fmt::Display for BoxShadow {
348 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349 write!(f, "{}", self.to_class_name())
350 }
351}
352
353impl fmt::Display for DropShadow {
354 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355 write!(f, "{}", self.to_class_name())
356 }
357}
358
359impl fmt::Display for Opacity {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 write!(f, "{}", self.to_class_name())
362 }
363}
364
365impl fmt::Display for MixBlendMode {
366 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 write!(f, "{}", self.to_class_name())
368 }
369}
370
371impl fmt::Display for BackgroundBlendMode {
372 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373 write!(f, "{}", self.to_class_name())
374 }
375}
376
377pub trait BoxShadowUtilities {
379 fn box_shadow(self, shadow: BoxShadow) -> Self;
380}
381
382impl BoxShadowUtilities for ClassBuilder {
383 fn box_shadow(self, shadow: BoxShadow) -> Self {
384 self.class(format!("shadow-{}", shadow.to_class_name()))
385 }
386}
387
388pub trait DropShadowUtilities {
390 fn drop_shadow(self, shadow: DropShadow) -> Self;
391}
392
393impl DropShadowUtilities for ClassBuilder {
394 fn drop_shadow(self, shadow: DropShadow) -> Self {
395 self.class(format!("drop-shadow-{}", shadow.to_class_name()))
396 }
397}
398
399pub trait OpacityUtilities {
401 fn opacity(self, opacity: Opacity) -> Self;
402}
403
404impl OpacityUtilities for ClassBuilder {
405 fn opacity(self, opacity: Opacity) -> Self {
406 self.class(format!("opacity-{}", opacity.to_class_name()))
407 }
408}
409
410pub trait MixBlendModeUtilities {
412 fn mix_blend_mode(self, mode: MixBlendMode) -> Self;
413}
414
415impl MixBlendModeUtilities for ClassBuilder {
416 fn mix_blend_mode(self, mode: MixBlendMode) -> Self {
417 self.class(format!("mix-blend-{}", mode.to_class_name()))
418 }
419}
420
421pub trait BackgroundBlendModeUtilities {
423 fn background_blend_mode(self, mode: BackgroundBlendMode) -> Self;
424}
425
426impl BackgroundBlendModeUtilities for ClassBuilder {
427 fn background_blend_mode(self, mode: BackgroundBlendMode) -> Self {
428 self.class(format!("bg-blend-{}", mode.to_class_name()))
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 #[test]
437 fn test_box_shadow_utilities() {
438 let classes = ClassBuilder::new()
439 .box_shadow(BoxShadow::None)
440 .box_shadow(BoxShadow::Sm)
441 .box_shadow(BoxShadow::Default)
442 .box_shadow(BoxShadow::Md)
443 .box_shadow(BoxShadow::Lg)
444 .box_shadow(BoxShadow::Xl)
445 .box_shadow(BoxShadow::Xl2)
446 .box_shadow(BoxShadow::Inner)
447 .build();
448
449 let css_classes = classes.to_css_classes();
450 assert!(css_classes.contains("shadow-none"));
451 assert!(css_classes.contains("shadow-sm"));
452 assert!(css_classes.contains("shadow-default"));
453 assert!(css_classes.contains("shadow-md"));
454 assert!(css_classes.contains("shadow-lg"));
455 assert!(css_classes.contains("shadow-xl"));
456 assert!(css_classes.contains("shadow-2xl"));
457 assert!(css_classes.contains("shadow-inner"));
458 }
459
460 #[test]
461 fn test_drop_shadow_utilities() {
462 let classes = ClassBuilder::new()
463 .drop_shadow(DropShadow::None)
464 .drop_shadow(DropShadow::Sm)
465 .drop_shadow(DropShadow::Default)
466 .drop_shadow(DropShadow::Md)
467 .drop_shadow(DropShadow::Lg)
468 .drop_shadow(DropShadow::Xl)
469 .drop_shadow(DropShadow::Xl2)
470 .drop_shadow(DropShadow::Xl3)
471 .build();
472
473 let css_classes = classes.to_css_classes();
474 assert!(css_classes.contains("drop-shadow-none"));
475 assert!(css_classes.contains("drop-shadow-sm"));
476 assert!(css_classes.contains("drop-shadow-default"));
477 assert!(css_classes.contains("drop-shadow-md"));
478 assert!(css_classes.contains("drop-shadow-lg"));
479 assert!(css_classes.contains("drop-shadow-xl"));
480 assert!(css_classes.contains("drop-shadow-2xl"));
481 assert!(css_classes.contains("drop-shadow-3xl"));
482 }
483
484 #[test]
485 fn test_opacity_utilities() {
486 let classes = ClassBuilder::new()
487 .opacity(Opacity::Zero)
488 .opacity(Opacity::TwentyFive)
489 .opacity(Opacity::Fifty)
490 .opacity(Opacity::SeventyFive)
491 .opacity(Opacity::Hundred)
492 .build();
493
494 let css_classes = classes.to_css_classes();
495 assert!(css_classes.contains("opacity-0"));
496 assert!(css_classes.contains("opacity-25"));
497 assert!(css_classes.contains("opacity-50"));
498 assert!(css_classes.contains("opacity-75"));
499 assert!(css_classes.contains("opacity-100"));
500 }
501
502 #[test]
503 fn test_mix_blend_mode_utilities() {
504 let classes = ClassBuilder::new()
505 .mix_blend_mode(MixBlendMode::Normal)
506 .mix_blend_mode(MixBlendMode::Multiply)
507 .mix_blend_mode(MixBlendMode::Screen)
508 .mix_blend_mode(MixBlendMode::Overlay)
509 .mix_blend_mode(MixBlendMode::Difference)
510 .build();
511
512 let css_classes = classes.to_css_classes();
513 assert!(css_classes.contains("mix-blend-normal"));
514 assert!(css_classes.contains("mix-blend-multiply"));
515 assert!(css_classes.contains("mix-blend-screen"));
516 assert!(css_classes.contains("mix-blend-overlay"));
517 assert!(css_classes.contains("mix-blend-difference"));
518 }
519
520 #[test]
521 fn test_background_blend_mode_utilities() {
522 let classes = ClassBuilder::new()
523 .background_blend_mode(BackgroundBlendMode::Normal)
524 .background_blend_mode(BackgroundBlendMode::Multiply)
525 .background_blend_mode(BackgroundBlendMode::Screen)
526 .background_blend_mode(BackgroundBlendMode::Overlay)
527 .background_blend_mode(BackgroundBlendMode::Difference)
528 .build();
529
530 let css_classes = classes.to_css_classes();
531 assert!(css_classes.contains("bg-blend-normal"));
532 assert!(css_classes.contains("bg-blend-multiply"));
533 assert!(css_classes.contains("bg-blend-screen"));
534 assert!(css_classes.contains("bg-blend-overlay"));
535 assert!(css_classes.contains("bg-blend-difference"));
536 }
537
538 #[test]
539 fn test_complex_effects_combination() {
540 let classes = ClassBuilder::new()
541 .box_shadow(BoxShadow::Lg)
542 .drop_shadow(DropShadow::Md)
543 .opacity(Opacity::Eighty)
544 .mix_blend_mode(MixBlendMode::Multiply)
545 .background_blend_mode(BackgroundBlendMode::Screen)
546 .build();
547
548 let css_classes = classes.to_css_classes();
549 assert!(css_classes.contains("shadow-lg"));
550 assert!(css_classes.contains("drop-shadow-md"));
551 assert!(css_classes.contains("opacity-80"));
552 assert!(css_classes.contains("mix-blend-multiply"));
553 assert!(css_classes.contains("bg-blend-screen"));
554 }
555
556 #[test]
558 fn test_week9_shadow_opacity_utilities() {
559 let classes = ClassBuilder::new()
561 .box_shadow(BoxShadow::Sm)
563 .box_shadow(BoxShadow::Default)
564 .box_shadow(BoxShadow::Md)
565 .box_shadow(BoxShadow::Lg)
566 .box_shadow(BoxShadow::Xl)
567 .box_shadow(BoxShadow::Xl2)
568 .box_shadow(BoxShadow::Inner)
569 .box_shadow(BoxShadow::None)
570 .drop_shadow(DropShadow::Sm)
572 .drop_shadow(DropShadow::Default)
573 .drop_shadow(DropShadow::Md)
574 .drop_shadow(DropShadow::Lg)
575 .drop_shadow(DropShadow::Xl)
576 .drop_shadow(DropShadow::Xl2)
577 .drop_shadow(DropShadow::None)
578 .opacity(Opacity::Zero)
580 .opacity(Opacity::Five)
581 .opacity(Opacity::Ten)
582 .opacity(Opacity::Twenty)
583 .opacity(Opacity::TwentyFive)
584 .opacity(Opacity::Thirty)
585 .opacity(Opacity::Forty)
586 .opacity(Opacity::Fifty)
587 .opacity(Opacity::Sixty)
588 .opacity(Opacity::Seventy)
589 .opacity(Opacity::SeventyFive)
590 .opacity(Opacity::Eighty)
591 .opacity(Opacity::Ninety)
592 .opacity(Opacity::NinetyFive)
593 .opacity(Opacity::Hundred)
594 .build();
595
596 let css_classes = classes.to_css_classes();
597
598 assert!(css_classes.contains("shadow-sm"));
600 assert!(css_classes.contains("shadow"));
601 assert!(css_classes.contains("shadow-md"));
602 assert!(css_classes.contains("shadow-lg"));
603 assert!(css_classes.contains("shadow-xl"));
604 assert!(css_classes.contains("shadow-2xl"));
605 assert!(css_classes.contains("shadow-inner"));
606 assert!(css_classes.contains("shadow-none"));
607
608 assert!(css_classes.contains("drop-shadow-sm"));
610 assert!(css_classes.contains("drop-shadow"));
611 assert!(css_classes.contains("drop-shadow-md"));
612 assert!(css_classes.contains("drop-shadow-lg"));
613 assert!(css_classes.contains("drop-shadow-xl"));
614 assert!(css_classes.contains("drop-shadow-2xl"));
615 assert!(css_classes.contains("drop-shadow-none"));
616
617 assert!(css_classes.contains("opacity-0"));
619 assert!(css_classes.contains("opacity-5"));
620 assert!(css_classes.contains("opacity-10"));
621 assert!(css_classes.contains("opacity-20"));
622 assert!(css_classes.contains("opacity-25"));
623 assert!(css_classes.contains("opacity-30"));
624 assert!(css_classes.contains("opacity-40"));
625 assert!(css_classes.contains("opacity-50"));
626 assert!(css_classes.contains("opacity-60"));
627 assert!(css_classes.contains("opacity-70"));
628 assert!(css_classes.contains("opacity-75"));
629 assert!(css_classes.contains("opacity-80"));
630 assert!(css_classes.contains("opacity-90"));
631 assert!(css_classes.contains("opacity-95"));
632 assert!(css_classes.contains("opacity-100"));
633 }
634}