1use std::marker::PhantomData;
2
3use crate::prelude::*;
4use crate::theme;
5
6#[derive(Default, PartialEq)]
7pub enum InputVariant {
8 #[default]
9 Ghost,
10 Filled,
11}
12
13#[derive(Element)]
14pub struct Input<S: 'static + Send + Sync> {
15 state_type: PhantomData<S>,
16 placeholder: SharedString,
17 value: String,
18 state: InteractionState,
19 variant: InputVariant,
20}
21
22impl<S: 'static + Send + Sync> Input<S> {
23 pub fn new(placeholder: impl Into<SharedString>) -> Self {
24 Self {
25 state_type: PhantomData,
26 placeholder: placeholder.into(),
27 value: "".to_string(),
28 state: InteractionState::default(),
29 variant: InputVariant::default(),
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 fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
49 let theme = theme(cx);
50
51 let text_el;
52 let text_color;
53 let background_color_default;
54 let background_color_active;
55
56 let mut border_color_default = theme.middle.base.default.border;
57 let mut border_color_hover = theme.middle.base.hovered.border;
58 let border_color_focus = theme.middle.base.pressed.background;
59
60 match self.variant {
61 InputVariant::Ghost => {
62 background_color_default = theme.middle.base.default.background;
63 background_color_active = theme.middle.base.active.background;
64 }
65 InputVariant::Filled => {
66 background_color_default = theme.middle.on.default.background;
67 background_color_active = theme.middle.on.active.background;
68 }
69 };
70
71 if self.state == InteractionState::Focused {
72 border_color_default = theme.players[0].cursor;
73 border_color_hover = theme.players[0].cursor;
74 }
75
76 if self.state == InteractionState::Focused || self.state == InteractionState::Active {
77 text_el = self.value.clone();
78 text_color = theme.lowest.base.default.foreground;
79 } else {
80 text_el = self.placeholder.to_string().clone();
81 text_color = theme.lowest.base.disabled.foreground;
82 }
83
84 div()
85 .id("input")
86 .h_7()
87 .w_full()
88 .px_2()
89 .border()
90 .border_color(border_color_default)
91 .bg(background_color_default)
92 .hover(|style| {
93 style
94 .border_color(border_color_hover)
95 .bg(background_color_active)
96 })
97 .active(|style| style.border_color(theme.middle.base.active.border))
98 .flex()
99 .items_center()
100 .child(
101 div()
102 .flex()
103 .items_center()
104 .text_sm()
105 .text_color(text_color)
106 .child(text_el)
107 .child(div().text_color(theme.players[0].cursor).child("|")),
108 )
109 }
110}
111
112#[cfg(feature = "stories")]
113pub use stories::*;
114
115#[cfg(feature = "stories")]
116mod stories {
117 use crate::Story;
118
119 use super::*;
120
121 #[derive(Element)]
122 pub struct InputStory<S: 'static + Send + Sync + Clone> {
123 state_type: PhantomData<S>,
124 }
125
126 impl<S: 'static + Send + Sync + Clone> InputStory<S> {
127 pub fn new() -> Self {
128 Self {
129 state_type: PhantomData,
130 }
131 }
132
133 fn render(
134 &mut self,
135 _view: &mut S,
136 cx: &mut ViewContext<S>,
137 ) -> impl Element<ViewState = S> {
138 Story::container(cx)
139 .child(Story::title_for::<_, Input<S>>(cx))
140 .child(Story::label(cx, "Default"))
141 .child(div().flex().child(Input::new("Search")))
142 }
143 }
144}