Skip to main content

dioxus_material/
text_field.rs

1use crate::use_theme;
2use dioxus::prelude::*;
3use dioxus_spring::{use_animated, use_spring};
4use dioxus_use_mounted::use_mounted;
5use std::time::Duration;
6
7/// Text field component.
8///
9/// Text fields let users enter text into a UI.
10///
11/// [material.io](https://m3.material.io/components/text-fields)
12///
13/// ## Panics
14/// This component requires access to a [`Theme`](crate::Theme).
15///
16/// ## Examples
17/// ```rust
18/// use dioxus::prelude::*;
19/// use dioxus_material::{TextField, Theme};
20///
21/// fn app() -> Element {
22///     let mut value = use_signal(|| String::from("Filled"));
23///     rsx!(
24///         Theme {
25///             TextField {
26///                 label: "Text field",
27///                 value: "{value}",
28///                 onchange: move |event: FormEvent| value.set(event.value())
29///             }
30///         }
31///     )
32/// }
33/// ```
34#[component]
35pub fn TextField(
36    label: String,
37    value: String,
38    onchange: EventHandler<FormEvent>,
39    background: Option<String>,
40    font_size: Option<f32>,
41    width: Option<String>,
42) -> Element {
43    let mut is_populated = use_signal(|| !value.is_empty());
44    let theme = use_theme();
45
46    let font_size = font_size.unwrap_or(theme.label_medium);
47    let spring = use_spring(
48        if is_populated() {
49            [10f32, 12f32, 16f32]
50        } else {
51            [20., font_size, 24.]
52        },
53        Duration::from_millis(50),
54    );
55
56    let mounted = use_mounted();
57    use_animated(mounted, spring, |[top, font_size, line_height]| {
58        format!(
59            r"
60            position: absolute;
61            top: {top}px;
62            left: 20px;
63            font-size: {font_size}px;
64            line-height: {line_height}px;
65        "
66        )
67    });
68
69    let background = background.as_deref().unwrap_or(&theme.background_color);
70    let width = width.as_deref().unwrap_or("200px");
71
72    rsx!(
73        div {
74            position: "relative",
75            display: "flex",
76            width,
77            background: "{background}",
78            font_family: "sans-serif",
79            border_bottom: "2px solid #999",
80            label { onmounted: move |event| mounted.onmounted(event), "{label}" }
81            input {
82                position: "relative",
83                z_index: 9,
84                r#type: "text",
85                value: value.clone(),
86                padding: "10px 20px",
87                padding_top: "30px",
88                font_size: "{font_size}px",
89                height: "34px",
90                border: "none",
91                outline: "none",
92                background: "none",
93                onfocusin: move |_| {
94                    if !is_populated() {
95                        is_populated.set(true)
96                    }
97                },
98                onfocusout: move |_| {
99                    if is_populated() && value.is_empty() {
100                        is_populated.set(false)
101                    }
102                },
103                oninput: move |event| onchange.call(event)
104            }
105        }
106    )
107}