mirror of
https://github.com/adoyle0/thaw.git
synced 2025-02-02 08:34:15 -05:00
feat: Modal adds mask_closeable and z_index prop
This commit is contained in:
parent
396e654f0b
commit
e62d156068
5 changed files with 198 additions and 39 deletions
|
@ -6,6 +6,7 @@ pub fn CSSTransition<T, CF, IV>(
|
|||
node_ref: NodeRef<T>,
|
||||
#[prop(into)] show: MaybeSignal<bool>,
|
||||
#[prop(into)] name: MaybeSignal<String>,
|
||||
#[prop(optional, into)] on_enter: Option<Callback<()>>,
|
||||
#[prop(optional, into)] on_after_leave: Option<Callback<()>>,
|
||||
children: CF,
|
||||
) -> impl IntoView
|
||||
|
@ -71,6 +72,9 @@ where
|
|||
let _ = class_list.add_1(&enter_to);
|
||||
remove_class_name
|
||||
.set_value(Some(RemoveClassName::Enter(enter_active, enter_to)));
|
||||
if let Some(on_enter) = on_enter {
|
||||
on_enter.call(());
|
||||
}
|
||||
});
|
||||
} else if !show && prev {
|
||||
let leave_from = format!("{name}-leave-from");
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use crate::icon::*;
|
||||
use crate::utils::Model;
|
||||
use crate::{
|
||||
components::{OptionComp, Teleport},
|
||||
utils::mount_style,
|
||||
Card, CardFooter, CardHeader, CardHeaderExtra,
|
||||
components::{CSSTransition, OptionComp, Teleport},
|
||||
utils::{mount_style, use_click_position, Model},
|
||||
Card, CardFooter, CardHeader, CardHeaderExtra, Icon,
|
||||
};
|
||||
use leptos::*;
|
||||
|
||||
|
@ -15,37 +13,99 @@ pub struct ModalFooter {
|
|||
#[component]
|
||||
pub fn Modal(
|
||||
#[prop(into)] show: Model<bool>,
|
||||
#[prop(default = true.into(), into)] mask_closeable: MaybeSignal<bool>,
|
||||
#[prop(default = 2000.into(), into)] z_index: MaybeSignal<i16>,
|
||||
#[prop(optional, into)] title: MaybeSignal<String>,
|
||||
children: Children,
|
||||
#[prop(optional)] modal_footer: Option<ModalFooter>,
|
||||
) -> impl IntoView {
|
||||
mount_style("modal", include_str!("./modal.css"));
|
||||
let on_mask_click = move |_| {
|
||||
if mask_closeable.get_untracked() {
|
||||
show.set(false);
|
||||
}
|
||||
};
|
||||
|
||||
let mask_ref = NodeRef::<html::Div>::new();
|
||||
let scroll_ref = NodeRef::<html::Div>::new();
|
||||
let modal_ref = NodeRef::<html::Div>::new();
|
||||
|
||||
let click_position = use_click_position();
|
||||
let on_enter = move |_| {
|
||||
let Some(position) = click_position.get_untracked() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(scroll_el) = scroll_ref.get_untracked() else {
|
||||
return;
|
||||
};
|
||||
let scroll_top = scroll_el.scroll_top();
|
||||
|
||||
let Some(modal_el) = modal_ref.get_untracked() else {
|
||||
return;
|
||||
};
|
||||
let x = -(modal_el.offset_left() - position.0);
|
||||
let y = -(modal_el.offset_top() - position.1 - scroll_top);
|
||||
|
||||
let _ = modal_el.attr("style", format!("transform-origin: {}px {}px", x, y));
|
||||
};
|
||||
|
||||
view! {
|
||||
<Teleport>
|
||||
<div
|
||||
class="thaw-modal-container"
|
||||
style=move || if show.get() { "" } else { "display: none" }
|
||||
style=move || format!("z-index: {};", z_index.get())
|
||||
>
|
||||
<div class="thaw-modal-mask"></div>
|
||||
<div class="thaw-modal-body">
|
||||
<Card>
|
||||
<CardHeader slot>
|
||||
<span class="thaw-model-title">{move || title.get()}</span>
|
||||
</CardHeader>
|
||||
<CardHeaderExtra slot>
|
||||
<span style="cursor: pointer;" on:click=move |_| show.set(false)>
|
||||
<Icon icon=icondata_ai::AiCloseOutlined/>
|
||||
</span>
|
||||
</CardHeaderExtra>
|
||||
{children()}
|
||||
<CardFooter slot if_=modal_footer.is_some()>
|
||||
<OptionComp value=modal_footer.as_ref() let:footer>
|
||||
{(footer.children)()}
|
||||
</OptionComp>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
<CSSTransition
|
||||
node_ref=mask_ref
|
||||
show=show.signal()
|
||||
name="fade-in-transition"
|
||||
let:display
|
||||
>
|
||||
<div
|
||||
class="thaw-modal-mask"
|
||||
style=move || display.get()
|
||||
on:click=on_mask_click
|
||||
ref=mask_ref
|
||||
></div>
|
||||
</CSSTransition>
|
||||
<CSSTransition
|
||||
node_ref=scroll_ref
|
||||
show=show.signal()
|
||||
name="fade-in-scale-up-transition"
|
||||
on_enter
|
||||
let:display
|
||||
>
|
||||
<div
|
||||
class="thaw-modal-scroll"
|
||||
style=move || display.get()
|
||||
ref=scroll_ref
|
||||
>
|
||||
<div
|
||||
class="thaw-modal-body"
|
||||
ref=modal_ref
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<Card>
|
||||
<CardHeader slot>
|
||||
<span class="thaw-model-title">{move || title.get()}</span>
|
||||
</CardHeader>
|
||||
<CardHeaderExtra slot>
|
||||
<span style="cursor: pointer;" on:click=move |_| show.set(false)>
|
||||
<Icon icon=icondata_ai::AiCloseOutlined/>
|
||||
</span>
|
||||
</CardHeaderExtra>
|
||||
{children()}
|
||||
<CardFooter slot if_=modal_footer.is_some()>
|
||||
<OptionComp value=modal_footer.as_ref() let:footer>
|
||||
{(footer.children)()}
|
||||
</OptionComp>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</CSSTransition>
|
||||
</div>
|
||||
</Teleport>
|
||||
}
|
||||
|
|
|
@ -1,6 +1,25 @@
|
|||
.thaw-modal-container {
|
||||
z-index: 2001;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
min-height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.thaw-modal-container > * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.thaw-modal-scroll {
|
||||
display: flex;
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.thaw-modal-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
@ -8,24 +27,56 @@
|
|||
left: 0;
|
||||
bottom: 0;
|
||||
background-color: #0007;
|
||||
z-index: 2000;
|
||||
}
|
||||
.thaw-modal-body {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
min-height: 100%;
|
||||
z-index: 2002;
|
||||
}
|
||||
|
||||
.thaw-modal-body .thaw-card {
|
||||
width: 600px;
|
||||
.thaw-modal-mask.fade-in-transition-enter-active {
|
||||
transition: all 0.25s cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.thaw-modal-mask.fade-in-transition-leave-active {
|
||||
transition: all 0.25s cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.thaw-modal-mask.fade-in-transition-enter-from,
|
||||
.thaw-modal-mask.fade-in-transition-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.thaw-modal-mask.fade-in-transition-leave-from,
|
||||
.thaw-modal-mask.fade-in-transition-enter-to {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.thaw-modal-body {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.thaw-model-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.fade-in-scale-up-transition-leave-active > .thaw-modal-body {
|
||||
transform-origin: inherit;
|
||||
transition: opacity 0.25s cubic-bezier(0.4, 0, 1, 1),
|
||||
transform 0.25s cubic-bezier(0.4, 0, 1, 1);
|
||||
}
|
||||
|
||||
.fade-in-scale-up-transition-enter-active > .thaw-modal-body {
|
||||
transform-origin: inherit;
|
||||
transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1),
|
||||
transform 0.25s cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.fade-in-scale-up-transition-enter-from > .thaw-modal-body,
|
||||
.fade-in-scale-up-transition-leave-to > .thaw-modal-body {
|
||||
opacity: 0;
|
||||
transform: scale(0.5);
|
||||
}
|
||||
|
||||
.fade-in-scale-up-transition-leave-from > .thaw-modal-body,
|
||||
.fade-in-scale-up-transition-enter-to > .thaw-modal-body {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ mod optional_prop;
|
|||
mod signal;
|
||||
mod stored_maybe_signal;
|
||||
mod time;
|
||||
mod use_click_position;
|
||||
mod use_lock_html_scroll;
|
||||
|
||||
// pub use callback::AsyncCallback;
|
||||
|
@ -19,6 +20,7 @@ pub(crate) use optional_prop::OptionalProp;
|
|||
pub use signal::SignalWatch;
|
||||
pub(crate) use stored_maybe_signal::*;
|
||||
pub(crate) use time::*;
|
||||
pub(crate) use use_click_position::*;
|
||||
pub(crate) use use_lock_html_scroll::*;
|
||||
|
||||
pub(crate) fn with_hydration_off<T>(f: impl FnOnce() -> T) -> T {
|
||||
|
|
42
thaw/src/utils/use_click_position.rs
Normal file
42
thaw/src/utils/use_click_position.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use leptos::{ev, on_cleanup, window_event_listener, ReadSignal, RwSignal, SignalSet};
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::MouseEvent;
|
||||
|
||||
fn click_handler(event: MouseEvent) -> Option<(i32, i32)> {
|
||||
if event.client_x() > 0 || event.client_y() > 0 {
|
||||
return Some((event.client_x(), event.client_y()));
|
||||
}
|
||||
let Some(target) = event.target() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Ok(target) = target.dyn_into::<web_sys::Element>() else {
|
||||
return None;
|
||||
};
|
||||
let rect = target.get_bounding_client_rect();
|
||||
let left = rect.left() as i32;
|
||||
let top = rect.top() as i32;
|
||||
let width = rect.width() as i32;
|
||||
let height = rect.height() as i32;
|
||||
if left > 0 || top > 0 {
|
||||
Some((left + width / 2, top + height / 2))
|
||||
} else {
|
||||
Some((0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_click_position() -> ReadSignal<Option<(i32, i32)>> {
|
||||
let mouse_position = RwSignal::new(None);
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
{
|
||||
let handle = window_event_listener(ev::click, move |event| {
|
||||
let position = click_handler(event);
|
||||
mouse_position.set(position);
|
||||
});
|
||||
on_cleanup(move || {
|
||||
handle.remove();
|
||||
});
|
||||
}
|
||||
|
||||
mouse_position.read_only()
|
||||
}
|
Loading…
Add table
Reference in a new issue