1use crate::prelude::*;
2use crate::Label;
3use crate::LabelColor;
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,
63 cx.theme().colors().ghost_element_hover,
64 cx.theme().colors().ghost_element_active,
65 ),
66 InputVariant::Filled => (
67 cx.theme().colors().element,
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 LabelColor::Disabled
75 } else {
76 LabelColor::Placeholder
77 });
78
79 let label = Label::new(self.value.clone()).color(if self.disabled {
80 LabelColor::Disabled
81 } else {
82 LabelColor::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(
98 div()
99 .flex()
100 .items_center()
101 .text_sm()
102 .when(self.value.is_empty(), |this| this.child(placeholder_label))
103 .when(!self.value.is_empty(), |this| this.child(label)),
104 )
105 }
106}
107
108#[cfg(feature = "stories")]
109pub use stories::*;
110
111#[cfg(feature = "stories")]
112mod stories {
113 use super::*;
114 use crate::Story;
115 use gpui2::{Div, Render};
116
117 pub struct InputStory;
118
119 impl Render for InputStory {
120 type Element = Div<Self>;
121
122 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
123 Story::container(cx)
124 .child(Story::title_for::<_, Input>(cx))
125 .child(Story::label(cx, "Default"))
126 .child(div().flex().child(Input::new("Search")))
127 }
128 }
129}