input.rs

  1use std::marker::PhantomData;
  2
  3use crate::prelude::*;
  4use crate::Label;
  5use crate::LabelColor;
  6
  7#[derive(Default, PartialEq)]
  8pub enum InputVariant {
  9    #[default]
 10    Ghost,
 11    Filled,
 12}
 13
 14#[derive(Element)]
 15pub struct Input<S: 'static + Send + Sync> {
 16    state_type: PhantomData<S>,
 17    placeholder: SharedString,
 18    value: String,
 19    state: InteractionState,
 20    variant: InputVariant,
 21    disabled: bool,
 22    is_active: bool,
 23}
 24
 25impl<S: 'static + Send + Sync> Input<S> {
 26    pub fn new(placeholder: impl Into<SharedString>) -> Self {
 27        Self {
 28            state_type: PhantomData,
 29            placeholder: placeholder.into(),
 30            value: "".to_string(),
 31            state: InteractionState::default(),
 32            variant: InputVariant::default(),
 33            disabled: false,
 34            is_active: false,
 35        }
 36    }
 37
 38    pub fn value(mut self, value: String) -> Self {
 39        self.value = value;
 40        self
 41    }
 42
 43    pub fn state(mut self, state: InteractionState) -> Self {
 44        self.state = state;
 45        self
 46    }
 47
 48    pub fn variant(mut self, variant: InputVariant) -> Self {
 49        self.variant = variant;
 50        self
 51    }
 52
 53    pub fn disabled(mut self, disabled: bool) -> Self {
 54        self.disabled = disabled;
 55        self
 56    }
 57
 58    pub fn is_active(mut self, is_active: bool) -> Self {
 59        self.is_active = is_active;
 60        self
 61    }
 62
 63    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<S> {
 64        let theme = theme(cx);
 65
 66        let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
 67            InputVariant::Ghost => (
 68                theme.ghost_element,
 69                theme.ghost_element_hover,
 70                theme.ghost_element_active,
 71            ),
 72            InputVariant::Filled => (
 73                theme.filled_element,
 74                theme.filled_element_hover,
 75                theme.filled_element_active,
 76            ),
 77        };
 78
 79        let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled {
 80            LabelColor::Disabled
 81        } else {
 82            LabelColor::Placeholder
 83        });
 84
 85        let label = Label::new(self.value.clone()).color(if self.disabled {
 86            LabelColor::Disabled
 87        } else {
 88            LabelColor::Default
 89        });
 90
 91        div()
 92            .id("input")
 93            .h_7()
 94            .w_full()
 95            .px_2()
 96            .border()
 97            .border_color(theme.transparent)
 98            .bg(input_bg)
 99            .hover(|style| style.bg(input_hover_bg))
100            .active(|style| style.bg(input_active_bg))
101            .flex()
102            .items_center()
103            .child(
104                div()
105                    .flex()
106                    .items_center()
107                    .text_sm()
108                    .when(self.value.is_empty(), |this| this.child(placeholder_label))
109                    .when(!self.value.is_empty(), |this| this.child(label)),
110            )
111    }
112}
113
114#[cfg(feature = "stories")]
115pub use stories::*;
116
117#[cfg(feature = "stories")]
118mod stories {
119    use crate::Story;
120
121    use super::*;
122
123    #[derive(Element)]
124    pub struct InputStory<S: 'static + Send + Sync> {
125        state_type: PhantomData<S>,
126    }
127
128    impl<S: 'static + Send + Sync> InputStory<S> {
129        pub fn new() -> Self {
130            Self {
131                state_type: PhantomData,
132            }
133        }
134
135        fn render(
136            &mut self,
137            _view: &mut S,
138            cx: &mut ViewContext<S>,
139        ) -> impl Element<S> {
140            Story::container(cx)
141                .child(Story::title_for::<_, Input<S>>(cx))
142                .child(Story::label(cx, "Default"))
143                .child(div().flex().child(Input::new("Search")))
144        }
145    }
146}