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