Skip to main content

vertigo_forms/select/
multi_select.rs

1use vertigo::{
2    Computed, Css, DomNode, Value, bind, bind_rc, computed_tuple, css, dom, render::render_list,
3    transaction,
4};
5
6/// Select component based on vector of `T` values,
7/// which allows to have multiple options selected at once.
8///
9/// Example:
10/// ```
11/// use vertigo::{DomNode, dom, Value};
12/// use vertigo_forms::MultiSelect;
13///
14/// let value = Value::new(vec!["foo".to_string()]);
15/// let options = Value::new(
16///     vec![
17///         "foo".to_string(),
18///         "bar".to_string(),
19///         "baz".to_string(),
20///     ]
21/// );
22///
23/// dom! {
24///     <MultiSelect
25///         value={value.clone()}
26///         options={options}
27///     />
28/// };
29/// ```
30pub struct MultiSelect<T: Clone + PartialEq + 'static> {
31    pub value: Value<Vec<T>>,
32    pub options: Computed<Vec<T>>,
33}
34
35impl<T> MultiSelect<T>
36where
37    T: Clone + From<String> + PartialEq + ToString + 'static,
38{
39    pub fn into_component(self) -> Self {
40        self
41    }
42
43    pub fn mount(&self) -> DomNode {
44        let Self { value, options } = self;
45        let toggle = bind_rc!(value, |item: &T| {
46            value.change(|value| {
47                if let Some(idx) = value.iter().position(|i| i == item) {
48                    value.remove(idx);
49                } else {
50                    value.push(item.clone());
51                }
52            });
53        });
54
55        let list = render_list(options, |item| item.to_string(), {
56            let toggle = toggle.clone();
57            let value = value.clone();
58            move |item: &Computed<T>| {
59                let item = item.clone();
60                let text_item = item.map(|item| item.to_string());
61
62                let on_click = bind!(toggle, item, |_| transaction(|ctx| toggle(&item.get(ctx))));
63
64                let css = computed_tuple!(item, value).map(|(item, value)| {
65                    if value.contains(&item) {
66                        css! {"
67                            border-style: inset;
68                            font-weight: bold;
69                            color: green;
70                        "}
71                    } else {
72                        Css::default()
73                    }
74                });
75
76                dom! {
77                    <button {css} {on_click}>{text_item}</button>
78                }
79            }
80        });
81
82        let list_css = css! {"
83            display: flex;
84            flex-wrap: wrap;
85        "};
86
87        dom! {
88            <div css={list_css}>
89                {list}
90            </div>
91        }
92    }
93}