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 dioxus::prelude::*;
6use crate::theme::use_style;
7use crate::styles::Style;
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(1, if is_focused() { &t.colors.ring } else { &t.colors.border })
131            .bg(&t.colors.background)
132            .text_color(&t.colors.foreground)
133            .px(&t.spacing, "md")
134            .text(&t.typography, "sm")
135            .transition("all 150ms cubic-bezier(0.4, 0, 0.2, 1)")
136            .outline("none");
137            
138        // Disabled state
139        let base = if disabled || readonly {
140            base.cursor("not-allowed")
141                .opacity(0.5)
142                .bg(&t.colors.muted)
143        } else {
144            base.cursor("text")
145                .opacity(1.0)
146        };
147        
148        // Focus ring effect
149        let base = if is_focused() && !disabled {
150            Style {
151                box_shadow: Some(format!("0 0 0 1px {}", t.colors.ring.to_rgba())),
152                ..base
153            }
154        } else {
155            base
156        };
157        
158        // Hover effect (only when not focused)
159        let base = if is_hovered() && !is_focused() && !disabled {
160            base.border_color(&t.colors.foreground.darken(0.2))
161        } else {
162            base
163        };
164        
165        base.build()
166    });
167    
168    // Combine with custom styles
169    let final_style = if let Some(custom) = &props.style {
170        format!("{} {}", style(), custom)
171    } else {
172        style()
173    };
174    
175    let class = props.class.clone().unwrap_or_default();
176    let input_type = props.input_type.clone();
177    let placeholder = props.placeholder.clone();
178    let name = props.name.clone();
179    let id = props.id.clone();
180    let value = props.value.clone();
181    let required = props.required;
182    let autofocus = props.autofocus;
183    let readonly = props.readonly;
184    let disabled = props.disabled;
185    
186    rsx! {
187        input {
188            r#type: input_type.as_str(),
189            style: "{final_style}",
190            class: "{class}",
191            value: "{value}",
192            placeholder: placeholder,
193            name: name,
194            id: id,
195            required: required,
196            autofocus: autofocus,
197            readonly: readonly,
198            disabled: disabled,
199            onmouseenter: move |_| is_hovered.set(true),
200            onmouseleave: move |_| is_hovered.set(false),
201            onfocus: move |e| {
202                is_focused.set(true);
203                if let Some(handler) = &props.onfocus {
204                    handler.call(e);
205                }
206            },
207            onblur: move |e| {
208                is_focused.set(false);
209                if let Some(handler) = &props.onblur {
210                    handler.call(e);
211                }
212            },
213            oninput: move |e| {
214                if let Some(handler) = &props.oninput {
215                    handler.call(e);
216                }
217            },
218            onchange: move |e| {
219                if let Some(handler) = &props.onchange {
220                    handler.call(e.value());
221                }
222            },
223        }
224    }
225}
226
227// Note: outline method is now in the main Style builder