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 theme = theme(cx);
61
62 let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
63 InputVariant::Ghost => (
64 theme.ghost_element,
65 theme.ghost_element_hover,
66 theme.ghost_element_active,
67 ),
68 InputVariant::Filled => (
69 theme.filled_element,
70 theme.filled_element_hover,
71 theme.filled_element_active,
72 ),
73 };
74
75 let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled {
76 LabelColor::Disabled
77 } else {
78 LabelColor::Placeholder
79 });
80
81 let label = Label::new(self.value.clone()).color(if self.disabled {
82 LabelColor::Disabled
83 } else {
84 LabelColor::Default
85 });
86
87 div()
88 .id("input")
89 .h_7()
90 .w_full()
91 .px_2()
92 .border()
93 .border_color(theme.transparent)
94 .bg(input_bg)
95 .hover(|style| style.bg(input_hover_bg))
96 .active(|style| style.bg(input_active_bg))
97 .flex()
98 .items_center()
99 .child(
100 div()
101 .flex()
102 .items_center()
103 .text_sm()
104 .when(self.value.is_empty(), |this| this.child(placeholder_label))
105 .when(!self.value.is_empty(), |this| this.child(label)),
106 )
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 gpui2::{Div, Render};
118
119 pub struct InputStory;
120
121 impl Render 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}