yew_google_material/input_text/
mod.rs

1//! # GTextInput
2//! is similar to google material text field (but not identical) `https://material-web.dev/components/text-field`
3//! It allows you to choose style, add leading and/or trailing icons, or leading and/or trailing icon buttons. 
4//! 
5//! The key size attribute of input field is `font_size`. It bonds a lot of other sizes of input text field and has the default value 16px. 
6//! According to this 1px here = 0.0625em
7//! 
8//! GTextInput has a lot of attributes, but only `id`, onchange and `label` are required. Label here has the same role as placeholder. If you do not need `label`, add it with empty double quotes `""`.
9//! All other attributes with default parameters:
10//! - style: GInputStyle,
11//! [default GInputStyle::Outlined]
12//! - name: `AttrValue`,
13//! [default ""]
14//! - event: `GInputEvent`,
15//! [default GInputEvent::OnChange]
16//! - input_type: `AttrValue`, 
17//! [default "text"]. Another variants: password, search, number, month, url, etc.
18//! - class: `AttrValue`,
19//! [default ""]
20//! - required: `bool`,
21//! [default false]
22//! - multiple: `bool`,
23//! [default false]
24//! - readonly: `bool`,
25//! [default false]
26//! - maxlength: `Option<i32>`,
27//! [default None]
28//! - max: `Option<AttrValue>`,
29//! [default None]
30//! - minlength: `Option<i32>,`
31//! [default None]
32//! - min: `Option<AttrValue>`,
33//! [default None]
34//! - step: `Option<AttrValue>`, 
35//! [default None]
36//! - no_spinner: `Option<bool>`, 
37//! [default None]
38//! - value: `AttrValue`, 
39//! [default ""]
40//! - autocomplete: `AttrValue`, 
41//! [default "off"]
42//! - autofocus: bool,
43//! [default false]
44//! - disabled: bool,
45//! [default false]
46//! - inputmode: `Option<AttrValue>`,
47//! [default None]
48//! - id: `AttrValue`,
49//! - label: `AttrValue`,
50//! - onchange: `Callback<AttrValue>`,
51//! - width: `AttrValue`, 
52//! [default "100%"]
53//! - height: `Option<AttrValue>`
54//! [default 3.5em] Be careful to change this! It can break the sizes of text field. Better use `em`, e.g. `3.4em` or `2em`
55//! - font_size: `AttrValue`, 
56//! [default "16px"]
57//! - border_radius: `AttrValue`, 
58//! [default "4px"] It is similar to container_shape in google material buttons
59//! - border_color: `AttrValue`, 
60//! [default "grey"]
61//! - border_color_hover: `AttrValue`, 
62//! [default "black"]
63//! - border_focus_color: `AttrValue`, 
64//! [default "#6200ee"]
65//! - label_background_color: `AttrValue`, 
66//! [default "white"]
67//! - label_text_color: `AttrValue`, 
68//! [default "#aaa"]
69//! - align_supporting_text: `AttrValue`, 
70//! [default "left"]
71//! - supporting_text_color: `Option<AttrValue>`, 
72//! [default None] e.g. `black` or `red` or `#ffffff`
73//! - supporting_text: `Option<AttrValue>`, 
74//! [default None] e.g. `*required` or `Error`
75//! - no_asterisk: `bool`, 
76//! [default false]
77//! - has_leading_icon: `bool`, 
78//! [default false]
79//! - has_trailing_icon: `bool`, 
80//! [default false]
81//! 
82//! See the describtion of this attributes here: `https://material-web.dev/components/text-field/#api`
83//! 
84//! ## Examples
85//! ```
86//! use yew::prelude::*;
87//! use yew_google_material::prelude::*;
88//! 
89//! let onchange_username = Callback::from(|username: AttrValue| {Msg::InputUsername(username)});
90//! 
91//! <GTextInput
92//! id="username_text_login_name"
93//! onchange={onchange_username} 
94//! label="Имя пользователя" />
95//! ```
96//! 
97//! Also you can add leading and trailing GIcons, change style, change Event to InputEvent and do many other things via attributes in this way:
98//! 
99//! ```
100//! use yew::prelude::*;
101//! use yew_google_material::prelude::*;
102//! 
103//! let search_input = Callback::from(|search_input: AttrValue| {Msg::Search(search_input)});
104//! 
105//! <GTextInput
106//!     id="username_text_login_name"
107//!     event={GInputEvent::OnInput}
108//!     oninput={search_input} 
109//!     has_leading_icon=true
110//!     has_trailing_icon=true
111//!     input_type="text" 
112//!     supporting_text="text"
113//!     label="Введите поисковый запрос" >
114//!     <GIcon 
115//!        icon="search" 
116//!        leading_icon=true
117//!        icon_style={GIconStyle::Outlined} 
118//!     />
119//!     <GIcon 
120//!        icon="cancel" 
121//!        trailing_icon=true
122//!        icon_style={GIconStyle::Outlined} 
123//!     />
124//! </GTextInput>
125//! ```
126//! 
127//! If you need to add trailing button icon inside input field, instead of `GIcon` use `GButton` inside `<GTextInput></GTextInput>` with attributes:
128//! `has_icon` (icon name from `fonts.google.com/icons`), `trailing_icon` (`true`), `parent` (`DependsOn::GTextInput`), `icon_style` (Outlined, Rounded or Sharp)
129//! 
130//! Do not use `label` attribute for `GButton` inside `GTextInput`!
131//! ```
132//! <GTextInput
133//!     id="username_text_login_name"
134//!     onchange={username_input} 
135//!     input_type="text" 
136//!     has_trailing_icon=true
137//!     supporting_text="text"
138//!     label="Введите поисковый запрос" >
139//!     <GButton 
140//!         id="login_button"
141//!         button_type="button"
142//!         parent={DependsOn::GTextInput}      // required inside GTextInput
143//!         style={GButtonStyle::Outlined}      // required for icon inside GButton
144//!         label_color="#6750A4"
145//!         has_icon="login"                    // required for icon inside GButton
146//!         trailing_icon=true
147//!         icon_style={GIconStyle::Outlined}   // required for icon inside GButton
148//!     />
149//! </GTextInput>
150//! ```
151//! If you need leading button icon element inside `GTextInput`, just remove `trailing_icon` attribute from `GButton`, add `has_leading_icon=true` for `GTextInput` and remove `has_trailing_icon=true`. 
152//! Attention! It is recomended to use `button_type` attribute with `"button"`, or your button will be on its own inside `<form></form>` element.
153
154pub(crate) mod input_text_css;
155use web_sys::HtmlInputElement;
156use yew::{prelude::*, virtual_dom::VNode};
157
158use crate::{input_text::input_text_css::input_style, GInputStyle};
159
160#[derive(Debug, Clone)]
161pub enum Msg {
162    InputTextInit,
163    InputTextOnchange,
164    InputTextOninput,
165}
166
167#[derive(PartialEq, Default)]
168pub enum GInputEvent {
169    #[default]
170    OnChange,
171    OnInput,
172}
173
174#[derive(Properties, PartialEq)]
175pub struct GTextInputProps {
176    #[prop_or_default]
177    pub style: GInputStyle,
178    #[prop_or_default]
179    pub name: AttrValue,
180    #[prop_or_default]
181    pub event: GInputEvent,
182    #[prop_or_else(|| AttrValue::from("text"))]
183    pub input_type: AttrValue, 
184    #[prop_or_default]
185    pub class: AttrValue,
186    #[prop_or_default]
187    pub required: bool,
188    #[prop_or_default]
189    pub multiple: bool,
190    #[prop_or_default]
191    pub readonly: bool,
192    #[prop_or_default]
193    pub maxlength: Option<i32>,
194    #[prop_or_default]
195    pub max: Option<AttrValue>,
196    #[prop_or_default]
197    pub minlength: Option<i32>,
198    #[prop_or_default]
199    pub min: Option<AttrValue>,
200    #[prop_or_default]
201    pub step: Option<AttrValue>, 
202    #[prop_or_default]
203    pub no_spinner: Option<bool>, 
204    #[prop_or_default]
205    pub value: AttrValue, 
206    #[prop_or_else(|| AttrValue::from("off"))]
207    pub autocomplete: AttrValue, 
208    #[prop_or_default]
209    pub pattern: Option<AttrValue>, 
210    #[prop_or_default]
211    pub autofocus: bool,
212    #[prop_or_default]
213    pub disabled: bool,
214    #[prop_or_default]
215    pub inputmode: Option<AttrValue>,
216    pub id: AttrValue,
217    pub label: AttrValue,
218    pub onchange: Callback<AttrValue>,
219    #[prop_or_else(|| AttrValue::from("100%"))]
220    pub width: AttrValue, 
221    #[prop_or_default]
222    pub height: Option<AttrValue>, 
223    #[prop_or_else(|| AttrValue::from("16px"))]
224    pub font_size: AttrValue, 
225    #[prop_or_else(|| AttrValue::from("4px"))]
226    pub border_radius: AttrValue, 
227    #[prop_or_else(|| AttrValue::from("grey"))]
228    pub border_color: AttrValue, 
229    #[prop_or_else(|| AttrValue::from("black"))]
230    pub border_color_hover: AttrValue, 
231    #[prop_or_else(|| AttrValue::from("#6200ee"))]
232    pub border_focus_color: AttrValue, 
233    #[prop_or_else(|| AttrValue::from("white"))]
234    pub label_background_color: AttrValue, 
235    #[prop_or_else(|| AttrValue::from("#aaa"))]
236    pub label_text_color: AttrValue, 
237    #[prop_or_else(|| AttrValue::from("left"))]
238    pub align_supporting_text: AttrValue, 
239    #[prop_or_default]
240    pub supporting_text_color: Option<AttrValue>, 
241    #[prop_or_default]
242    pub supporting_text: Option<AttrValue>, 
243    #[prop_or_default]
244    pub no_asterisk: bool, 
245    #[prop_or_default]
246    pub has_leading_icon: bool, 
247    #[prop_or_default]
248    pub has_trailing_icon: bool, 
249    #[prop_or_default]
250    pub children: Html,
251}
252
253pub struct GTextInput {
254    refs: NodeRef,
255}
256
257impl Component for GTextInput {
258    type Message = Msg;
259    type Properties = GTextInputProps;
260
261    fn create(ctx: &yew::Context<Self>) -> Self {
262        assert!(!ctx.props().id.is_empty());
263        Self {
264            refs: NodeRef::default(),
265        }
266    } 
267
268    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
269        match msg {
270            Msg::InputTextInit => {
271                let input = self.refs.cast::<HtmlInputElement>().unwrap();
272                if let Some(maxlength) = ctx.props().maxlength {
273                    input.set_max_length(maxlength)
274                }
275                if let Some(max) = &ctx.props().max {
276                    input.set_max(max)
277                }
278                if let Some(minlength) = ctx.props().minlength {
279                    input.set_min_length(minlength)
280                }
281                if let Some(min) = &ctx.props().min {
282                    input.set_min(min)
283                }
284                if let Some(inputmode) = &ctx.props().inputmode {
285                    input.set_input_mode(inputmode)
286                }
287                if let Some(step) = &ctx.props().step {
288                    input.set_step(step)
289                }
290                if let Some(pattern) = &ctx.props().pattern {
291                    input.set_step(pattern)
292                }
293            },
294            Msg::InputTextOnchange => {
295                let input = self.refs.cast::<HtmlInputElement>();
296                if let Some(input) = input {
297                    ctx.props().onchange.emit(AttrValue::from(input.value()));
298                } 
299            },
300            Msg::InputTextOninput => {
301                let input = self.refs.cast::<HtmlInputElement>();
302                if let Some(input) = input {
303                    ctx.props().onchange.emit(AttrValue::from(input.value()));
304                } 
305            },
306        }
307        false
308    }
309
310    fn view(&self, ctx: &yew::Context<Self>) -> Html {
311        let g_init = AttrValue::from(format!("g_init_{}", ctx.props().id));
312        let g_container = AttrValue::from(format!("g_container_{}", ctx.props().id));
313        let stylesheet = input_style(
314            &ctx.props().style,
315            ctx.props().id.clone(),
316            g_init.clone(),
317            g_container.clone(),
318            ctx.props().width.clone(), 
319            ctx.props().height.clone(), 
320            ctx.props().font_size.clone(), 
321            ctx.props().border_radius.clone(), 
322            ctx.props().border_color.clone(), 
323            ctx.props().border_color_hover.clone(), 
324            ctx.props().border_focus_color.clone(), 
325            ctx.props().label_background_color.clone(), 
326            ctx.props().label_text_color.clone(), 
327            ctx.props().align_supporting_text.clone(), 
328            ctx.props().supporting_text_color.clone(),
329            ctx.props().no_asterisk.clone(), 
330            ctx.props().has_leading_icon.clone(), 
331            ctx.props().has_trailing_icon.clone(), 
332            ctx.props().no_spinner.clone(), 
333        );
334        let onfocus = ctx.link().callback(|_| Msg::InputTextInit);
335        let onchange = ctx.link().callback(|_| Msg::InputTextOnchange);
336        let oninput = ctx.link().callback(|_| Msg::InputTextOninput);
337        let input_event: VNode = match ctx.props().event {
338            GInputEvent::OnChange => html! {
339                <input 
340                    ref={&self.refs}
341                    {onfocus}
342                    {onchange} 
343                    class={&ctx.props().class} 
344                    id={&ctx.props().id}
345                    type={&ctx.props().input_type} 
346                    name={&ctx.props().name} 
347                    value={&ctx.props().value}
348                    required={ctx.props().required} 
349                    autofocus={ctx.props().autofocus}
350                    autocomplete={&ctx.props().autocomplete}
351                    multiple={ctx.props().multiple}
352                    readonly={ctx.props().readonly}
353                    disabled={ctx.props().disabled}
354                    placeholder={""}
355                />
356            },
357            GInputEvent::OnInput => html! {
358                <input 
359                    ref={&self.refs}
360                    {onfocus}
361                    {oninput} 
362                    class={&ctx.props().class} 
363                    id={&ctx.props().id}
364                    type={&ctx.props().input_type} 
365                    name={&ctx.props().name} 
366                    value={&ctx.props().value}
367                    required={ctx.props().required} 
368                    autofocus={ctx.props().autofocus}
369                    autocomplete={&ctx.props().autocomplete}
370                    multiple={ctx.props().multiple}
371                    readonly={ctx.props().readonly}
372                    disabled={ctx.props().disabled}
373                    placeholder={""}
374                />
375            }
376        };
377        html! {
378            <stl class={stylesheet}>
379                <div id={g_init}>  
380                    <div id={g_container}>  
381                        {input_event}
382                        <label for={&ctx.props().id}>{&ctx.props().label}</label>
383                        {ctx.props().children.clone()}
384                    </div>
385                    <div class="g_supporting_text_below_input_text_field">
386                        {ctx.props().supporting_text.clone()}
387                    </div>
388                </div>
389            </stl>
390        }
391    }
392}