use leptodon_proc_macros::generate_docs;
use leptos::children::Children;
use leptos::logging::debug_log;
use leptos::logging::error;
use leptos::prelude::AriaAttributes;
use leptos::prelude::ClassAttribute;
use leptos::prelude::Effect;
use leptos::prelude::ElementChild;
use leptos::prelude::Get;
use leptos::prelude::GlobalAttributes;
use leptos::prelude::IntoAny;
use leptos::prelude::OnAttribute;
use leptos::prelude::RwSignal;
use leptos::prelude::Set;
use leptos::slot;
use leptos::{IntoView, component, prelude::MaybeProp, view};
use leptos_use::UseTimeoutFnReturn;
use leptos_use::use_timeout_fn;
use web_sys::KeyboardEvent;
use crate::button::ButtonRef;
use crate::button::ControlButton;
use crate::class_list;
use crate::icon::CloseIcon;
use crate::util::signals::ComponentRef;
const MODAL_CLASSES: &str =
"relative bg-white rounded-lg shadow dark:bg-gray-700 px-4 m-4 w-full max-w-2xl max-h-full";
const MODAL_BACKDROP_CLASSES: &str = "overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-[1000] justify-center items-center w-full md:inset-0 h-full max-h-full flex bg-black/50";
#[slot]
pub struct ModalFooterChildren {
children: Children,
}
#[generate_docs]
#[component]
pub fn Modal(
#[prop(optional, into)]
id: MaybeProp<String>,
#[prop(optional, into)]
class: MaybeProp<String>,
#[prop(optional, into)]
title: MaybeProp<String>,
#[prop(optional, into)]
visible: RwSignal<bool>,
children: Children,
footer: ModalFooterChildren,
) -> impl IntoView {
let first_button: ComponentRef<ButtonRef> = ComponentRef::new();
let warp_focus = move || {
let Some(first_button): Option<ButtonRef> = first_button.get_untracked() else {
error!("Internal modal first-div reference is not mounted!");
return;
};
let UseTimeoutFnReturn { start, .. } = use_timeout_fn(
move |_| {
first_button.focus();
},
0.0,
);
start(());
debug_log!("modal: successfully focused first-div");
};
Effect::watch(
move || visible.get(),
move |new, old, _| {
if *new && Some(new) != old {
warp_focus();
}
},
false,
);
view! {
<div tabindex="-1"
class=class_list!(
MODAL_BACKDROP_CLASSES,
("hidden", move || !visible.get())
)
on:click=move |_| visible.set(false)
on:keydown=move |key: KeyboardEvent| {
if key.code() == "Escape"{
visible.set(false);
}
}
>
<div
id=move || id.get()
class=class_list!(MODAL_CLASSES, class)
on:click=move |e| e.stop_propagation()
role="dialog"
aria-label=move || title.get()
aria-modal=true
>
<span tabindex="0" aria-hidden="true" on:focus=move |_| warp_focus()></span>
<div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
<h3 class="text-lg font-medium text-heading">
{ title.get() }
</h3>
<ControlButton
icon=CloseIcon()
comp_ref=first_button
on_click=move |_| visible.set(false)
/>
</div>
<div class="p-4 md:p-5 space-y-4">
{children()}
</div>
<div
class="flex items-center p-4 md:p-5 border-t border-gray-200 rounded-b dark:border-gray-600">
{(footer.children)().into_any()}
</div>
<span tabindex="0" on:focus=move |_| warp_focus() aria-hidden="true"></span>
</div>
</div>
}
}