Skip to main content

dioxus_ui_system/atoms/
input.rs

1//! Input atom component
2//!
3//! Text input field with full theme integration and state management.
4
5use crate::styles::Style;
6use crate::theme::use_style;
7use dioxus::prelude::*;
8
9/// Input types
10#[derive(Default, Clone, PartialEq)]
11pub enum InputType {
12    #[default]
13    Text,
14    Password,
15    Email,
16    Number,
17    Tel,
18    Url,
19    Search,
20    Date,
21    Time,
22    DatetimeLocal,
23    Month,
24    Week,
25    Color,
26}
27
28impl InputType {
29    pub fn as_str(&self) -> &'static str {
30        match self {
31            InputType::Text => "text",
32            InputType::Password => "password",
33            InputType::Email => "email",
34            InputType::Number => "number",
35            InputType::Tel => "tel",
36            InputType::Url => "url",
37            InputType::Search => "search",
38            InputType::Date => "date",
39            InputType::Time => "time",
40            InputType::DatetimeLocal => "datetime-local",
41            InputType::Month => "month",
42            InputType::Week => "week",
43            InputType::Color => "color",
44        }
45    }
46}
47
48/// Input properties
49#[derive(Props, Clone, PartialEq)]
50pub struct InputProps {
51    /// Current value
52    #[props(default)]
53    pub value: String,
54    /// Placeholder text
55    #[props(default)]
56    pub placeholder: Option<String>,
57    /// Input type
58    #[props(default)]
59    pub input_type: InputType,
60    /// Disabled state
61    #[props(default)]
62    pub disabled: bool,
63    /// Read-only state
64    #[props(default)]
65    pub readonly: bool,
66    /// Required field
67    #[props(default)]
68    pub required: bool,
69    /// Autofocus on mount
70    #[props(default)]
71    pub autofocus: bool,
72    /// Change handler
73    #[props(default)]
74    pub onchange: Option<EventHandler<String>>,
75    /// Focus handler
76    #[props(default)]
77    pub onfocus: Option<EventHandler<FocusEvent>>,
78    /// Blur handler
79    #[props(default)]
80    pub onblur: Option<EventHandler<FocusEvent>>,
81    /// Input handler (real-time)
82    #[props(default)]
83    pub oninput: Option<EventHandler<FormEvent>>,
84    /// Custom inline styles
85    #[props(default)]
86    pub style: Option<String>,
87    /// Custom class name
88    #[props(default)]
89    pub class: Option<String>,
90    /// Input name attribute
91    #[props(default)]
92    pub name: Option<String>,
93    /// Input id attribute
94    #[props(default)]
95    pub id: Option<String>,
96}
97
98/// Input atom component
99///
100/// # Example
101/// ```rust,ignore
102/// use dioxus_ui_system::atoms::Input;
103///
104/// let mut value = use_signal(|| String::new());
105///
106/// rsx! {
107///     Input {
108///         value: value(),
109///         placeholder: "Enter text...",
110///         onchange: move |v| value.set(v),
111///     }
112/// }
113/// ```
114#[component]
115pub fn Input(props: InputProps) -> Element {
116    let disabled = props.disabled;
117    let readonly = props.readonly;
118
119    // Interactive states
120    let mut is_focused = use_signal(|| false);
121    let mut is_hovered = use_signal(|| false);
122
123    // Memoized styles
124    let style = use_style(move |t| {
125        let base = Style::new()
126            .flex()
127            .w_full()
128            .h_px(40)
129            .rounded(&t.radius, "md")
130            .border(
131                1,
132                if is_focused() {
133                    &t.colors.ring
134                } else {
135                    &t.colors.border
136                },
137            )
138            .bg(&t.colors.background)
139            .text_color(&t.colors.foreground)
140            .px(&t.spacing, "md")
141            .text(&t.typography, "sm")
142            .transition("all 150ms cubic-bezier(0.4, 0, 0.2, 1)")
143            .outline("none");
144
145        // Disabled state
146        let base = if disabled || readonly {
147            base.cursor("not-allowed").opacity(0.5).bg(&t.colors.muted)
148        } else {
149            base.cursor("text").opacity(1.0)
150        };
151
152        // Focus ring effect
153        let base = if is_focused() && !disabled {
154            Style {
155                box_shadow: Some(format!("0 0 0 1px {}", t.colors.ring.to_rgba())),
156                ..base
157            }
158        } else {
159            base
160        };
161
162        // Hover effect (only when not focused)
163        let base = if is_hovered() && !is_focused() && !disabled {
164            base.border_color(&t.colors.foreground.darken(0.2))
165        } else {
166            base
167        };
168
169        base.build()
170    });
171
172    // Combine with custom styles
173    let final_style = if let Some(custom) = &props.style {
174        format!("{} {}", style(), custom)
175    } else {
176        style()
177    };
178
179    let class = props.class.clone().unwrap_or_default();
180    let input_type = props.input_type.clone();
181    let placeholder = props.placeholder.clone();
182    let name = props.name.clone();
183    let id = props.id.clone();
184    let value = props.value.clone();
185    let required = props.required;
186    let autofocus = props.autofocus;
187    let readonly = props.readonly;
188    let disabled = props.disabled;
189
190    rsx! {
191        input {
192            r#type: input_type.as_str(),
193            style: "{final_style}",
194            class: "{class}",
195            value: "{value}",
196            placeholder: placeholder,
197            name: name,
198            id: id,
199            required: required,
200            autofocus: autofocus,
201            readonly: readonly,
202            disabled: disabled,
203            onmouseenter: move |_| is_hovered.set(true),
204            onmouseleave: move |_| is_hovered.set(false),
205            onfocus: move |e| {
206                is_focused.set(true);
207                if let Some(handler) = &props.onfocus {
208                    handler.call(e);
209                }
210            },
211            onblur: move |e| {
212                is_focused.set(false);
213                if let Some(handler) = &props.onblur {
214                    handler.call(e);
215                }
216            },
217            oninput: move |e| {
218                if let Some(handler) = &props.oninput {
219                    handler.call(e);
220                }
221            },
222            onchange: move |e| {
223                if let Some(handler) = &props.onchange {
224                    handler.call(e.value());
225                }
226            },
227        }
228    }
229}
230
231// Note: outline method is now in the main Style builder