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