use leptos::prelude::*;
use leptos::text_prop::TextProp;
use leptos::wasm_bindgen::JsCast;
use lepticons::LucideGlyph;
use crate::IconPicker;
#[component]
pub fn IconPickerPopover(
#[prop(into)]
selected: Signal<Option<LucideGlyph>>,
on_select: Callback<LucideGlyph>,
children: Children,
#[prop(into, optional)]
class: Option<TextProp>,
#[prop(default = true)]
close_on_select: bool,
#[prop(into, optional)]
width: Option<TextProp>,
#[prop(into, optional)]
height: Option<TextProp>,
#[prop(into, optional)]
aria_label: Option<TextProp>,
) -> impl IntoView {
let width = width.unwrap_or_else(|| "480px".into());
let height = height.unwrap_or_else(|| "400px".into());
let aria_label = aria_label.unwrap_or_else(|| "Choose an icon".into());
let (open, set_open) = signal(false);
let stored_width: RwSignal<String> = RwSignal::new(width.get().to_string());
let stored_height: RwSignal<String> = RwSignal::new(height.get().to_string());
let panel_ref: NodeRef<leptos::html::Div> = NodeRef::new();
let trigger_ref: NodeRef<leptos::html::Div> = NodeRef::new();
let capture_panel_size = move || {
if let Some(el) = panel_ref.get() {
let w = el.offset_width();
let h = el.offset_height();
if w > 0 {
stored_width.set(format!("{}px", w));
}
if h > 0 {
stored_height.set(format!("{}px", h));
}
}
};
let close = move || {
capture_panel_size();
set_open.set(false);
};
let toggle_open = move |ev: web_sys::MouseEvent| {
ev.stop_propagation();
if open.get() {
close();
} else {
set_open.set(true);
}
};
let keydown_handle = window_event_listener(
leptos::ev::keydown,
move |ev: leptos::ev::KeyboardEvent| {
if ev.key() == "Escape" && open.get_untracked() {
close();
}
},
);
let mousedown_handle = window_event_listener(
leptos::ev::mousedown,
move |ev: web_sys::MouseEvent| {
if !open.get_untracked() {
return;
}
let Some(target) = ev.target() else { return; };
let Ok(node) = target.dyn_into::<web_sys::Node>() else {
return;
};
let inside_panel = panel_ref
.get()
.map(|el| el.contains(Some(&node)))
.unwrap_or(false);
let inside_trigger = trigger_ref
.get()
.map(|el| el.contains(Some(&node)))
.unwrap_or(false);
if !inside_panel && !inside_trigger {
close();
}
},
);
on_cleanup(move || {
keydown_handle.remove();
mousedown_handle.remove();
});
let wrapped_on_select = Callback::new(move |glyph: LucideGlyph| {
on_select.run(glyph);
if close_on_select {
close();
}
});
let class_stored = StoredValue::new(class);
let aria_label_stored = StoredValue::new(aria_label);
let panel_style = move || {
format!(
"position:absolute;top:100%;left:0;z-index:50;\
width:{};height:{};\
margin-top:0.25rem;\
box-shadow:0 4px 6px -1px rgba(0,0,0,0.1),0 2px 4px -2px rgba(0,0,0,0.1)",
stored_width.get(),
stored_height.get()
)
};
view! {
<div style="position:relative;display:inline-block">
<div node_ref=trigger_ref on:click=toggle_open style="cursor:pointer">
{children()}
</div>
{move || open.get().then(|| view! {
<div node_ref=panel_ref
style=panel_style
class=move || class_stored.with_value(|c| c.as_ref().map(|c| c.get().to_string()).unwrap_or_default())
role="dialog"
aria-modal="true"
aria-label=move || aria_label_stored.with_value(|a| a.get().to_string())>
<IconPicker
selected=selected
on_select=wrapped_on_select
max_height=move || stored_height.get()
/>
</div>
})}
</div>
}
}