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<ViewState = S> {
 64        let color = ThemeColor::new(cx);
 65        let system_color = SystemColor::new();
 66
 67        let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
 68            InputVariant::Ghost => (
 69                color.ghost_element,
 70                color.ghost_element_hover,
 71                color.ghost_element_active,
 72            ),
 73            InputVariant::Filled => (
 74                color.filled_element,
 75                color.filled_element_hover,
 76                color.filled_element_active,
 77            ),
 78        };
 79
 80        let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled {
 81            LabelColor::Disabled
 82        } else {
 83            LabelColor::Placeholder
 84        });
 85
 86        let label = Label::new(self.value.clone()).color(if self.disabled {
 87            LabelColor::Disabled
 88        } else {
 89            LabelColor::Default
 90        });
 91
 92        div()
 93            .id("input")
 94            .h_7()
 95            .w_full()
 96            .px_2()
 97            .border()
 98            .border_color(system_color.transparent)
 99            .bg(input_bg)
100            .hover(|style| style.bg(input_hover_bg))
101            .active(|style| style.bg(input_active_bg))
102            .flex()
103            .items_center()
104            .child(
105                div()
106                    .flex()
107                    .items_center()
108                    .text_sm()
109                    .when(self.value.is_empty(), |this| this.child(placeholder_label))
110                    .when(!self.value.is_empty(), |this| this.child(label)),
111            )
112    }
113}
114
115#[cfg(feature = "stories")]
116pub use stories::*;
117
118#[cfg(feature = "stories")]
119mod stories {
120    use crate::Story;
121
122    use super::*;
123
124    #[derive(Element)]
125    pub struct InputStory<S: 'static + Send + Sync> {
126        state_type: PhantomData<S>,
127    }
128
129    impl<S: 'static + Send + Sync> InputStory<S> {
130        pub fn new() -> Self {
131            Self {
132                state_type: PhantomData,
133            }
134        }
135
136        fn render(
137            &mut self,
138            _view: &mut S,
139            cx: &mut ViewContext<S>,
140        ) -> impl Element<ViewState = S> {
141            Story::container(cx)
142                .child(Story::title_for::<_, Input<S>>(cx))
143                .child(Story::label(cx, "Default"))
144                .child(div().flex().child(Input::new("Search")))
145        }
146    }
147}