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}