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, _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 .h_7()
86 .w_full()
87 .px_2()
88 .border()
89 .border_color(border_color_default)
90 .fill(background_color_default)
91 .hover(|style| {
92 style
93 .border_color(border_color_hover)
94 .fill(background_color_active)
95 })
96 // .active(|a| .border_color(border_color_active))
97 .flex()
98 .items_center()
99 .child(
100 div()
101 .flex()
102 .items_center()
103 .text_sm()
104 .text_color(text_color)
105 .child(text_el)
106 .child(div().text_color(theme.players[0].cursor).child("|")),
107 )
108 }
109}
110
111#[cfg(feature = "stories")]
112pub use stories::*;
113
114#[cfg(feature = "stories")]
115mod stories {
116 use crate::Story;
117
118 use super::*;
119
120 #[derive(Element)]
121 pub struct InputStory<S: 'static + Send + Sync + Clone> {
122 state_type: PhantomData<S>,
123 }
124
125 impl<S: 'static + Send + Sync + Clone> InputStory<S> {
126 pub fn new() -> Self {
127 Self {
128 state_type: PhantomData,
129 }
130 }
131
132 fn render(
133 &mut self,
134 _view: &mut S,
135 cx: &mut ViewContext<S>,
136 ) -> impl Element<ViewState = S> {
137 Story::container(cx)
138 .child(Story::title_for::<_, Input<S>>(cx))
139 .child(Story::label(cx, "Default"))
140 .child(div().flex().child(Input::new("Search")))
141 }
142 }
143}