dao_ui/components/button/
button.rs1use css_in_rust::Style;
2use yew::{
3 html, Component, ShouldRender, Html, ComponentLink,
4 Properties, Children, Callback, MouseEvent,
5 Classes,
6};
7use yew::virtual_dom::{VNode, VList};
8use crate::theme::{Theme, fade};
9
10pub enum Msg {
11 Click(MouseEvent),
12}
13
14#[derive(Clone, PartialEq, Debug)]
15#[allow(dead_code)]
16pub enum ButtonSize {
17 Small,
18 Medium,
19 Large,
20}
21
22#[derive(Clone, PartialEq, Debug)]
23#[allow(dead_code)]
24pub enum ButtonVariant {
25 Contained,
26 Outlined,
27 Inline,
28}
29
30#[derive(Clone, PartialEq, Debug)]
31#[allow(dead_code)]
32pub enum ButtonColor {
33 Primary,
34 Secondary,
35 Error,
36}
37
38#[derive(Clone, PartialEq, Debug)]
39#[allow(dead_code)]
40pub enum ButtonType {
41 Submit,
42 Button,
43}
44
45pub struct Button {
46 style: Style,
47 props: Props,
48 link: ComponentLink<Self>,
49}
50
51#[derive(Properties, Clone, PartialEq, Debug)]
52pub struct Props {
53 #[prop_or_default]
54 pub class: String,
55 #[prop_or_default]
56 pub children: Children,
57 #[prop_or_else(Callback::noop)]
58 pub onclick: Callback<MouseEvent>,
59 #[prop_or(ButtonVariant::Contained)]
60 pub variant: ButtonVariant,
61 #[prop_or(ButtonColor::Primary)]
62 pub color: ButtonColor,
63 #[prop_or(ButtonSize::Medium)]
64 pub size: ButtonSize,
65 #[prop_or(false)]
66 pub fullwidth: bool,
67 #[prop_or(false)]
68 pub loading: bool,
69 #[prop_or(false)]
70 pub disabled: bool,
71 #[prop_or(ButtonType::Button)]
72 pub button_type: ButtonType,
73}
74
75fn get_style_class(variant: &ButtonVariant, color: &ButtonColor) -> String {
76 let v = match variant {
77 ButtonVariant::Contained => { "contained" }
78 ButtonVariant::Outlined => { "outlined" }
79 ButtonVariant::Inline => { "inline" }
80 };
81 let c = match color {
82 ButtonColor::Primary => { "primary" }
83 ButtonColor::Secondary => { "secondary" }
84 ButtonColor::Error => { "error" }
85 };
86
87 format!("{c}-{v}", c = c, v = v)
88}
89
90fn get_size_class(size: &ButtonSize) -> String {
91 let s = match size {
92 ButtonSize::Small => { "small" }
93 ButtonSize::Medium => { "medium" }
94 ButtonSize::Large => { "large" }
95 };
96
97 format!("size-{}", s)
98}
99
100impl Component for Button {
101 type Message = Msg;
102 type Properties = Props;
103
104 fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
105 let theme = Theme::default();
106 let style = Style::create(
107 String::from("button"),
108 include_str!("button.scss")
109 .replace("$button_border_radius", &theme.components.button.border_radius)
110 .replace("$button_small_size_padding", &theme.components.button.small_size_padding)
111 .replace("$button_large_size_padding", &theme.components.button.large_size_padding)
112 .replace("$palette_primary_main_fade", &fade(theme.palette.primary.main.clone()))
113 .replace("$palette_primary_main", &theme.palette.primary.main)
114 .replace("$palette_primary_dark", &theme.palette.primary.dark)
115 .replace("$palette_secondary_main_fade", &fade(theme.palette.secondary.main.clone()))
116 .replace("$palette_secondary_main", &theme.palette.secondary.main)
117 .replace("$palette_secondary_dark", &theme.palette.secondary.dark)
118 .replace("$button_large_height", &theme.components.button.large_height)
119 .replace("$button_small_height", &theme.components.button.small_height)
120 .replace("$palette_error_main_fade", &fade(theme.palette.error.main.clone()))
121 .replace("$palette_error_main", &theme.palette.error.main)
122 .replace("$palette_error_dark", &theme.palette.error.dark),
123 )
124 .expect("An error occured while creating the style");
125
126 Self {
127 style,
128 props,
129 link,
130 }
131 }
132
133 fn update(&mut self, msg: Self::Message) -> ShouldRender {
134 match msg {
135 Msg::Click(event) => {
136 println!("Click");
137 self.props.onclick.emit(event);
138 }
139 }
140 false
141 }
142
143 fn change(&mut self, props: Self::Properties) -> ShouldRender {
144 if self.props != props {
145 self.props = props;
146
147 true
148 } else {
149 false
150 }
151 }
152
153 fn view(&self) -> Html {
154 let onclick = self.link.callback(Msg::Click);
155 let class = self.format_classes();
156
157 html! {
158 <button
159 class=class
160 onclick=onclick
161 disabled=self.props.disabled || self.props.loading
162 type=self.get_type()
163 >
164 <div class="content">
165 {self.props.children.clone()}
166 </div>
167 {self.render_spinner()}
168 </button>
169 }
170 }
171}
172
173impl Button {
174 fn format_classes(&self) -> Classes {
175 let mut classes = Classes::from(self.style.clone().to_string());
176
177 classes.push(self.props.class.clone());
178 classes.push(get_style_class(&self.props.variant, &self.props.color));
179 classes.push(get_size_class(&self.props.size));
180
181 if self.props.disabled {
182 classes.push("disabled");
183 }
184 if self.props.loading {
185 classes.push("loading");
186 }
187 if self.props.fullwidth {
188 classes.push("fullwidth");
189 }
190
191 classes
192 }
193
194 fn render_spinner(&self) -> Html {
195 if self.props.loading {
196 html! {
197 <svg
198 class="spinner"
199 viewBox="0 0 50 50"
200 data-ui-element="button-spinner"
201 >
202 <circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
203 </svg>
204 }
205 } else {
206 VNode::from(VList::new())
207 }
208 }
209
210 fn get_type(&self) -> String {
211 match self.props.button_type {
212 ButtonType::Button => { String::from("button") }
213 ButtonType::Submit => { String::from("submit") }
214 }
215 }
216}