use std::time::Duration;
use crate::button::ButtonProps;
use crate::button::ButtonShape;
use crate::button::{Button, ButtonAppearance, ButtonRef, ButtonType};
use crate::class_list;
use crate::class_list::reactive_class::MaybeReactiveClass;
use crate::dialog::Dialog;
use crate::dropdown::AlignmentAnchor;
use crate::dropdown::Dropdown;
use crate::icon;
use crate::icon::icon_data::IconData;
use crate::icon::icon_data::IconRef;
use crate::modal::{Modal, ModalFooterChildren};
use crate::util::callback::BoxCallback;
use crate::util::callback::BoxOneCallback;
use crate::util::signals::ComponentRef;
use leptodon_proc_macros::generate_docs;
use leptos::prelude::ClassAttribute;
use leptos::prelude::IntoAny;
use leptos::prelude::Set;
use leptos::prelude::set_timeout;
use leptos::prelude::{Children, Get, MaybeProp, Signal, provide_context, signal};
use leptos::prelude::{ElementChild, RwSignal, Update};
use leptos::{IntoView, component, view};
use leptos::{ev, slot};
use web_sys::window;
#[component]
pub fn ControlButton(
#[prop(optional, into)]
id: MaybeProp<String>,
#[prop(optional, into)]
class: MaybeProp<String>,
#[prop(into)]
icon: IconRef,
#[prop(into)] on_click: BoxOneCallback<ev::MouseEvent>,
#[prop(optional)] comp_ref: ComponentRef<ButtonRef>,
) -> impl IntoView {
view! {
<Button
id
icon
on_click
comp_ref
appearance=ButtonAppearance::Transparent
class=class_list!["!px-3", class]
>
</Button>
}
}
#[component]
pub fn AddButton(
#[prop(default = BoxOneCallback::new(|_| ()), into)]
on_click: BoxOneCallback<ev::MouseEvent>,
) -> impl IntoView {
view! {
<Button
icon=icon::AddIcon()
on_click=on_click
appearance=ButtonAppearance::Primary
class="m-2"
{..}>
Add
</Button>
}
}
#[component]
pub fn EditButton(
#[prop(default = BoxOneCallback::new(|_| ()), into)]
on_click: BoxOneCallback<ev::MouseEvent>,
) -> impl IntoView {
view! {
<Button
icon=icon::EditIcon()
on_click=on_click
appearance=ButtonAppearance::Secondary
class="m-2"
{..}>
Edit
</Button>
}
}
#[component]
pub fn DeleteButton(
#[prop(default = BoxOneCallback::new(|_| ()), into)]
on_click: BoxOneCallback<ev::MouseEvent>,
) -> impl IntoView {
view! {
<Button
icon=icon::DeleteIcon()
on_click=on_click
appearance=ButtonAppearance::Danger
class="m-2"
{..}>
Delete
</Button>
}
}
#[component]
pub fn DownloadButton(
#[prop(default = BoxOneCallback::new(|_| ()), into)]
on_click: BoxOneCallback<ev::MouseEvent>,
) -> impl IntoView {
view! {
<Button
icon=icon::DownloadIcon()
on_click=on_click
appearance=ButtonAppearance::Secondary
class="m-2"
{..}>
Download
</Button>
}
}
#[generate_docs]
#[component]
pub fn CopyButton(
#[prop(optional, into)]
id: MaybeProp<String>,
#[prop(optional, into)]
class: MaybeReactiveClass,
#[prop(into)]
to_copy: Signal<String>,
) -> impl IntoView {
let btn_text = RwSignal::new("Copy");
let on_copy = move |_| {
let _ = window()
.expect("Window should be present")
.navigator()
.clipboard()
.write_text(&to_copy.get());
btn_text.set("Copied!");
set_timeout(
move || {
btn_text.set("Copy");
},
Duration::from_secs(2),
);
};
view! {
<Button
id
appearance=ButtonAppearance::Transparent
class=class_list!("bg-white dark:bg-black", class)
on_click=on_copy
>
{btn_text}
</Button>
}
}
impl From<&'static IconData> for MaybeProp<Signal<&'static IconData>> {
fn from(value: &'static IconData) -> Self {
MaybeProp::from(Signal::<&'static IconData>::from(value))
}
}
#[slot]
pub struct DropdownButtonChildren {
children: Children,
}
#[generate_docs]
#[component]
pub fn DropdownButton(
#[prop(optional, into)]
id: MaybeProp<String>,
#[prop(optional, into)]
class: MaybeProp<String>,
#[prop(default = AlignmentAnchor::default(), into)]
alignment: AlignmentAnchor,
#[prop(optional, into)]
appearance: Signal<ButtonAppearance>,
#[prop(optional, into)]
button_type: ButtonType,
#[prop(default = ButtonShape::default(), into)]
shape: ButtonShape,
#[prop(optional, into)]
icon: MaybeProp<Signal<IconRef>>,
#[prop(optional, into)]
loading: Signal<bool>,
#[prop(optional, default = true, into)]
should_autoclose: bool,
button_children: DropdownButtonChildren,
children: Children,
#[prop(optional)]
comp_ref: ComponentRef<ButtonRef>,
) -> impl IntoView
where
{
let (is_visible, set_visible) = signal(false);
let button = Button(ButtonProps {
id,
class: class.into(),
appearance,
button_type,
shape,
icon,
loading,
on_click: Some(BoxOneCallback::new(move |_e| {
set_visible.update(|setter| *setter = !*setter);
})),
children: Some(button_children.children),
comp_ref,
});
provide_context::<crate::dropdown::SetVisibleCallback>(set_visible);
provide_context::<crate::dropdown::AutoClose>(should_autoclose);
let dropdown_id = id.get().map(|id| format!("{id}-modal"));
view! {
<div class="w-fit relative">
{button}
<Dropdown id=dropdown_id is_visible alignment>
{children().into_any()}
</Dropdown>
</div>
}
}
#[slot]
pub struct ModalButtonChildren {
children: Children,
}
#[generate_docs]
#[component]
pub fn ModalButton(
#[prop(optional, into)]
id: MaybeProp<String>,
#[prop(optional, into)]
class: MaybeProp<String>,
#[prop(optional, into)]
appearance: Signal<ButtonAppearance>,
#[prop(optional, into)]
button_type: ButtonType,
#[prop(default = ButtonShape::default(), into)]
shape: ButtonShape,
#[prop(optional, into)]
icon: MaybeProp<Signal<IconRef>>,
#[prop(optional, into)]
loading: Signal<bool>,
button_children: ModalButtonChildren,
#[prop(optional)]
comp_ref: ComponentRef<ButtonRef>,
#[prop(optional, into)]
modal_title: MaybeProp<String>,
#[prop(default = RwSignal::new(false), into)]
modal_visible: RwSignal<bool>,
children: Children,
modal_footer: ModalFooterChildren,
) -> impl IntoView
where
{
let button = Button(ButtonProps {
id,
class: class.into(),
appearance,
button_type,
shape,
icon,
loading,
on_click: Some(BoxOneCallback::new(move |_e| {
modal_visible.update(|inner_visible| *inner_visible = !*inner_visible);
})),
children: Some(button_children.children),
comp_ref,
});
let modal_id = id.get().map(|id| format!("{id}-modal"));
view! {
<div>
{button}
<Modal id=modal_id title=modal_title visible=modal_visible>
{children()}
<ModalFooterChildren slot:footer>
{(modal_footer.children)()}
</ModalFooterChildren>
</Modal>
</div>
}
}
#[slot]
pub struct DialogButtonChildren {
children: Children,
}
#[generate_docs]
#[component]
pub fn DialogButton(
#[prop(optional, into)]
id: MaybeProp<String>,
#[prop(optional, into)]
class: MaybeProp<String>,
#[prop(optional, into)]
appearance: Signal<ButtonAppearance>,
#[prop(optional, into)]
button_type: ButtonType,
#[prop(default = ButtonShape::default(), into)]
shape: ButtonShape,
#[prop(optional, into)]
icon: MaybeProp<Signal<IconRef>>,
#[prop(optional, into)]
loading: Signal<bool>,
#[prop(optional)]
comp_ref: ComponentRef<ButtonRef>,
button_children: DialogButtonChildren,
#[prop(optional, into)]
dialog_title: MaybeProp<String>,
#[prop(default = RwSignal::new(false), into)]
dialog_visible: RwSignal<bool>,
#[prop(default = "Ok".into(), into)]
primary_text: String,
#[prop(default = BoxCallback::new(|| ()), into)]
on_click_primary: BoxCallback,
#[prop(default = "Cancel".into(), into)]
secondary_text: String,
#[prop(default = BoxCallback::new(|| ()), into)]
on_click_secondary: BoxCallback,
children: Children,
) -> impl IntoView
where
{
let button = Button(ButtonProps {
id,
class: class.into(),
appearance,
button_type,
shape,
icon,
loading,
on_click: Some(BoxOneCallback::new(move |_e| {
dialog_visible.update(|inner_visible| *inner_visible = !*inner_visible);
})),
children: Some(button_children.children),
comp_ref,
});
let dialog_id = id.get().map(|id| format!("{id}-dialog"));
view! {
<div>
{button}
<Dialog id=dialog_id title=dialog_title visible=dialog_visible primary_text secondary_text on_click_primary on_click_secondary>
{children()}
</Dialog>
</div>
}
}