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