yew_styles/components/
button.rs

1use crate::styles::{get_palette, get_size, get_style, Palette, Size, Style};
2use stylist::{css, StyleSource};
3use wasm_bindgen_test::*;
4use web_sys::window;
5use yew::prelude::*;
6use yew::{utils, App};
7
8/// # Button component
9///
10/// ## Features required
11///
12/// button
13///
14/// ## Example
15///
16/// ```rust
17/// use yew::prelude::*;
18/// use yew::services::ConsoleService;
19/// use yew_styles::{
20///     button::{Button},
21///     styles::{Palette, Style, Size},
22/// };
23///
24/// pub struct App {
25///   link: ComponentLink<Self>,
26/// }
27///
28/// pub enum Msg {
29///   Clicked(String),
30/// }
31/// #[derive(Clone, Properties)]
32/// pub struct Props {}
33///
34/// impl Component for App {
35///     type Message = Msg;
36///     type Properties = Props;
37///
38///     fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
39///         App {
40///             link
41///         }
42///     }
43///
44///     fn update(&mut self, msg: Self::Message) -> ShouldRender {
45///         match msg {
46///             Msg::Clicked(greeting) => {
47///                 let mut console = ConsoleService::log(&format!("{}", menu));
48///             }
49///         }
50///         false
51///     }
52///
53///     fn change(&mut self, _props: Self::Properties) -> ShouldRender {
54///         false
55///     }
56///
57///     fn view(&self) -> Html {
58///        html! {
59///          <Button
60///             onclick_signal=link.callback(move |_| Msg::Clicked(String::from("Hello world")))
61///             class_name="hello-world"
62///             button_palette=Pallete::Standard
63///             button_style=Style::Light
64///             button_size=Size::Medium
65///          >{"Greeting"}</Button>
66///        }
67///     }
68/// }
69/// ```
70pub struct Button {
71    link: ComponentLink<Self>,
72    props: ButtonProps,
73}
74
75#[derive(PartialEq)]
76struct ButtonProps {
77    button_palette: String,
78    button_size: String,
79    button_style: String,
80    class_name: String,
81    id: String,
82    key: String,
83    code_ref: NodeRef,
84    onclick_signal: Callback<MouseEvent>,
85    styles: StyleSource<'static>,
86    children: Children,
87}
88
89impl From<Props> for ButtonProps {
90    fn from(props: Props) -> Self {
91        ButtonProps {
92            button_palette: get_palette(props.button_palette),
93            button_size: get_size(props.button_size),
94            button_style: get_style(props.button_style),
95            class_name: props.class_name,
96            id: props.id,
97            key: props.key,
98            code_ref: props.code_ref,
99            onclick_signal: props.onclick_signal,
100            styles: props.styles,
101            children: props.children,
102        }
103    }
104}
105
106#[derive(Clone, Properties, PartialEq)]
107pub struct Props {
108    /// Type botton style. Default `Palette::Standard`
109    #[prop_or(Palette::Standard)]
110    pub button_palette: Palette,
111    /// General property to add custom class styles
112    #[prop_or_default]
113    pub class_name: String,
114    /// General property to add custom id
115    #[prop_or_default]
116    pub id: String,
117    /// General property to get the ref of the component
118    #[prop_or_default]
119    pub code_ref: NodeRef,
120    /// General property to add keys
121    #[prop_or_default]
122    pub key: String,
123    /// Three diffent button standard sizes. Default `Size::Medium`
124    #[prop_or(Size::Medium)]
125    pub button_size: Size,
126    /// Button styles. Default `Style::Regular`
127    #[prop_or(Style::Regular)]
128    pub button_style: Style,
129    /// Click event for button. Required
130    pub onclick_signal: Callback<MouseEvent>,
131    /// Set css styles directly in the component
132    #[prop_or(css!(""))]
133    pub styles: StyleSource<'static>,
134    pub children: Children,
135}
136
137pub enum Msg {
138    Clicked(MouseEvent),
139}
140
141impl Component for Button {
142    type Message = Msg;
143    type Properties = Props;
144
145    fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
146        Self {
147            link,
148            props: ButtonProps::from(props),
149        }
150    }
151
152    fn update(&mut self, msg: Self::Message) -> ShouldRender {
153        match msg {
154            Msg::Clicked(mouse_event) => {
155                self.props.onclick_signal.emit(mouse_event);
156            }
157        };
158
159        true
160    }
161
162    fn change(&mut self, props: Self::Properties) -> ShouldRender {
163        let prop_mapped = ButtonProps::from(props);
164        if self.props != prop_mapped {
165            self.props = prop_mapped;
166            return true;
167        }
168        true
169    }
170
171    fn view(&self) -> Html {
172        html! {
173            <button
174                onclick=self.link.callback(Msg::Clicked)
175                class=classes!("button",
176                    self.props.button_palette.clone(),
177                    self.props.button_size.clone(),
178                    self.props.button_style.clone(),
179                    self.props.class_name.clone(),
180                    self.props.styles.clone(),
181                )
182                key=self.props.key.clone()
183                ref=self.props.code_ref.clone()
184                id=self.props.id.clone()
185            > { self.props.children.clone() }
186            </button>
187        }
188    }
189}
190
191wasm_bindgen_test_configure!(run_in_browser);
192
193#[wasm_bindgen_test]
194fn should_trigger_action_when_button_clicked() {
195    let body = window().unwrap().document().unwrap().body().unwrap();
196
197    let element = window()
198        .unwrap()
199        .document()
200        .unwrap()
201        .create_element("div")
202        .unwrap();
203    element.set_text_content(Some("home"));
204    element.set_id("menu");
205
206    body.append_child(&element).unwrap();
207
208    let onchange_name = Callback::from(|_| {
209        let content = window()
210            .unwrap()
211            .document()
212            .unwrap()
213            .get_element_by_id("menu")
214            .unwrap();
215
216        content.set_text_content(Some("about"));
217    });
218
219    let props = Props {
220        class_name: String::from("test-button"),
221        id: String::from("button-id-test"),
222        key: "".to_string(),
223        code_ref: NodeRef::default(),
224        button_size: Size::Medium,
225        button_style: Style::Regular,
226        onclick_signal: onchange_name,
227        button_palette: Palette::Standard,
228        styles: css!("background-color: #918d94;"),
229        children: Children::new(vec![html! {<div id="submenu">{"another menu"}</div>}]),
230    };
231
232    let mouse_event = MouseEvent::new("click").unwrap();
233
234    props.onclick_signal.emit(mouse_event);
235
236    let updated_content = window()
237        .unwrap()
238        .document()
239        .unwrap()
240        .get_element_by_id("menu")
241        .unwrap()
242        .text_content()
243        .unwrap();
244
245    assert_eq!(updated_content, String::from("about"));
246}
247
248#[wasm_bindgen_test]
249fn should_create_button_component() {
250    let props = Props {
251        class_name: String::from("test-button"),
252        id: String::from("button-id-test"),
253        key: "".to_string(),
254        code_ref: NodeRef::default(),
255        button_size: Size::Medium,
256        button_style: Style::Regular,
257        onclick_signal: Callback::noop(),
258        button_palette: Palette::Standard,
259        styles: css!("background-color: #918d94;"),
260        children: Children::new(vec![html! {<div id="result">{"result"}</div>}]),
261    };
262
263    let button: App<Button> = App::new();
264    button.mount_with_props(
265        utils::document().get_element_by_id("output").unwrap(),
266        props,
267    );
268
269    let button_element = utils::document()
270        .get_elements_by_tag_name("button")
271        .get_with_index(0)
272        .unwrap();
273
274    let child = button_element.first_element_child().unwrap();
275
276    assert_eq!(button_element.tag_name(), "BUTTON");
277    assert_eq!(child.id(), "result");
278}