From 4aeab59e6ffa136e90eef89a0153eb424b3c1964 Mon Sep 17 00:00:00 2001 From: luoxiaozero <48741584+luoxiaozero@users.noreply.github.com> Date: Thu, 11 Jan 2024 11:00:39 +0800 Subject: [PATCH] Feat/css transition (#75) * feat: add CSSTransition component * fix: CSSTransition transitionend * feat: collapse item style * fix: CSSTransition display --- thaw/src/collapse/collapse.css | 26 +++++++ thaw/src/collapse/collapse_item.rs | 18 +++-- thaw/src/components/css_transition/mod.rs | 95 +++++++++++++++++++++++ thaw/src/components/mod.rs | 2 + 4 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 thaw/src/components/css_transition/mod.rs diff --git a/thaw/src/collapse/collapse.css b/thaw/src/collapse/collapse.css index bfe6d1b..7dca169 100644 --- a/thaw/src/collapse/collapse.css +++ b/thaw/src/collapse/collapse.css @@ -27,3 +27,29 @@ .thaw-collapse-item__content { padding-top: 16px; } + +.thaw-collapse-item-enter-from, +.thaw-collapse-item-enter-to { + opacity: 1; +} + +.thaw-collapse-item-leave-to, +.thaw-collapse-item-enter-from { + opacity: 0; + padding-top: 0; + max-height: 0; +} + +.thaw-collapse-item-leave-active { + overflow: hidden; + transition: max-height 0.15s cubic-bezier(0.4, 0, 0.2, 1) 0s, + opacity 0.15s cubic-bezier(0, 0, 0.2, 1) 0s, + padding-top 0.15s cubic-bezier(0.4, 0, 0.2, 1) 0s; +} + +.thaw-collapse-item-enter-active { + overflow: hidden; + transition: max-height 0.15s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.15s cubic-bezier(0.4, 0, 1, 1), + padding-top 0.15s cubic-bezier(0.4, 0, 0.2, 1); +} diff --git a/thaw/src/collapse/collapse_item.rs b/thaw/src/collapse/collapse_item.rs index a73715e..f39725c 100644 --- a/thaw/src/collapse/collapse_item.rs +++ b/thaw/src/collapse/collapse_item.rs @@ -1,5 +1,6 @@ use super::use_collapse; use crate::{ + components::CSSTransition, utils::{class_list::class_list, StoredMaybeSignal}, Icon, }; @@ -15,6 +16,8 @@ pub fn CollapseItem( ) -> impl IntoView { let collapse = use_collapse(); let key: StoredMaybeSignal<_> = key.into(); + let content_ref = create_node_ref::(); + let is_show_content = create_memo(move |_| { collapse.value.with(|keys| { if key.with(|key| keys.contains(key)) { @@ -48,12 +51,15 @@ pub fn CollapseItem( {move || title.get()} -
- {children()} -
+ +
+ {children()} +
+
} } diff --git a/thaw/src/components/css_transition/mod.rs b/thaw/src/components/css_transition/mod.rs new file mode 100644 index 0000000..4b284bc --- /dev/null +++ b/thaw/src/components/css_transition/mod.rs @@ -0,0 +1,95 @@ +use leptos::{html::ElementDescriptor, *}; +use std::ops::Deref; + +#[component] +pub fn CSSTransition( + node_ref: NodeRef, + #[prop(into)] show: MaybeSignal, + #[prop(into)] name: MaybeSignal, + children: CF, +) -> impl IntoView +where + T: ElementDescriptor + Clone + 'static, + CF: FnOnce(ReadSignal>) -> IV + 'static, + IV: IntoView, +{ + let display = create_rw_signal((!show.get_untracked()).then_some("display: none;")); + let remove_class_name = store_value(None::); + + node_ref.on_load(move |node_el| { + let el = node_el.clone().into_any(); + let el = el.deref(); + let class_list = el.class_list(); + let remove_class = Callback::new(move |_| { + remove_class_name.update_value(|class| { + if let Some(class) = class.take() { + match class { + RemoveClassName::Enter(active, to) => { + let _ = class_list.remove_2(&active, &to); + } + RemoveClassName::Leave(active, to) => { + let _ = class_list.remove_2(&active, &to); + display.set(Some("display: none;")); + } + } + } + }); + }); + + let _ = node_el + .on(ev::transitionend, move |_| { + remove_class.call(()); + }) + .on(ev::animationend, move |_| { + remove_class.call(()); + }); + }); + + create_render_effect(move |prev: Option| { + let show = show.get(); + if let Some(node_el) = node_ref.get_untracked() { + if let Some(prev) = prev { + let name = name.get_untracked(); + + let el = node_el.into_any(); + let el = el.deref(); + let class_list = el.class_list(); + + if show && !prev { + let enter_from = format!("{name}-enter-from"); + let enter_active = format!("{name}-enter-active"); + let enter_to = format!("{name}-enter-to"); + + let _ = class_list.add_2(&enter_from, &enter_active); + display.set(None); + request_animation_frame(move || { + let _ = class_list.remove_1(&enter_from); + let _ = class_list.add_1(&enter_to); + remove_class_name + .set_value(Some(RemoveClassName::Enter(enter_active, enter_to))); + }); + } else if !show && prev { + let leave_from = format!("{name}-leave-from"); + let leave_active = format!("{name}-leave-active"); + let leave_to = format!("{name}-leave-to"); + + let _ = class_list.add_2(&leave_from, &leave_active); + request_animation_frame(move || { + let _ = class_list.remove_1(&leave_from); + let _ = class_list.add_1(&leave_to); + remove_class_name + .set_value(Some(RemoveClassName::Leave(leave_active, leave_to))); + }); + } + } + } + show + }); + + children(display.read_only()) +} + +enum RemoveClassName { + Enter(String, String), + Leave(String, String), +} diff --git a/thaw/src/components/mod.rs b/thaw/src/components/mod.rs index a81efda..8bbd702 100644 --- a/thaw/src/components/mod.rs +++ b/thaw/src/components/mod.rs @@ -1,10 +1,12 @@ mod binder; +mod css_transition; mod if_comp; mod option_comp; mod teleport; mod wave; pub(crate) use binder::*; +pub(crate) use css_transition::CSSTransition; pub(crate) use if_comp::*; use leptos::*; pub(crate) use option_comp::*;