Skip to main content

shadow_design/
button.rs

1use dioxus::prelude::*;
2
3#[derive(Clone, Copy, PartialEq, Debug, Default)]
4pub enum ButtonVariant {
5    #[default]
6    Primary,
7    Secondary,
8    Outline,
9    Ghost,
10    Danger,
11    Success,
12}
13
14impl ButtonVariant {
15    pub fn scss_class(&self) -> &'static str {
16        match self {
17            ButtonVariant::Primary => "sd-btn--primary",
18            ButtonVariant::Secondary => "sd-btn--secondary",
19            ButtonVariant::Outline => "sd-btn--outline",
20            ButtonVariant::Ghost => "sd-btn--ghost",
21            ButtonVariant::Danger => "sd-btn--danger",
22            ButtonVariant::Success => "sd-btn--success",
23        }
24    }
25
26    pub fn name(&self) -> &'static str {
27        match self {
28            ButtonVariant::Primary => "primary",
29            ButtonVariant::Secondary => "secondary",
30            ButtonVariant::Outline => "outline",
31            ButtonVariant::Ghost => "ghost",
32            ButtonVariant::Danger => "danger",
33            ButtonVariant::Success => "success",
34        }
35    }
36}
37
38#[derive(Clone, Copy, PartialEq, Debug, Default)]
39pub enum ButtonSize {
40    Xs,
41    Sm,
42    #[default]
43    Md,
44    Lg,
45    Xl,
46}
47
48impl ButtonSize {
49    pub fn scss_class(&self) -> &'static str {
50        match self {
51            ButtonSize::Xs => "sd-btn--xs",
52            ButtonSize::Sm => "sd-btn--sm",
53            ButtonSize::Md => "sd-btn--md",
54            ButtonSize::Lg => "sd-btn--lg",
55            ButtonSize::Xl => "sd-btn--xl",
56        }
57    }
58
59    pub fn name(&self) -> &'static str {
60        match self {
61            ButtonSize::Xs => "xs",
62            ButtonSize::Sm => "sm",
63            ButtonSize::Md => "md",
64            ButtonSize::Lg => "lg",
65            ButtonSize::Xl => "xl",
66        }
67    }
68}
69
70#[component]
71pub fn Button(
72    #[props(default)] variant: ButtonVariant,
73    #[props(default)] size: ButtonSize,
74    #[props(default)] disabled: bool,
75    #[props(default)] loading: bool,
76    #[props(default)] full_width: bool,
77    #[props(default)] glow: Option<bool>,
78    #[props(default)] left_icon: Option<Element>,
79    #[props(default)] right_icon: Option<Element>,
80    #[props(default)] class: String,
81    onclick: Option<EventHandler<MouseEvent>>,
82    children: Element,
83) -> Element {
84    let size_class = size.scss_class();
85    let full_class = if full_width { "sd-btn--full" } else { "" };
86
87    #[cfg(feature = "theme")]
88    let theme_val = crate::theme::use_theme()();
89
90    #[cfg(feature = "theme")]
91    let default_primary = match &theme_val.primary_color {
92        crate::theme::PrimaryColor::Violet => "sd-btn--primary".to_string(),
93        crate::theme::PrimaryColor::Obsidian => "sd-btn--primary-obsidian".to_string(),
94        crate::theme::PrimaryColor::Crimson => "sd-btn--primary-crimson".to_string(),
95        crate::theme::PrimaryColor::Cyan => "sd-btn--primary-cyan".to_string(),
96        crate::theme::PrimaryColor::Emerald => "sd-btn--primary-emerald".to_string(),
97        crate::theme::PrimaryColor::Custom { .. } => {
98            format!("sd-btn--primary-{}", theme_val.primary_color.css_prefix())
99        }
100    };
101
102    #[cfg(feature = "theme")]
103    let fallback_variant = if variant == ButtonVariant::Primary {
104        default_primary
105    } else {
106        variant.scss_class().to_string()
107    };
108
109    // Risoluzione a 3 Livelli: L1 (fallback) -> L2 (Theme override)
110    #[cfg(feature = "theme")]
111    let variant_class = theme_val.resolve_variant_class("button", variant.name(), &fallback_variant);
112
113    #[cfg(feature = "theme")]
114    let base_override = theme_val.resolve_component_base("button").unwrap_or("");
115
116    #[cfg(feature = "theme")]
117    let class_modifiers = theme_val.resolve_class_modifiers("button", &class);
118
119    #[cfg(feature = "theme")]
120    let radius_class = match size {
121        ButtonSize::Xs | ButtonSize::Sm => theme_val.radius_scale.sm(),
122        ButtonSize::Md => theme_val.radius_scale.md(),
123        ButtonSize::Lg | ButtonSize::Xl => theme_val.radius_scale.lg(),
124    };
125
126    #[cfg(feature = "theme")]
127    let glow_class = theme_val.resolve_elevation_class("button", glow);
128
129    #[cfg(not(feature = "theme"))]
130    let base_override = "";
131    #[cfg(not(feature = "theme"))]
132    let class_modifiers = String::new();
133    #[cfg(not(feature = "theme"))]
134    let variant_class = variant.scss_class().to_string();
135    #[cfg(not(feature = "theme"))]
136    let radius_class = "";
137    #[cfg(not(feature = "theme"))]
138    let glow_class = if glow == Some(true) || (glow.is_none() && variant == ButtonVariant::Primary)
139    {
140        "sd-btn--glow".to_string()
141    } else {
142        String::new()
143    };
144
145    let is_disabled = disabled || loading;
146
147    rsx! {
148        button {
149            r#type: "button",
150            class: "sd-btn {base_override} {variant_class} {size_class} {full_class} {radius_class} {glow_class} {class_modifiers} {class}",
151            disabled: is_disabled,
152            onclick: move |evt| {
153                if !is_disabled {
154                    if let Some(handler) = onclick.as_ref() {
155                        handler.call(evt);
156                    }
157                }
158            },
159            if loading {
160                svg {
161                    class: "animate-spin shrink-0 h-4 w-4 mr-1",
162                    xmlns: "http://www.w3.org/2000/svg",
163                    fill: "none",
164                    view_box: "0 0 24 24",
165                    circle {
166                        class: "opacity-25",
167                        cx: "12",
168                        cy: "12",
169                        r: "10",
170                        stroke: "currentColor",
171                        stroke_width: "4",
172                    }
173                    path {
174                        class: "opacity-75",
175                        fill: "currentColor",
176                        d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z",
177                    }
178                }
179            } else if let Some(licon) = left_icon {
180                span { class: "sd-btn__icon sd-btn__icon--left", {licon} }
181            }
182            span { class: "sd-btn__text", {children} }
183            if !loading {
184                if let Some(ricon) = right_icon {
185                    span { class: "sd-btn__icon sd-btn__icon--right", {ricon} }
186                }
187            }
188        }
189    }
190}