input.rs

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