Skip to main content

basecoat_core/classes/
button.rs

1use crate::props::button::{ButtonProps, ButtonSize, ButtonVariant};
2
3/// Returns the canonical CSS class string for a button.
4///
5/// Upstream basecoat uses compound classes: `btn-{size}-{variant}` for non-default
6/// sizes, and `btn-{variant}` for default (no size prefix). Examples:
7/// - Default + Primary   → `"btn-primary"`
8/// - Sm + Outline        → `"btn-sm-outline"`
9/// - Lg + Destructive    → `"btn-lg-destructive"`
10/// - Icon + Ghost        → `"btn-icon-ghost"`
11pub fn button(p: &ButtonProps) -> String {
12    let variant_str = match p.variant {
13        ButtonVariant::Primary => "primary",
14        ButtonVariant::Secondary => "secondary",
15        ButtonVariant::Outline => "outline",
16        ButtonVariant::Ghost => "ghost",
17        ButtonVariant::Link => "link",
18        ButtonVariant::Destructive => "destructive",
19    };
20
21    let base = match p.size {
22        ButtonSize::Default => format!("btn-{variant_str}"),
23        ButtonSize::Sm => format!("btn-sm-{variant_str}"),
24        ButtonSize::Lg => format!("btn-lg-{variant_str}"),
25        ButtonSize::Icon => format!("btn-icon-{variant_str}"),
26    };
27
28    match &p.class {
29        Some(extra) if !extra.is_empty() => format!("{base} {extra}"),
30        _ => base,
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use crate::props::button::{ButtonProps, ButtonSize, ButtonVariant};
38
39    #[test]
40    fn default_variant_and_size() {
41        let p = ButtonProps::default();
42        assert_eq!(button(&p), "btn-primary");
43    }
44
45    #[test]
46    fn outline_default_size() {
47        let p = ButtonProps {
48            variant: ButtonVariant::Outline,
49            ..Default::default()
50        };
51        assert_eq!(button(&p), "btn-outline");
52    }
53
54    #[test]
55    fn sm_outline() {
56        let p = ButtonProps {
57            variant: ButtonVariant::Outline,
58            size: ButtonSize::Sm,
59            ..Default::default()
60        };
61        assert_eq!(button(&p), "btn-sm-outline");
62    }
63
64    #[test]
65    fn lg_destructive() {
66        let p = ButtonProps {
67            variant: ButtonVariant::Destructive,
68            size: ButtonSize::Lg,
69            ..Default::default()
70        };
71        assert_eq!(button(&p), "btn-lg-destructive");
72    }
73
74    #[test]
75    fn icon_ghost() {
76        let p = ButtonProps {
77            variant: ButtonVariant::Ghost,
78            size: ButtonSize::Icon,
79            ..Default::default()
80        };
81        assert_eq!(button(&p), "btn-icon-ghost");
82    }
83
84    #[test]
85    fn extra_class_appended() {
86        let p = ButtonProps {
87            variant: ButtonVariant::Secondary,
88            class: Some("w-full".into()),
89            ..Default::default()
90        };
91        assert_eq!(button(&p), "btn-secondary w-full");
92    }
93
94    #[test]
95    fn sm_secondary() {
96        let p = ButtonProps {
97            variant: ButtonVariant::Secondary,
98            size: ButtonSize::Sm,
99            ..Default::default()
100        };
101        assert_eq!(button(&p), "btn-sm-secondary");
102    }
103
104    #[test]
105    fn lg_primary() {
106        let p = ButtonProps {
107            size: ButtonSize::Lg,
108            ..Default::default()
109        };
110        assert_eq!(button(&p), "btn-lg-primary");
111    }
112}