1use std::marker::PhantomData;
2
3use crate::prelude::*;
4use crate::Label;
5use crate::LabelColor;
6
7#[derive(Default, PartialEq)]
8pub enum InputVariant {
9 #[default]
10 Ghost,
11 Filled,
12}
13
14#[derive(Element)]
15pub struct Input<S: 'static + Send + Sync> {
16 state_type: PhantomData<S>,
17 placeholder: SharedString,
18 value: String,
19 state: InteractionState,
20 variant: InputVariant,
21 disabled: bool,
22 is_active: bool,
23}
24
25impl<S: 'static + Send + Sync> Input<S> {
26 pub fn new(placeholder: impl Into<SharedString>) -> Self {
27 Self {
28 state_type: PhantomData,
29 placeholder: placeholder.into(),
30 value: "".to_string(),
31 state: InteractionState::default(),
32 variant: InputVariant::default(),
33 disabled: false,
34 is_active: false,
35 }
36 }
37
38 pub fn value(mut self, value: String) -> Self {
39 self.value = value;
40 self
41 }
42
43 pub fn state(mut self, state: InteractionState) -> Self {
44 self.state = state;
45 self
46 }
47
48 pub fn variant(mut self, variant: InputVariant) -> Self {
49 self.variant = variant;
50 self
51 }
52
53 pub fn disabled(mut self, disabled: bool) -> Self {
54 self.disabled = disabled;
55 self
56 }
57
58 pub fn is_active(mut self, is_active: bool) -> Self {
59 self.is_active = is_active;
60 self
61 }
62
63 fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<S> {
64 let theme = theme(cx);
65
66 let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
67 InputVariant::Ghost => (
68 theme.ghost_element,
69 theme.ghost_element_hover,
70 theme.ghost_element_active,
71 ),
72 InputVariant::Filled => (
73 theme.filled_element,
74 theme.filled_element_hover,
75 theme.filled_element_active,
76 ),
77 };
78
79 let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled {
80 LabelColor::Disabled
81 } else {
82 LabelColor::Placeholder
83 });
84
85 let label = Label::new(self.value.clone()).color(if self.disabled {
86 LabelColor::Disabled
87 } else {
88 LabelColor::Default
89 });
90
91 div()
92 .id("input")
93 .h_7()
94 .w_full()
95 .px_2()
96 .border()
97 .border_color(theme.transparent)
98 .bg(input_bg)
99 .hover(|style| style.bg(input_hover_bg))
100 .active(|style| style.bg(input_active_bg))
101 .flex()
102 .items_center()
103 .child(
104 div()
105 .flex()
106 .items_center()
107 .text_sm()
108 .when(self.value.is_empty(), |this| this.child(placeholder_label))
109 .when(!self.value.is_empty(), |this| this.child(label)),
110 )
111 }
112}
113
114#[cfg(feature = "stories")]
115pub use stories::*;
116
117#[cfg(feature = "stories")]
118mod stories {
119 use crate::Story;
120
121 use super::*;
122
123 #[derive(Element)]
124 pub struct InputStory<S: 'static + Send + Sync> {
125 state_type: PhantomData<S>,
126 }
127
128 impl<S: 'static + Send + Sync> InputStory<S> {
129 pub fn new() -> Self {
130 Self {
131 state_type: PhantomData,
132 }
133 }
134
135 fn render(
136 &mut self,
137 _view: &mut S,
138 cx: &mut ViewContext<S>,
139 ) -> impl Element<S> {
140 Story::container(cx)
141 .child(Story::title_for::<_, Input<S>>(cx))
142 .child(Story::label(cx, "Default"))
143 .child(div().flex().child(Input::new("Search")))
144 }
145 }
146}