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 #[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}