Skip to main content

dao_ui/components/button/
button.rs

1use 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}