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: &'static str,
17 value: String,
18 state: InteractionState,
19 variant: InputVariant,
20}
21
22impl<S: 'static + Send + Sync> Input<S> {
23 pub fn new(placeholder: &'static str) -> Self {
24 Self {
25 state_type: PhantomData,
26 placeholder,
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, cx: &mut ViewContext<S>) -> impl Element<State = 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 mut border_color_active = theme.middle.base.pressed.border;
59 let border_color_focus = theme.middle.base.pressed.background;
60
61 match self.variant {
62 InputVariant::Ghost => {
63 background_color_default = theme.middle.base.default.background;
64 background_color_active = theme.middle.base.active.background;
65 }
66 InputVariant::Filled => {
67 background_color_default = theme.middle.on.default.background;
68 background_color_active = theme.middle.on.active.background;
69 }
70 };
71
72 if self.state == InteractionState::Focused {
73 border_color_default = theme.players[0].cursor;
74 border_color_hover = theme.players[0].cursor;
75 border_color_active = theme.players[0].cursor;
76 }
77
78 if self.state == InteractionState::Focused || self.state == InteractionState::Active {
79 text_el = self.value.clone();
80 text_color = theme.lowest.base.default.foreground;
81 } else {
82 text_el = self.placeholder.to_string().clone();
83 text_color = theme.lowest.base.disabled.foreground;
84 }
85
86 div()
87 .h_7()
88 .w_full()
89 .px_2()
90 .border()
91 .border_color(border_color_default)
92 .fill(background_color_default)
93 .hover()
94 .border_color(border_color_hover)
95 // .active()
96 // .border_color(border_color_active)
97 .fill(background_color_active)
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(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
134 Story::container(cx)
135 .child(Story::title_for::<_, Input<S>>(cx))
136 .child(Story::label(cx, "Default"))
137 .child(div().flex().child(Input::new("Search")))
138 }
139 }
140}