input.rs

  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}