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 placement = create_rw_signal(DrawerPlacement::Top);
let open = Callback::new(move |new_placement: DrawerPlacement| { let open = Callback::new(move |new_placement: DrawerPlacement| {
show.set(true);
placement.set(new_placement); placement.set(new_placement);
show.set(true);
}); });
view! { view! {
@ -28,9 +28,9 @@ view! {
let show = create_rw_signal(false); let show = create_rw_signal(false);
view! { 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> <Button on_click=move |_| show.set(true)>"Open"</Button>
<Drawer show mount=DrawerMount::None> <Drawer show mount=DrawerMount::None width="50%">
"Current position" "Current position"
</Drawer> </Drawer>
</div> </div>

View file

@ -6,6 +6,7 @@ pub fn CSSTransition<T, CF, IV>(
node_ref: NodeRef<T>, node_ref: NodeRef<T>,
#[prop(into)] show: MaybeSignal<bool>, #[prop(into)] show: MaybeSignal<bool>,
#[prop(into)] name: MaybeSignal<String>, #[prop(into)] name: MaybeSignal<String>,
#[prop(optional, into)] on_after_leave: Option<Callback<()>>,
children: CF, children: CF,
) -> impl IntoView ) -> impl IntoView
where where
@ -30,6 +31,9 @@ where
RemoveClassName::Leave(active, to) => { RemoveClassName::Leave(active, to) => {
let _ = class_list.remove_2(&active, &to); let _ = class_list.remove_2(&active, &to);
display.set(Some("display: none;")); 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; right: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
pointer-events: none;
}
.thaw-drawer-container > * {
pointer-events: auto;
} }
.thaw-drawer-mask { .thaw-drawer-mask {
@ -62,3 +67,117 @@
bottom: 0; bottom: 0;
right: 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::{ use crate::{
components::Teleport, components::{CSSTransition, Teleport},
utils::{class_list::class_list, mount_style, Model, OptionalProp}, utils::{class_list::class_list, mount_style, use_lock_html_scroll, Model, OptionalProp},
Card, Card,
}; };
use leptos::*; use leptos::*;
@ -18,11 +18,14 @@ pub fn Drawer(
children: Children, children: Children,
) -> impl IntoView { ) -> impl IntoView {
mount_style("drawer", include_str!("./drawer.css")); mount_style("drawer", include_str!("./drawer.css"));
let css_vars = create_memo(move |_| { let style = create_memo(move |_| {
let mut css_vars = String::new(); let mut style = String::new();
css_vars.push_str(&format!("--thaw-width: {};", width.get()));
css_vars.push_str(&format!("--thaw-height: {};", height.get())); style.push_str(&format!("--thaw-width: {};", width.get()));
css_vars style.push_str(&format!("--thaw-height: {};", height.get()));
style.push_str(&format!("z-index: {};", z_index.get()));
style
}); });
#[component] #[component]
@ -30,38 +33,71 @@ pub fn Drawer(
show: Model<bool>, show: Model<bool>,
title: OptionalProp<MaybeSignal<String>>, title: OptionalProp<MaybeSignal<String>>,
placement: MaybeSignal<DrawerPlacement>, placement: MaybeSignal<DrawerPlacement>,
z_index: MaybeSignal<i16>,
class: OptionalProp<MaybeSignal<String>>, class: OptionalProp<MaybeSignal<String>>,
css_vars: Memo<String>, style: Memo<String>,
children: Children, children: Children,
) -> impl IntoView { ) -> impl IntoView {
let mask_ref = NodeRef::<html::Div>::new();
let drawer_ref = NodeRef::<html::Div>::new();
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! { view! {
<div <div class="thaw-drawer-container" style=move || style.get()>
class="thaw-drawer-container" <CSSTransition
style=move || if show.get() { format!("z-index: {}", z_index.get()) } else { "display: none".to_string() } node_ref=mask_ref
show=show.signal()
name="fade-in-transition"
let:display
>
<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="thaw-drawer-mask" on:click=move |_| show.set(false)></div>
<div <div
class=class_list![ class=class_list![
"thaw-drawer", move || format!("thaw-drawer--placement-{}", placement.get() "thaw-drawer", move || format!("thaw-drawer--placement-{}", placement
.as_str()), class.map(| c | move || c.get()) .get().as_str()), class.map(| c | move || c.get())
] ]
style=move || css_vars.get() style=move || display.get()
ref=drawer_ref
role="dialog"
aria-modal="true"
> >
<Card title>{children()}</Card> <Card title>{children()}</Card>
</div> </div>
</CSSTransition>
</div> </div>
} }
} }
match mount { match mount {
DrawerMount::None => view! { DrawerMount::None => view! { <DrawerInnr show title placement class style children/> },
<DrawerInnr show title placement z_index class css_vars children />
},
DrawerMount::Body => view! { DrawerMount::Body => view! {
<Teleport> <Teleport>
<DrawerInnr show title placement z_index class css_vars children /> <DrawerInnr show title placement class style children/>
</Teleport> </Teleport>
}, },
} }
@ -76,6 +112,8 @@ pub enum DrawerPlacement {
Right, Right,
} }
impl Copy for DrawerPlacement {}
impl DrawerPlacement { impl DrawerPlacement {
pub fn as_str(&self) -> &'static str { pub fn as_str(&self) -> &'static str {
match self { match self {

View file

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