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}