leptos_components/input/
search.rs

1use components_core::{BASE_CLASS, concat};
2use leptos::prelude::*;
3use leptos::{IntoView, component, view};
4
5use crate::icons::{Filter as FilterIcon, Search as SearchIcon};
6use crate::tag::Tag;
7
8#[derive(Clone, Debug)]
9pub struct Filter {
10    pub label: String,
11    pub value: String,
12}
13
14#[component]
15pub fn InputSearch(
16    #[prop(into, optional)] filters: Option<Vec<Filter>>,
17    #[prop(into, optional)] active_filters: Vec<Filter>,
18    #[prop(into)] on_change_filter: Callback<(Vec<Filter>,), ()>,
19) -> impl IntoView {
20    let (filter_modal, set_filter_modal) = signal(false);
21    let has_filter = filters.as_ref().map_or(false, |f| !f.is_empty());
22
23    let handle_select_filter = {
24        let active_filters = active_filters.clone();
25
26        move |filter: Filter, is_selected: bool| {
27            if !is_selected {
28                let mut new_filters = active_filters.clone();
29                new_filters.push(filter);
30                on_change_filter.run((new_filters,));
31            } else {
32                let new_filters = active_filters
33                    .iter()
34                    .filter(|f| f.value != filter.value)
35                    .cloned()
36                    .collect();
37                on_change_filter.run((new_filters,));
38            }
39        }
40    };
41
42    let handle_close_on_click_input = move || {
43        if has_filter {
44            set_filter_modal.set(false);
45        }
46    };
47
48    view! {
49        <div class=concat!(BASE_CLASS, "-input-search-container")>
50            <label
51                class=crate::tw!(
52                    concat!(BASE_CLASS, "-input-search"),
53                    has_filter.then_some(concat!(BASE_CLASS, "-input-search--filter"))
54                )
55            >
56                <SearchIcon size=24 />
57                <input
58                    type="text"
59                    placeholder="Buscar"
60                    on:click=move |_| handle_close_on_click_input()
61                    class="text-caption"
62                />
63            </label>
64            <div class=concat!(BASE_CLASS, "-input-search__filter")>
65                {has_filter.then(|| view! {
66                    <button on:click=move |_| set_filter_modal.update(|v| *v = !*v) tabindex="0">
67                        <FilterIcon size=24 />
68                    </button>
69                })}
70                <div
71                    class=crate::tw!(
72                        concat!(BASE_CLASS, "-input-search-backdrop__content"),
73                        filter_modal.get().then_some(concat!(BASE_CLASS, "-input-search-backdrop__content--open"))
74                            .unwrap_or(concat!(BASE_CLASS, "-input-search-backdrop__content--closed"))
75                    )
76                >
77                    {filter_modal.get().then(|| {
78                        view! {
79                        <ul class=concat!(BASE_CLASS, "-input-search-backdrop__list")>
80                            {filters.map(|filters| filters.iter().map(|filter| {
81                                let is_selected = active_filters.iter().any(|f| f.value == filter.value);
82                                let filter = filter.clone();
83                                let handle_select_filter = handle_select_filter.clone();
84                                view! {
85                                    <li
86                                        on:click=move |_| handle_select_filter(filter.clone(), is_selected)
87                                    >
88                                        <Tag
89                                            selected=is_selected
90                                            label=filter.label.clone()
91                                        />
92                                    </li>
93                                }
94                            }).collect::<Vec<_>>()).unwrap_or_default()}
95                        </ul>
96                    }})}
97                </div>
98            </div>
99        </div>
100    }
101}