@ -1,8 +1,16 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::components::base_async_select::IntoAsyncSelectListItem;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Company {
|
pub struct Company {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub slug: 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