use leptos::prelude::*;
use utils::prelude::ThreadSafe;
use utils_leptos::signal::URwSignal;
pub trait TabName
where
Self: Sized,
{
fn name(&self) -> &'static str;
fn html_id(&self) -> &'static str;
fn try_from_name(name: &str) -> Option<Self>;
}
pub trait Tab<O: PartialEq>: ThreadSafe {
fn id(&self) -> O;
fn view(&self) -> AnyView;
}
#[component]
pub fn TabPanel<S, O>(
id: S,
tabs: Vec<Box<dyn Tab<O>>>,
selector: URwSignal<O>,
) -> impl IntoView
where
S: ToString,
O: ThreadSafe + Clone + PartialEq + TabName,
{
let tab_selector = selector.map(
|v| v.name().to_string(),
|v, new_name| {
if let Some(new_value) = O::try_from_name(&new_name) {
*v = new_value;
}
},
);
let tabpanel_id = id.to_string();
let buttons = tabs
.iter()
.map(|tab| {
let tabpanel_id = tabpanel_id.clone();
let id = tab.id();
view! {
<TabButton id selector=tab_selector tabpanel_id/>
}
})
.collect_view();
let views = tabs
.into_iter()
.map(|tab| {
view! {
<TabView tab selector />
}
})
.collect_view();
view! {
<div id={tabpanel_id} class="leptos-forge-tab-panel overflow-auto">
<div class="">
{ buttons }
</div>
<div>
{ views }
</div>
</div>
}
}
#[component]
fn TabButton<S, O>(
tabpanel_id: S,
id: O,
selector: URwSignal<String>,
) -> impl IntoView
where
S: ToString,
O: ThreadSafe + Clone + TabName,
{
let tab_button_id = format!("{}-{}", tabpanel_id.to_string(), id.html_id());
let name = id.name();
view! {
<div class="inline-block">
<input id={tab_button_id.clone()} class="peer hidden" name={tabpanel_id.to_string()} value={name} type="radio" bind:group=selector />
<label for=tab_button_id class="px-2 py-1 text-sm bg-forgegray-300 peer-checked:bg-forgeblue-300">{ name }</label>
</div>
}
}
#[component]
fn TabView<O>(
tab: Box<dyn Tab<O>>,
#[prop(into)]
selector: URwSignal<O>,
) -> impl IntoView
where
O: ThreadSafe + Clone + PartialEq,
{
let id = tab.id();
view! {
<Show when= move ||{ selector.get() == id }>
{ tab.view() }
</Show>
}
}