mirror of
https://github.com/adoyle0/thaw.git
synced 2025-02-02 08:34:15 -05:00
Feat/css transition (#75)
* feat: add CSSTransition component * fix: CSSTransition transitionend * feat: collapse item style * fix: CSSTransition display
This commit is contained in:
parent
8dde51c4a5
commit
4aeab59e6f
4 changed files with 135 additions and 6 deletions
|
@ -27,3 +27,29 @@
|
||||||
.thaw-collapse-item__content {
|
.thaw-collapse-item__content {
|
||||||
padding-top: 16px;
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use super::use_collapse;
|
use super::use_collapse;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
components::CSSTransition,
|
||||||
utils::{class_list::class_list, StoredMaybeSignal},
|
utils::{class_list::class_list, StoredMaybeSignal},
|
||||||
Icon,
|
Icon,
|
||||||
};
|
};
|
||||||
|
@ -15,6 +16,8 @@ pub fn CollapseItem(
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let collapse = use_collapse();
|
let collapse = use_collapse();
|
||||||
let key: StoredMaybeSignal<_> = key.into();
|
let key: StoredMaybeSignal<_> = key.into();
|
||||||
|
let content_ref = create_node_ref::<html::Div>();
|
||||||
|
|
||||||
let is_show_content = create_memo(move |_| {
|
let is_show_content = create_memo(move |_| {
|
||||||
collapse.value.with(|keys| {
|
collapse.value.with(|keys| {
|
||||||
if key.with(|key| keys.contains(key)) {
|
if key.with(|key| keys.contains(key)) {
|
||||||
|
@ -48,12 +51,15 @@ pub fn CollapseItem(
|
||||||
<Icon icon=Icon::from(AiIcon::AiRightOutlined) class="thaw-collapse-item-arrow"/>
|
<Icon icon=Icon::from(AiIcon::AiRightOutlined) class="thaw-collapse-item-arrow"/>
|
||||||
{move || title.get()}
|
{move || title.get()}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<CSSTransition node_ref=content_ref show=is_show_content name="thaw-collapse-item" let:display>
|
||||||
class="thaw-collapse-item__content"
|
<div
|
||||||
style=move || (!is_show_content.get()).then_some("display: none;")
|
class="thaw-collapse-item__content"
|
||||||
>
|
ref=content_ref
|
||||||
{children()}
|
style=move || display.get()
|
||||||
</div>
|
>
|
||||||
|
{children()}
|
||||||
|
</div>
|
||||||
|
</CSSTransition>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
95
thaw/src/components/css_transition/mod.rs
Normal file
95
thaw/src/components/css_transition/mod.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use leptos::{html::ElementDescriptor, *};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn CSSTransition<T, CF, IV>(
|
||||||
|
node_ref: NodeRef<T>,
|
||||||
|
#[prop(into)] show: MaybeSignal<bool>,
|
||||||
|
#[prop(into)] name: MaybeSignal<String>,
|
||||||
|
children: CF,
|
||||||
|
) -> impl IntoView
|
||||||
|
where
|
||||||
|
T: ElementDescriptor + Clone + 'static,
|
||||||
|
CF: FnOnce(ReadSignal<Option<&'static str>>) -> IV + 'static,
|
||||||
|
IV: IntoView,
|
||||||
|
{
|
||||||
|
let display = create_rw_signal((!show.get_untracked()).then_some("display: none;"));
|
||||||
|
let remove_class_name = store_value(None::<RemoveClassName>);
|
||||||
|
|
||||||
|
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<bool>| {
|
||||||
|
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),
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
mod binder;
|
mod binder;
|
||||||
|
mod css_transition;
|
||||||
mod if_comp;
|
mod if_comp;
|
||||||
mod option_comp;
|
mod option_comp;
|
||||||
mod teleport;
|
mod teleport;
|
||||||
mod wave;
|
mod wave;
|
||||||
|
|
||||||
pub(crate) use binder::*;
|
pub(crate) use binder::*;
|
||||||
|
pub(crate) use css_transition::CSSTransition;
|
||||||
pub(crate) use if_comp::*;
|
pub(crate) use if_comp::*;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
pub(crate) use option_comp::*;
|
pub(crate) use option_comp::*;
|
||||||
|
|
Loading…
Add table
Reference in a new issue