1use crate::classes::ClassBuilder;
8use crate::utilities::colors::{Color, ColorPalette, ColorShade};
9use serde::{Deserialize, Serialize};
10use std::fmt;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub enum GradientDirection {
15 ToRight,
17 ToLeft,
19 ToTop,
21 ToBottom,
23 ToTopRight,
25 ToTopLeft,
27 ToBottomRight,
29 ToBottomLeft,
31}
32
33impl GradientDirection {
34 pub fn to_class_name(&self) -> String {
36 match self {
37 GradientDirection::ToRight => "to-r".to_string(),
38 GradientDirection::ToLeft => "to-l".to_string(),
39 GradientDirection::ToTop => "to-t".to_string(),
40 GradientDirection::ToBottom => "to-b".to_string(),
41 GradientDirection::ToTopRight => "to-tr".to_string(),
42 GradientDirection::ToTopLeft => "to-tl".to_string(),
43 GradientDirection::ToBottomRight => "to-br".to_string(),
44 GradientDirection::ToBottomLeft => "to-bl".to_string(),
45 }
46 }
47
48 pub fn to_css_value(&self) -> String {
50 match self {
51 GradientDirection::ToRight => "to right".to_string(),
52 GradientDirection::ToLeft => "to left".to_string(),
53 GradientDirection::ToTop => "to top".to_string(),
54 GradientDirection::ToBottom => "to bottom".to_string(),
55 GradientDirection::ToTopRight => "to top right".to_string(),
56 GradientDirection::ToTopLeft => "to top left".to_string(),
57 GradientDirection::ToBottomRight => "to bottom right".to_string(),
58 GradientDirection::ToBottomLeft => "to bottom left".to_string(),
59 }
60 }
61}
62
63impl fmt::Display for GradientDirection {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(f, "{}", self.to_class_name())
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
71pub enum GradientStop {
72 From(Color),
74 Via(Color),
76 To(Color),
78}
79
80impl GradientStop {
81 pub fn from(color: Color) -> Self {
83 Self::From(color)
84 }
85
86 pub fn via(color: Color) -> Self {
88 Self::Via(color)
89 }
90
91 pub fn to(color: Color) -> Self {
93 Self::To(color)
94 }
95
96 pub fn to_class_name(&self) -> String {
98 match self {
99 GradientStop::From(color) => format!("from-{}", color.to_class_name()),
100 GradientStop::Via(color) => format!("via-{}", color.to_class_name()),
101 GradientStop::To(color) => format!("to-{}", color.to_class_name()),
102 }
103 }
104
105 pub fn to_css_value(&self) -> String {
107 match self {
108 GradientStop::From(color) => color.to_css_value(),
109 GradientStop::Via(color) => color.to_css_value(),
110 GradientStop::To(color) => color.to_css_value(),
111 }
112 }
113}
114
115impl fmt::Display for GradientStop {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 write!(f, "{}", self.to_class_name())
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
123pub struct Gradient {
124 pub direction: GradientDirection,
126 pub stops: Vec<GradientStop>,
128}
129
130impl Gradient {
131 pub fn new(direction: GradientDirection) -> Self {
133 Self {
134 direction,
135 stops: Vec::new(),
136 }
137 }
138
139 pub fn from(mut self, color: Color) -> Self {
141 self.stops.push(GradientStop::from(color));
142 self
143 }
144
145 pub fn via(mut self, color: Color) -> Self {
147 self.stops.push(GradientStop::via(color));
148 self
149 }
150
151 pub fn to(mut self, color: Color) -> Self {
153 self.stops.push(GradientStop::to(color));
154 self
155 }
156
157 pub fn to_class_names(&self) -> Vec<String> {
159 let mut classes = vec![format!("bg-gradient-{}", self.direction.to_class_name())];
160
161 for stop in &self.stops {
162 classes.push(stop.to_class_name());
163 }
164
165 classes
166 }
167
168 pub fn to_css_value(&self) -> String {
170 let mut css = format!("linear-gradient({}, ", self.direction.to_css_value());
171
172 let stop_values: Vec<String> = self.stops.iter()
173 .map(|stop| stop.to_css_value())
174 .collect();
175
176 css.push_str(&stop_values.join(", "));
177 css.push(')');
178 css
179 }
180
181 pub fn validate(&self) -> Result<(), GradientError> {
183 if self.stops.is_empty() {
184 return Err(GradientError::NoStops);
185 }
186
187 let has_from = self.stops.iter().any(|stop| matches!(stop, GradientStop::From(_)));
189 let has_to = self.stops.iter().any(|stop| matches!(stop, GradientStop::To(_)));
190
191 if !has_from {
192 return Err(GradientError::MissingFromStop);
193 }
194
195 if !has_to {
196 return Err(GradientError::MissingToStop);
197 }
198
199 Ok(())
200 }
201}
202
203#[derive(Debug, thiserror::Error)]
205pub enum GradientError {
206 #[error("No gradient stops defined")]
207 NoStops,
208
209 #[error("Missing 'from' stop")]
210 MissingFromStop,
211
212 #[error("Missing 'to' stop")]
213 MissingToStop,
214}
215
216impl fmt::Display for Gradient {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 write!(f, "{}", self.to_class_names().join(" "))
219 }
220}
221
222pub trait GradientUtilities {
224 fn gradient_direction(self, direction: GradientDirection) -> Self;
226
227 fn gradient_from(self, color: Color) -> Self;
229
230 fn gradient_via(self, color: Color) -> Self;
232
233 fn gradient_to(self, color: Color) -> Self;
235
236 fn gradient(self, gradient: Gradient) -> Self;
238
239 fn gradient_simple(self, direction: GradientDirection, from: Color, to: Color) -> Self;
241
242 fn gradient_three(self, direction: GradientDirection, from: Color, via: Color, to: Color) -> Self;
244
245 fn gradient_from_names(self, direction: GradientDirection, from: &str, to: &str) -> Self;
247
248 fn gradient_from_names_with_via(self, direction: GradientDirection, from: &str, via: &str, to: &str) -> Self;
250}
251
252impl GradientUtilities for ClassBuilder {
253 fn gradient_direction(self, direction: GradientDirection) -> Self {
254 self.class(format!("bg-gradient-{}", direction.to_class_name()))
255 }
256
257 fn gradient_from(self, color: Color) -> Self {
258 self.class(format!("from-{}", color.to_class_name()))
259 }
260
261 fn gradient_via(self, color: Color) -> Self {
262 self.class(format!("via-{}", color.to_class_name()))
263 }
264
265 fn gradient_to(self, color: Color) -> Self {
266 self.class(format!("to-{}", color.to_class_name()))
267 }
268
269 fn gradient(self, gradient: Gradient) -> Self {
270 let mut builder = self;
271 let class_names = gradient.to_class_names();
272
273 for class_name in class_names {
274 builder = builder.class(class_name);
275 }
276
277 builder
278 }
279
280 fn gradient_simple(self, direction: GradientDirection, from: Color, to: Color) -> Self {
281 let gradient = Gradient::new(direction)
282 .from(from)
283 .to(to);
284
285 self.gradient(gradient)
286 }
287
288 fn gradient_three(self, direction: GradientDirection, from: Color, via: Color, to: Color) -> Self {
289 let gradient = Gradient::new(direction)
290 .from(from)
291 .via(via)
292 .to(to);
293
294 self.gradient(gradient)
295 }
296
297 fn gradient_from_names(self, direction: GradientDirection, from: &str, to: &str) -> Self {
298 let from_color = parse_color_name(from);
300 let to_color = parse_color_name(to);
301
302 self.gradient_simple(direction, from_color, to_color)
303 }
304
305 fn gradient_from_names_with_via(self, direction: GradientDirection, from: &str, via: &str, to: &str) -> Self {
306 let from_color = parse_color_name(from);
308 let via_color = parse_color_name(via);
309 let to_color = parse_color_name(to);
310
311 self.gradient_three(direction, from_color, via_color, to_color)
312 }
313}
314
315fn parse_color_name(color_name: &str) -> Color {
318 let parts: Vec<&str> = color_name.split('-').collect();
320
321 if parts.len() == 2 {
322 let palette = match parts[0] {
323 "slate" => ColorPalette::Slate,
324 "gray" => ColorPalette::Gray,
325 "zinc" => ColorPalette::Zinc,
326 "neutral" => ColorPalette::Neutral,
327 "stone" => ColorPalette::Stone,
328 "red" => ColorPalette::Red,
329 "orange" => ColorPalette::Orange,
330 "amber" => ColorPalette::Amber,
331 "yellow" => ColorPalette::Yellow,
332 "lime" => ColorPalette::Lime,
333 "green" => ColorPalette::Green,
334 "emerald" => ColorPalette::Emerald,
335 "teal" => ColorPalette::Teal,
336 "cyan" => ColorPalette::Cyan,
337 "sky" => ColorPalette::Sky,
338 "blue" => ColorPalette::Blue,
339 "indigo" => ColorPalette::Indigo,
340 "violet" => ColorPalette::Violet,
341 "purple" => ColorPalette::Purple,
342 "fuchsia" => ColorPalette::Fuchsia,
343 "pink" => ColorPalette::Pink,
344 "rose" => ColorPalette::Rose,
345 _ => ColorPalette::Blue, };
347
348 let shade = match parts[1] {
349 "50" => ColorShade::Shade50,
350 "100" => ColorShade::Shade100,
351 "200" => ColorShade::Shade200,
352 "300" => ColorShade::Shade300,
353 "400" => ColorShade::Shade400,
354 "500" => ColorShade::Shade500,
355 "600" => ColorShade::Shade600,
356 "700" => ColorShade::Shade700,
357 "800" => ColorShade::Shade800,
358 "900" => ColorShade::Shade900,
359 "950" => ColorShade::Shade950,
360 _ => ColorShade::Shade500, };
362
363 Color::new(palette, shade)
364 } else {
365 Color::new(ColorPalette::Blue, ColorShade::Shade500)
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use super::*;
373
374 #[test]
375 fn test_gradient_direction() {
376 assert_eq!(GradientDirection::ToRight.to_class_name(), "to-r");
377 assert_eq!(GradientDirection::ToLeft.to_class_name(), "to-l");
378 assert_eq!(GradientDirection::ToTop.to_class_name(), "to-t");
379 assert_eq!(GradientDirection::ToBottom.to_class_name(), "to-b");
380 assert_eq!(GradientDirection::ToTopRight.to_class_name(), "to-tr");
381 assert_eq!(GradientDirection::ToTopLeft.to_class_name(), "to-tl");
382 assert_eq!(GradientDirection::ToBottomRight.to_class_name(), "to-br");
383 assert_eq!(GradientDirection::ToBottomLeft.to_class_name(), "to-bl");
384 }
385
386 #[test]
387 fn test_gradient_direction_css() {
388 assert_eq!(GradientDirection::ToRight.to_css_value(), "to right");
389 assert_eq!(GradientDirection::ToLeft.to_css_value(), "to left");
390 assert_eq!(GradientDirection::ToTop.to_css_value(), "to top");
391 assert_eq!(GradientDirection::ToBottom.to_css_value(), "to bottom");
392 }
393
394 #[test]
395 fn test_gradient_stop() {
396 let from_stop = GradientStop::from(Color::new(ColorPalette::Blue, ColorShade::Shade500));
397 assert_eq!(from_stop.to_class_name(), "from-blue-500");
398
399 let via_stop = GradientStop::via(Color::new(ColorPalette::Purple, ColorShade::Shade500));
400 assert_eq!(via_stop.to_class_name(), "via-purple-500");
401
402 let to_stop = GradientStop::to(Color::new(ColorPalette::Pink, ColorShade::Shade500));
403 assert_eq!(to_stop.to_class_name(), "to-pink-500");
404 }
405
406 #[test]
407 fn test_gradient_creation() {
408 let gradient = Gradient::new(GradientDirection::ToRight)
409 .from(Color::new(ColorPalette::Blue, ColorShade::Shade500))
410 .to(Color::new(ColorPalette::Red, ColorShade::Shade500));
411
412 let class_names = gradient.to_class_names();
413 assert!(class_names.contains(&"bg-gradient-to-r".to_string()));
414 assert!(class_names.contains(&"from-blue-500".to_string()));
415 assert!(class_names.contains(&"to-red-500".to_string()));
416 }
417
418 #[test]
419 fn test_gradient_three_colors() {
420 let gradient = Gradient::new(GradientDirection::ToRight)
421 .from(Color::new(ColorPalette::Blue, ColorShade::Shade500))
422 .via(Color::new(ColorPalette::Purple, ColorShade::Shade500))
423 .to(Color::new(ColorPalette::Pink, ColorShade::Shade500));
424
425 let class_names = gradient.to_class_names();
426 assert!(class_names.contains(&"bg-gradient-to-r".to_string()));
427 assert!(class_names.contains(&"from-blue-500".to_string()));
428 assert!(class_names.contains(&"via-purple-500".to_string()));
429 assert!(class_names.contains(&"to-pink-500".to_string()));
430 }
431
432 #[test]
433 fn test_gradient_validation() {
434 let valid_gradient = Gradient::new(GradientDirection::ToRight)
436 .from(Color::new(ColorPalette::Blue, ColorShade::Shade500))
437 .to(Color::new(ColorPalette::Red, ColorShade::Shade500));
438 assert!(valid_gradient.validate().is_ok());
439
440 let invalid_gradient = Gradient::new(GradientDirection::ToRight);
442 assert!(invalid_gradient.validate().is_err());
443
444 let invalid_gradient2 = Gradient::new(GradientDirection::ToRight)
446 .to(Color::new(ColorPalette::Red, ColorShade::Shade500));
447 assert!(invalid_gradient2.validate().is_err());
448
449 let invalid_gradient3 = Gradient::new(GradientDirection::ToRight)
451 .from(Color::new(ColorPalette::Blue, ColorShade::Shade500));
452 assert!(invalid_gradient3.validate().is_err());
453 }
454
455 #[test]
456 fn test_gradient_utilities() {
457 let classes = ClassBuilder::new()
458 .gradient_direction(GradientDirection::ToRight)
459 .gradient_from(Color::new(ColorPalette::Blue, ColorShade::Shade500))
460 .gradient_to(Color::new(ColorPalette::Red, ColorShade::Shade500))
461 .build();
462
463 let css_classes = classes.to_css_classes();
464 assert!(css_classes.contains("bg-gradient-to-r"));
465 assert!(css_classes.contains("from-blue-500"));
466 assert!(css_classes.contains("to-red-500"));
467 }
468
469 #[test]
470 fn test_gradient_simple() {
471 let classes = ClassBuilder::new()
472 .gradient_simple(
473 GradientDirection::ToRight,
474 Color::new(ColorPalette::Blue, ColorShade::Shade500),
475 Color::new(ColorPalette::Red, ColorShade::Shade500)
476 )
477 .build();
478
479 let css_classes = classes.to_css_classes();
480 assert!(css_classes.contains("bg-gradient-to-r"));
481 assert!(css_classes.contains("from-blue-500"));
482 assert!(css_classes.contains("to-red-500"));
483 }
484
485 #[test]
486 fn test_gradient_three_colors_utility() {
487 let classes = ClassBuilder::new()
488 .gradient_three(
489 GradientDirection::ToRight,
490 Color::new(ColorPalette::Blue, ColorShade::Shade500),
491 Color::new(ColorPalette::Purple, ColorShade::Shade500),
492 Color::new(ColorPalette::Pink, ColorShade::Shade500)
493 )
494 .build();
495
496 let css_classes = classes.to_css_classes();
497 assert!(css_classes.contains("bg-gradient-to-r"));
498 assert!(css_classes.contains("from-blue-500"));
499 assert!(css_classes.contains("via-purple-500"));
500 assert!(css_classes.contains("to-pink-500"));
501 }
502
503 #[test]
504 fn test_gradient_from_names() {
505 let classes = ClassBuilder::new()
506 .gradient_from_names(GradientDirection::ToRight, "blue-500", "red-500")
507 .build();
508
509 let css_classes = classes.to_css_classes();
510 assert!(css_classes.contains("bg-gradient-to-r"));
511 assert!(css_classes.contains("from-blue-500"));
512 assert!(css_classes.contains("to-red-500"));
513 }
514
515 #[test]
516 fn test_gradient_from_names_with_via() {
517 let classes = ClassBuilder::new()
518 .gradient_from_names_with_via(GradientDirection::ToRight, "blue-500", "purple-500", "pink-500")
519 .build();
520
521 let css_classes = classes.to_css_classes();
522 assert!(css_classes.contains("bg-gradient-to-r"));
523 assert!(css_classes.contains("from-blue-500"));
524 assert!(css_classes.contains("via-purple-500"));
525 assert!(css_classes.contains("to-pink-500"));
526 }
527
528 #[test]
529 fn test_gradient_display() {
530 let gradient = Gradient::new(GradientDirection::ToRight)
531 .from(Color::new(ColorPalette::Blue, ColorShade::Shade500))
532 .to(Color::new(ColorPalette::Red, ColorShade::Shade500));
533
534 let display = format!("{}", gradient);
535 assert!(display.contains("bg-gradient-to-r"));
536 assert!(display.contains("from-blue-500"));
537 assert!(display.contains("to-red-500"));
538 }
539
540 #[test]
541 fn test_gradient_css_value() {
542 let gradient = Gradient::new(GradientDirection::ToRight)
543 .from(Color::new(ColorPalette::Blue, ColorShade::Shade500))
544 .to(Color::new(ColorPalette::Red, ColorShade::Shade500));
545
546 let css_value = gradient.to_css_value();
547 assert!(css_value.contains("linear-gradient(to right"));
548 assert!(css_value.contains("#3b82f6")); assert!(css_value.contains("#ef4444")); }
551}