commit
9d96ce7925
@ -1,8 +1,16 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::components::base_async_select::IntoAsyncSelectListItem;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Company {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
}
|
||||
|
||||
impl IntoAsyncSelectListItem for Company {
|
||||
fn to_select_list_item(&self) -> String {
|
||||
format!("{}", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,97 @@
|
||||
use serde::Deserialize;
|
||||
use sycamore::prelude::*;
|
||||
|
||||
pub trait IntoAsyncSelectListItem {
|
||||
fn to_select_list_item(&self) -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AsyncSelectRx<T>
|
||||
where
|
||||
T: 'static + PartialEq + Clone + IntoAsyncSelectListItem,
|
||||
{
|
||||
pub selected_item: Signal<Option<T>>,
|
||||
}
|
||||
|
||||
#[component(BaseAsyncSelect<G>)]
|
||||
pub fn create_component<T>(AsyncSelectRx { selected_item }: AsyncSelectRx<T>) -> View<G>
|
||||
where
|
||||
T: 'static + PartialEq + Clone + IntoAsyncSelectListItem,
|
||||
for<'de> T: Deserialize<'de>,
|
||||
{
|
||||
let input = Signal::new("".to_string());
|
||||
|
||||
let visible = Signal::new(false);
|
||||
let hide_dropdown = cloned!((visible, selected_item, input) => move |_| {
|
||||
visible.set(false);
|
||||
if selected_item.get().is_none() {
|
||||
input.set("".to_string());
|
||||
}
|
||||
});
|
||||
let item_list: Signal<Vec<T>> = Signal::new(vec![]);
|
||||
let selected = Signal::new(false);
|
||||
create_effect(
|
||||
cloned!((input, visible, item_list, selected, selected_item) => move || {
|
||||
// Early return if:
|
||||
// - The input is empty, there is nothing to search for nor to show
|
||||
// - We just selected an item
|
||||
if input.get().is_empty() || *selected.get_untracked() {
|
||||
selected.set(false);
|
||||
visible.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
selected_item.set(None);
|
||||
let url = "http://localhost:8000/v1/company/";
|
||||
if G::IS_BROWSER {
|
||||
perseus::spawn_local(
|
||||
cloned!((input, visible, item_list) => async move {
|
||||
let res = reqwasm::http::Request::get(
|
||||
&format!( "{}{}?limit={}", url, input.get(), 5)
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<Vec<T>>()
|
||||
.await
|
||||
.unwrap();
|
||||
visible.set(!res.is_empty());
|
||||
item_list.set(res);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let input2 = input.clone();
|
||||
let visible2 = visible.clone();
|
||||
|
||||
view! {
|
||||
input (bind:value=input, class="p-2 w-full rounded-md bg-slate-300 dark:bg-slate-800", on:blur=hide_dropdown) {}
|
||||
div (class="relative") {
|
||||
div (class=format!("absolute -top-1 w-80 rounded-b-md dark:bg-slate-800 bg-slate-300 {}", if *visible.get() { "visible" } else { "collapse" })) {
|
||||
ul {
|
||||
Indexed(IndexedProps {
|
||||
iterable: item_list.handle(),
|
||||
template: move |x| {
|
||||
view! {
|
||||
li (
|
||||
class="w-full p-2 cursor-pointer dark:hover:bg-slate-900 hover:bg-slate-400",
|
||||
on:mousedown=cloned!((x, input2, selected_item, visible2, selected) => move |_| {
|
||||
selected.set(true);
|
||||
selected_item.set(Some(x.clone()));
|
||||
input2.set(x.clone().to_select_list_item());
|
||||
visible2.set(false);
|
||||
}),
|
||||
)
|
||||
{
|
||||
(x.to_select_list_item())
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
use sycamore::prelude::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BaseButtonStateRx {
|
||||
pub label: ReadSignal<String>,
|
||||
pub disabled: ReadSignal<bool>,
|
||||
pub clicked: Signal<bool>,
|
||||
}
|
||||
|
||||
#[component(BaseButton<G>)]
|
||||
pub fn create_component(
|
||||
BaseButtonStateRx {
|
||||
label,
|
||||
disabled,
|
||||
clicked,
|
||||
}: BaseButtonStateRx,
|
||||
) -> View<G> {
|
||||
let click_event = cloned!((clicked) => move |_| { clicked.set(true) });
|
||||
|
||||
view! {
|
||||
button (
|
||||
class="my-2 z-0 p-2 bg-slate-300 dark:bg-slate-800 hover:bg-slate-400 dark:hover:bg-slate-900 disabled:cursor-not-allowed hover:cursor-pointer rounded-md ",
|
||||
disabled=*disabled.get(),
|
||||
on:click=click_event,
|
||||
) {
|
||||
(label.get())
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue