Feat/drawer transition (#97)

* feat: drawer adds CSSTransition

* fix: drawer scrollbar
This commit is contained in:
luoxiaozero 2024-02-06 13:36:47 +08:00
parent e361d86c4b
commit 70553b8069
6 changed files with 232 additions and 30 deletions

View file

@ -5,8 +5,8 @@ let show = create_rw_signal(false);
let placement = create_rw_signal(DrawerPlacement::Top);
let open = Callback::new(move |new_placement: DrawerPlacement| {
show.set(true);
placement.set(new_placement);
show.set(true);
});
view! {
@ -28,9 +28,9 @@ view! {
let show = create_rw_signal(false);
view! {
<div style="position: relative; height: 200px; background-color: #0078ff88;">
<div style="position: relative; overflow: hidden; height: 200px; background-color: #0078ff88;">
<Button on_click=move |_| show.set(true)>"Open"</Button>
<Drawer show mount=DrawerMount::None>
<Drawer show mount=DrawerMount::None width="50%">
"Current position"
</Drawer>
</div>

View file

@ -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_after_leave: Option<Callback<()>>,
children: CF,
) -> impl IntoView
where
@ -30,6 +31,9 @@ where
RemoveClassName::Leave(active, to) => {
let _ = class_list.remove_2(&active, &to);
display.set(Some("display: none;"));
if let Some(on_after_leave) = on_after_leave {
on_after_leave.call(());
}
}
}
}

View file

@ -4,6 +4,11 @@
right: 0;
left: 0;
bottom: 0;
pointer-events: none;
}
.thaw-drawer-container > * {
pointer-events: auto;
}
.thaw-drawer-mask {
@ -62,3 +67,117 @@
bottom: 0;
right: 0;
}
.thaw-drawer.slide-in-from-right-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-drawer.slide-in-from-right-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-drawer.slide-in-from-right-transition-enter-to {
transform: translateX(0);
}
.thaw-drawer.slide-in-from-right-transition-enter-from {
transform: translateX(100%);
}
.thaw-drawer.slide-in-from-right-transition-leave-from {
transform: translateX(0);
}
.thaw-drawer.slide-in-from-right-transition-leave-to {
transform: translateX(100%);
}
.thaw-drawer.slide-in-from-left-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-drawer.slide-in-from-left-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-drawer.slide-in-from-left-transition-enter-to {
transform: translateX(0);
}
.thaw-drawer.slide-in-from-left-transition-enter-from {
transform: translateX(-100%);
}
.thaw-drawer.slide-in-from-left-transition-leave-from {
transform: translateX(0);
}
.thaw-drawer.slide-in-from-left-transition-leave-to {
transform: translateX(-100%);
}
.thaw-drawer.slide-in-from-top-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-drawer.slide-in-from-top-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-drawer.slide-in-from-top-transition-enter-to {
transform: translateY(0);
}
.thaw-drawer.slide-in-from-top-transition-enter-from {
transform: translateY(-100%);
}
.thaw-drawer.slide-in-from-top-transition-leave-from {
transform: translateY(0);
}
.thaw-drawer.slide-in-from-top-transition-leave-to {
transform: translateY(-100%);
}
.thaw-drawer.slide-in-from-bottom-transition-leave-active {
transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-drawer.slide-in-from-bottom-transition-enter-active {
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-drawer.slide-in-from-bottom-transition-enter-to {
transform: translateY(0);
}
.thaw-drawer.slide-in-from-bottom-transition-enter-from {
transform: translateY(100%);
}
.thaw-drawer.slide-in-from-bottom-transition-leave-from {
transform: translateY(0);
}
.thaw-drawer.slide-in-from-bottom-transition-leave-to {
transform: translateY(100%);
}
.thaw-drawer-mask.fade-in-transition-enter-active {
transition: all 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.thaw-drawer-mask.fade-in-transition-leave-active {
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
}
.thaw-drawer-mask.fade-in-transition-enter-from,
.thaw-drawer-mask.fade-in-transition-leave-to {
opacity: 0;
}
.thaw-drawer-mask.fade-in-transition-leave-from,
.thaw-drawer-mask.fade-in-transition-enter-to {
opacity: 1;
}

View file

@ -1,6 +1,6 @@
use crate::{
components::Teleport,
utils::{class_list::class_list, mount_style, Model, OptionalProp},
components::{CSSTransition, Teleport},
utils::{class_list::class_list, mount_style, use_lock_html_scroll, Model, OptionalProp},
Card,
};
use leptos::*;
@ -18,11 +18,14 @@ pub fn Drawer(
children: Children,
) -> impl IntoView {
mount_style("drawer", include_str!("./drawer.css"));
let css_vars = create_memo(move |_| {
let mut css_vars = String::new();
css_vars.push_str(&format!("--thaw-width: {};", width.get()));
css_vars.push_str(&format!("--thaw-height: {};", height.get()));
css_vars
let style = create_memo(move |_| {
let mut style = String::new();
style.push_str(&format!("--thaw-width: {};", width.get()));
style.push_str(&format!("--thaw-height: {};", height.get()));
style.push_str(&format!("z-index: {};", z_index.get()));
style
});
#[component]
@ -30,38 +33,71 @@ pub fn Drawer(
show: Model<bool>,
title: OptionalProp<MaybeSignal<String>>,
placement: MaybeSignal<DrawerPlacement>,
z_index: MaybeSignal<i16>,
class: OptionalProp<MaybeSignal<String>>,
css_vars: Memo<String>,
style: Memo<String>,
children: Children,
) -> impl IntoView {
view! {
<div
class="thaw-drawer-container"
style=move || if show.get() { format!("z-index: {}", z_index.get()) } else { "display: none".to_string() }
>
<div class="thaw-drawer-mask" on:click=move |_| show.set(false)></div>
<div
class=class_list![
"thaw-drawer", move || format!("thaw-drawer--placement-{}", placement.get()
.as_str()), class.map(| c | move || c.get())
]
let mask_ref = NodeRef::<html::Div>::new();
let drawer_ref = NodeRef::<html::Div>::new();
style=move || css_vars.get()
let is_lock = RwSignal::new(show.get_untracked());
Effect::new(move |_| {
if show.get() {
is_lock.set(true);
}
});
use_lock_html_scroll(is_lock.into());
let on_after_leave = move |_| {
is_lock.set(false);
};
view! {
<div class="thaw-drawer-container" style=move || style.get()>
<CSSTransition
node_ref=mask_ref
show=show.signal()
name="fade-in-transition"
let:display
>
<Card title>{children()}</Card>
</div>
<div
class="thaw-drawer-mask"
style=move || display.get()
on:click=move |_| show.set(false)
ref=mask_ref
></div>
</CSSTransition>
<CSSTransition
node_ref=drawer_ref
show=show.signal()
name=Memo::new(move |_| {
format!("slide-in-from-{}-transition", placement.get().as_str())
})
on_after_leave
let:display
>
<div
class=class_list![
"thaw-drawer", move || format!("thaw-drawer--placement-{}", placement
.get().as_str()), class.map(| c | move || c.get())
]
style=move || display.get()
ref=drawer_ref
role="dialog"
aria-modal="true"
>
<Card title>{children()}</Card>
</div>
</CSSTransition>
</div>
}
}
match mount {
DrawerMount::None => view! {
<DrawerInnr show title placement z_index class css_vars children />
},
DrawerMount::None => view! { <DrawerInnr show title placement class style children/> },
DrawerMount::Body => view! {
<Teleport>
<DrawerInnr show title placement z_index class css_vars children />
<DrawerInnr show title placement class style children/>
</Teleport>
},
}
@ -76,6 +112,8 @@ pub enum DrawerPlacement {
Right,
}
impl Copy for DrawerPlacement {}
impl DrawerPlacement {
pub fn as_str(&self) -> &'static str {
match self {

View file

@ -8,6 +8,7 @@ mod optional_prop;
mod signal;
mod stored_maybe_signal;
mod time;
mod use_lock_html_scroll;
// pub use callback::AsyncCallback;
pub use component_ref::{create_component_ref, ComponentRef};
@ -18,6 +19,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_lock_html_scroll::*;
pub(crate) fn with_hydration_off<T>(f: impl FnOnce() -> T) -> T {
#[cfg(feature = "hydrate")]

View file

@ -0,0 +1,39 @@
use leptos::MaybeSignal;
pub fn use_lock_html_scroll(is_lock: MaybeSignal<bool>) {
#[cfg(any(feature = "csr", feature = "hydrate"))]
{
use leptos::{create_render_effect, document, on_cleanup, SignalGet, StoredValue};
let style_el = StoredValue::new(None::<web_sys::Element>);
let remove_style_el = move || {
style_el.update_value(move |el| {
if let Some(el) = el.take() {
let head = document().head().expect("head no exist");
_ = head.remove_child(&el);
}
});
};
create_render_effect(move |_| {
if is_lock.get() {
let head = document().head().expect("head no exist");
let style = document()
.create_element("style")
.expect("create style element error");
_ = style.set_attribute("data-id", &format!("thaw-lock-html-scroll"));
style.set_text_content(Some("html { overflow: hidden; }"));
_ = head.append_child(&style);
style_el.set_value(Some(style));
} else {
remove_style_el();
}
});
on_cleanup(remove_style_el)
}
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
{
_ = is_lock;
}
}