input.rs

  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}