1mod color;
6mod position;
7mod size;
8mod variant;
9
10use yew::{
11 classes, function_component, html, AttrValue, Callback, Children, Classes, DragEvent,
12 FocusEvent, Html, KeyboardEvent, MouseEvent, Properties, TouchEvent,
13};
14
15use crate::button::Button;
16use crate::circular_progress::{CircularProgress, Size as CircularProgressSize};
17use crate::styles::button_variant::ButtonVariant;
18use crate::styles::color::Color;
19use crate::styles::size::Size;
20pub use position::Position;
21
22#[derive(Debug, Clone, PartialEq, Properties)]
23pub struct Props {
24 #[prop_or_default]
25 pub aria_label: AttrValue,
26
27 #[prop_or_default]
28 pub children: Children,
29
30 #[prop_or_default]
31 pub classes: Classes,
32
33 #[prop_or(Color::Default)]
35 pub color: Color,
36
37 #[prop_or(false)]
38 pub disabled: bool,
39
40 #[prop_or(false)]
41 pub disable_elevation: bool,
42
43 #[prop_or(false)]
44 pub full_width: bool,
45
46 #[prop_or(false)]
50 pub loading: bool,
51
52 #[prop_or_default]
54 pub loading_indicator: Option<Html>,
55
56 #[prop_or(Position::Center)]
57 pub loading_position: Position,
58
59 #[prop_or_default]
60 pub start_icon: Option<Html>,
61
62 #[prop_or_default]
63 pub end_icon: Option<Html>,
64
65 #[prop_or(Size::Medium)]
67 pub size: Size,
68
69 #[prop_or_default]
70 pub style: AttrValue,
71
72 #[prop_or_default]
73 pub tab_index: Option<i32>,
74
75 #[prop_or(ButtonVariant::Text)]
76 pub variant: ButtonVariant,
77
78 #[prop_or_default]
79 pub on_blur: Option<Callback<FocusEvent>>,
80
81 #[prop_or_default]
82 pub on_click: Option<Callback<MouseEvent>>,
83
84 #[prop_or_default]
85 pub on_context_menu: Option<Callback<MouseEvent>>,
86
87 #[prop_or_default]
88 pub on_drag_leave: Option<Callback<DragEvent>>,
89
90 #[prop_or_default]
91 pub on_focus: Option<Callback<FocusEvent>>,
92
93 #[prop_or_default]
95 pub on_focus_visible: Option<Callback<FocusEvent>>,
96
97 #[prop_or_default]
98 pub on_key_down: Option<Callback<KeyboardEvent>>,
99
100 #[prop_or_default]
101 pub on_key_up: Option<Callback<KeyboardEvent>>,
102
103 #[prop_or_default]
104 pub on_mouse_down: Option<Callback<MouseEvent>>,
105
106 #[prop_or_default]
107 pub on_mouse_enter: Option<Callback<MouseEvent>>,
108
109 #[prop_or_default]
110 pub on_mouse_leave: Option<Callback<MouseEvent>>,
111
112 #[prop_or_default]
113 pub on_mouse_up: Option<Callback<MouseEvent>>,
114
115 #[prop_or_default]
116 pub on_touch_end: Option<Callback<TouchEvent>>,
117
118 #[prop_or_default]
119 pub on_touch_move: Option<Callback<TouchEvent>>,
120
121 #[prop_or_default]
122 pub on_touch_start: Option<Callback<TouchEvent>>,
123}
124
125fn create_indicator(
126 indicator: &Option<Html>,
127 position: Position,
128 button_size: Size,
129 button_variant: ButtonVariant,
130 full_width: bool,
131) -> Html {
132 let root_cls = classes!(
133 "ZuLoadingButton-loadingIndicator",
134 position.indicator_class(),
135 size::indicator_class(button_size),
136 variant::indicator_class(button_variant),
137 if full_width {
138 "ZuLoadingButton-loadingIndicatorFullWidth"
139 } else {
140 ""
141 }
142 );
143
144 if let Some(indicator) = indicator {
145 html! {
146 <span class={root_cls}>
147 {indicator.clone()}
148 </span>
149 }
150 } else {
151 html! {
152 <div class={root_cls}>
153 <CircularProgress color={Color::Inherit} size={CircularProgressSize::Num(16)} />
154 </div>
155 }
156 }
157}
158
159#[function_component(LoadingButton)]
160pub fn loading_button(props: &Props) -> Html {
161 if props.start_icon.is_some() && props.loading_position != Position::Start {
162 log::error!("The loading_position='start' should be used in combination with start_icon.");
163 }
164 if props.end_icon.is_some() && props.loading_position != Position::End {
165 log::error!("The loading_position='end' should be used in combination with end_icon.");
166 }
167
168 let root_cls = classes!(
169 "ZuLoadingButton-root",
170 if props.loading && props.loading_position == Position::Center {
171 "ZuLoadingButton-loading"
172 } else {
173 ""
174 },
175 if props.disable_elevation {
176 "ZuLoadingButton-disableElevation"
177 } else {
178 ""
179 },
180 if props.full_width {
181 "ZuLoadingButton-fullWidth"
182 } else {
183 ""
184 },
185 props.classes.clone(),
186 );
187
188 let indicator = if props.loading {
189 create_indicator(
190 &props.loading_indicator,
191 props.loading_position,
192 props.size,
193 props.variant,
194 props.full_width,
195 )
196 } else {
197 html! {}
198 };
199
200 html! {
201 <Button
202 classes={root_cls}
203 aria_label={&props.aria_label}
204 disabled={props.disabled || props.loading}
205 size={props.size}
206 start_icon={props.start_icon.clone()}
207 start_icon_classes={"ZuLoadingButton-startIconLoadingStart"}
208 end_icon={props.end_icon.clone()}
209 end_icon_classes={"ZuLoadingButton-endIconLoadingEnd"}
210 tab_index={props.tab_index}
211 on_blur={&props.on_blur}
212 on_click={&props.on_click}
213 on_context_menu={&props.on_context_menu}
214 on_drag_leave={&props.on_drag_leave}
215 on_focus={&props.on_focus}
216 on_focus_visible={&props.on_focus_visible}
217 on_key_down={&props.on_key_down}
218 on_key_up={&props.on_key_up}
219 on_mouse_down={&props.on_mouse_down}
220 on_mouse_enter={&props.on_mouse_enter}
221 on_mouse_leave={&props.on_mouse_leave}
222 on_mouse_up={&props.on_mouse_up}
223 on_touch_end={&props.on_touch_end}
224 on_touch_move={&props.on_touch_move}
225 on_touch_start={&props.on_touch_start}
226 >
227
228 if props.loading_position == Position::End {
229 {for props.children.iter()}
230 {indicator}
231 } else {
232 {indicator}
233 {for props.children.iter()}
234 }
235
236 </Button>
237 }
238}