vertigo_forms/
search_panel.rs1use std::rc::Rc;
2use vertigo::{AttrGroup, AutoMap, DomNode, Resource, ToComputed, Value, bind, component, dom};
3
4pub trait SearchResult {
5 fn is_empty(&self) -> bool;
6}
7
8impl<T> SearchResult for Vec<T> {
9 fn is_empty(&self) -> bool {
10 self.is_empty()
11 }
12}
13
14#[derive(Clone)]
15pub struct SearchPanelParams {
16 pub min_chars: usize,
17 pub prompt: String,
18 pub hint: String,
19 pub loading_text: String,
20 pub empty_text: String,
21}
22
23impl Default for SearchPanelParams {
24 fn default() -> Self {
25 Self {
26 min_chars: 3,
27 prompt: "Search: ".to_string(),
28 hint: "Enter at least {min_chars} letters".to_string(),
29 loading_text: "Loading...".to_string(),
30 empty_text: "No results".to_string(),
31 }
32 }
33}
34
35#[component]
37pub fn SearchPanel<T, K>(
38 query: Value<String>,
39 cache: AutoMap<String, K>,
40 render_results: Rc<dyn Fn(Rc<T>) -> DomNode>,
41 params: SearchPanelParams,
42 c: AttrGroup,
44 i: AttrGroup,
46 pc: AttrGroup,
48 ei: AttrGroup,
50) where
51 T: SearchResult + PartialEq + Clone + 'static,
52 K: ToComputed<Resource<Rc<T>>> + Clone + 'static,
53{
54 let prompt = params.prompt.clone();
55 let content = query.render_value(move |query| {
56 let SearchPanelParams {
57 min_chars,
58 prompt: _,
59 hint,
60 loading_text,
61 empty_text,
62 } = params.clone();
63 if query.len() < min_chars {
64 let msg = hint.replace("{min_chars}", &min_chars.to_string());
65 return dom! { <div>{msg}</div> };
66 }
67 let render_results = render_results.clone();
68 let (pc, ei) = (pc.clone(), ei.clone());
69 let content = cache
70 .get(&query)
71 .to_computed()
72 .render_value(move |items| match items {
73 Resource::Loading => dom! { <div>{loading_text.clone()}</div> },
74 Resource::Ready(dataset) => {
75 if !dataset.is_empty() {
76 render_results(dataset)
77 } else {
78 let ei = ei.clone();
79 dom! {
80 <div {..ei}>{empty_text.clone()}</div>
81 }
82 }
83 }
84 Resource::Error(err) => {
85 dom! { <div>{err}</div> }
86 }
87 });
88 dom! { <div {..pc}>{content}</div> }
89 });
90
91 let on_input = bind!(query, |new_value: String| {
92 query.set(new_value);
93 });
94
95 let value = query.to_computed();
96
97 dom! {
98 <div {..c}>
99 { prompt }
100 <input {value} on_input={on_input} {..i} />
101 { content }
102 </div>
103 }
104}