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<ViewState = S> {
64 let color = ThemeColor::new(cx);
65 let system_color = SystemColor::new();
66
67 let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
68 InputVariant::Ghost => (
69 color.ghost_element,
70 color.ghost_element_hover,
71 color.ghost_element_active,
72 ),
73 InputVariant::Filled => (
74 color.filled_element,
75 color.filled_element_hover,
76 color.filled_element_active,
77 ),
78 };
79
80 let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled {
81 LabelColor::Disabled
82 } else {
83 LabelColor::Placeholder
84 });
85
86 let label = Label::new(self.value.clone()).color(if self.disabled {
87 LabelColor::Disabled
88 } else {
89 LabelColor::Default
90 });
91
92 div()
93 .id("input")
94 .h_7()
95 .w_full()
96 .px_2()
97 .border()
98 .border_color(system_color.transparent)
99 .bg(input_bg)
100 .hover(|style| style.bg(input_hover_bg))
101 .active(|style| style.bg(input_active_bg))
102 .flex()
103 .items_center()
104 .child(
105 div()
106 .flex()
107 .items_center()
108 .text_sm()
109 .when(self.value.is_empty(), |this| this.child(placeholder_label))
110 .when(!self.value.is_empty(), |this| this.child(label)),
111 )
112 }
113}
114
115#[cfg(feature = "stories")]
116pub use stories::*;
117
118#[cfg(feature = "stories")]
119mod stories {
120 use crate::Story;
121
122 use super::*;
123
124 #[derive(Element)]
125 pub struct InputStory<S: 'static + Send + Sync> {
126 state_type: PhantomData<S>,
127 }
128
129 impl<S: 'static + Send + Sync> InputStory<S> {
130 pub fn new() -> Self {
131 Self {
132 state_type: PhantomData,
133 }
134 }
135
136 fn render(
137 &mut self,
138 _view: &mut S,
139 cx: &mut ViewContext<S>,
140 ) -> impl Element<ViewState = S> {
141 Story::container(cx)
142 .child(Story::title_for::<_, Input<S>>(cx))
143 .child(Story::label(cx, "Default"))
144 .child(div().flex().child(Input::new("Search")))
145 }
146 }
147}