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
27#[derive(Clone, Copy, PartialEq, Debug, Default)]
28pub enum ButtonSize {
29    Xs,
30    Sm,
31    #[default]
32    Md,
33    Lg,
34    Xl,
35}
36
37impl ButtonSize {
38    pub fn scss_class(&self) -> &'static str {
39        match self {
40            ButtonSize::Xs => "sd-btn--xs",
41            ButtonSize::Sm => "sd-btn--sm",
42            ButtonSize::Md => "sd-btn--md",
43            ButtonSize::Lg => "sd-btn--lg",
44            ButtonSize::Xl => "sd-btn--xl",
45        }
46    }
47}
48
49#[component]
50pub fn Button(
51    #[props(default)] variant: ButtonVariant,
52    #[props(default)] size: ButtonSize,
53    #[props(default)] disabled: bool,
54    #[props(default)] loading: bool,
55    #[props(default)] full_width: bool,
56    #[props(default)] glow: Option<bool>,
57    #[props(default)] left_icon: Option<Element>,
58    #[props(default)] right_icon: Option<Element>,
59    #[props(default)] class: String,
60    onclick: Option<EventHandler<MouseEvent>>,
61    children: Element,
62) -> Element {
63    let size_class = size.scss_class();
64    let full_class = if full_width { "sd-btn--full" } else { "" };
65
66    #[cfg(feature = "theme")]
67    let theme = crate::theme::use_theme();
68
69    #[cfg(feature = "theme")]
70    let variant_class = if variant == ButtonVariant::Primary {
71        match theme().primary_color {
72            crate::theme::PrimaryColor::Violet => "sd-btn--primary",
73            crate::theme::PrimaryColor::Obsidian => "sd-btn--primary-obsidian",
74            crate::theme::PrimaryColor::Crimson => "sd-btn--primary-crimson",
75            crate::theme::PrimaryColor::Cyan => "sd-btn--primary-cyan",
76            crate::theme::PrimaryColor::Emerald => "sd-btn--primary-emerald",
77        }
78    } else {
79        variant.scss_class()
80    };
81
82    #[cfg(feature = "theme")]
83    let radius_class = match size {
84        ButtonSize::Xs | ButtonSize::Sm => theme().radius_scale.sm(),
85        ButtonSize::Md => theme().radius_scale.md(),
86        ButtonSize::Lg | ButtonSize::Xl => theme().radius_scale.lg(),
87    };
88
89    #[cfg(feature = "theme")]
90    let theme_val = theme();
91    #[cfg(feature = "theme")]
92    let glow_class = match glow {
93        Some(true) => theme_val.component_glow("button"),
94        Some(false) => "",
95        None => {
96            if variant == ButtonVariant::Primary && theme_val.glow_mode != crate::theme::GlowMode::Off
97            {
98                theme_val.component_glow("button")
99            } else {
100                ""
101            }
102        }
103    };
104
105    #[cfg(not(feature = "theme"))]
106    let variant_class = variant.scss_class();
107    #[cfg(not(feature = "theme"))]
108    let radius_class = "";
109    #[cfg(not(feature = "theme"))]
110    let glow_class = if glow == Some(true) || (glow.is_none() && variant == ButtonVariant::Primary)
111    {
112        "sd-btn--glow"
113    } else {
114        ""
115    };
116
117    let is_disabled = disabled || loading;
118
119    rsx! {
120        button {
121            r#type: "button",
122            class: "sd-btn {variant_class} {size_class} {full_class} {radius_class} {glow_class} {class}",
123            disabled: is_disabled,
124            onclick: move |evt| {
125                if !is_disabled {
126                    if let Some(handler) = onclick.as_ref() {
127                        handler.call(evt);
128                    }
129                }
130            },
131            if loading {
132                svg {
133                    class: "animate-spin shrink-0 h-4 w-4 mr-1",
134                    xmlns: "http://www.w3.org/2000/svg",
135                    fill: "none",
136                    view_box: "0 0 24 24",
137                    circle {
138                        class: "opacity-25",
139                        cx: "12",
140                        cy: "12",
141                        r: "10",
142                        stroke: "currentColor",
143                        stroke_width: "4",
144                    }
145                    path {
146                        class: "opacity-75",
147                        fill: "currentColor",
148                        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",
149                    }
150                }
151            } else if let Some(licon) = left_icon {
152                span { class: "shrink-0 inline-flex items-center", {licon} }
153            }
154            {children}
155            if !loading {
156                if let Some(ricon) = right_icon {
157                    span { class: "shrink-0 inline-flex items-center", {ricon} }
158                }
159            }
160        }
161    }
162}