From ec551cd2e6234d3fbcd0289d0b221a6cbc284402 Mon Sep 17 00:00:00 2001 From: luoxiao Date: Thu, 2 Nov 2023 10:16:31 +0800 Subject: [PATCH] feat: button add wave --- src/button/button.css | 33 ++++++++++++++++++++++- src/button/mod.rs | 17 +++++++++++- src/lib.rs | 1 - src/utils/component_ref.rs | 30 +++++++++++++++++++++ src/utils/mod.rs | 2 ++ src/wave/mod.rs | 54 ++++++++++++++++++++++++-------------- src/wave/wave.css | 36 +++---------------------- 7 files changed, 118 insertions(+), 55 deletions(-) create mode 100644 src/utils/component_ref.rs diff --git a/src/button/button.css b/src/button/button.css index b3bb2d6..fa8003b 100644 --- a/src/button/button.css +++ b/src/button/button.css @@ -5,7 +5,7 @@ color: var(--font-color); border: 1px solid var(--border-color); border-radius: 5px; - + position: relative; display: inline-flex; align-items: center; justify-content: center; @@ -54,3 +54,34 @@ transform: rotate(360deg); } } + +.melt-button .melt-wave { + pointer-events: none; + animation-iteration-count: 1; + animation-duration: 0.6s; + animation-timing-function: cubic-bezier(0, 0, 0.2, 1), + cubic-bezier(0, 0, 0.2, 1); +} + +.melt-button .melt-wave.melt-wave--active { + z-index: 1; + animation-name: meltButtonWaveSpread, meltButtonWaveOpacity; +} + +@keyframes meltButtonWaveSpread { + from { + box-shadow: 0 0 0.5px 0 var(--melt-ripple-color); + } + to { + box-shadow: 0 0 0.5px 6px var(--melt-ripple-color); + } +} + +@keyframes meltButtonWaveOpacity { + from { + opacity: 0.6; + } + to { + opacity: 0; + } +} diff --git a/src/button/mod.rs b/src/button/mod.rs index 481b0be..15fbf2f 100644 --- a/src/button/mod.rs +++ b/src/button/mod.rs @@ -1,6 +1,12 @@ mod theme; -use crate::{components::*, icon::*, theme::*, utils::mount_style::mount_style}; +use crate::{ + components::*, + icon::*, + theme::*, + utils::{mount_style::mount_style, ComponentRef}, + wave::{Wave, WaveRef}, +}; use leptos::*; pub use theme::ButtonTheme; @@ -76,6 +82,7 @@ pub fn Button( css_vars.push_str("--font-color: #fff;"); css_vars.push_str(&format!("--border-color: {bg_color};")); css_vars.push_str(&format!("--border-color-hover: {bg_color};")); + css_vars.push_str(&format!("--melt-ripple-color: {bg_color};")); } else if variant.get() == ButtonVariant::Text { css_vars.push_str(&format!("--font-color-hover: {bg_color};")); css_vars.push_str(&format!( @@ -86,10 +93,12 @@ pub fn Button( "--background-color-active: {};", theme.button.color_text_active )); + css_vars.push_str(&format!("--melt-ripple-color: #0000;")); } else { css_vars.push_str(&format!("--font-color-hover: {bg_color};")); css_vars.push_str("--border-color: #555a;"); css_vars.push_str("--border-color-hover: #555;"); + css_vars.push_str(&format!("--melt-ripple-color: #0000;")); } }); @@ -111,10 +120,15 @@ pub fn Button( disabled.get() }); + let wave_ref = ComponentRef::::default(); + let on_click = move |event| { if disabled.get() { return; } + if let Some(wave_ref) = wave_ref.get_untracked() { + wave_ref.play(); + } let Some(callback) = on_click.as_ref() else { return; }; @@ -131,6 +145,7 @@ pub fn Button( style=move || format!("{}{}", css_vars.get(), style.get()) on:click=on_click > + {move || { if loading.get() { view! { diff --git a/src/lib.rs b/src/lib.rs index f58cd5d..0b1048e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,4 +69,3 @@ pub use tag::*; pub use theme::*; pub use upload::*; pub use utils::{mount_style::mount_style, signal::SignalWatch}; -pub use wave::*; diff --git a/src/utils/component_ref.rs b/src/utils/component_ref.rs new file mode 100644 index 0000000..66d0a34 --- /dev/null +++ b/src/utils/component_ref.rs @@ -0,0 +1,30 @@ +use leptos::{create_rw_signal, RwSignal, SignalGetUntracked, SignalSet}; + +pub struct ComponentRef(RwSignal>); + +impl Default for ComponentRef { + fn default() -> Self { + Self(create_rw_signal(None)) + } +} + +impl Clone for ComponentRef { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for ComponentRef {} + +impl ComponentRef { + pub fn get_untracked(&self) -> Option + where + T: Clone, + { + self.0.get_untracked() + } + + pub fn load(&self, comp_ref: T) { + self.0.set(Some(comp_ref)); + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index ea5eab4..2a7fb34 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,8 +1,10 @@ // mod callback; +mod component_ref; pub mod maybe_rw_signal; mod maybe_signal_store; pub mod mount_style; pub mod signal; // pub use callback::AsyncCallback; +pub use component_ref::ComponentRef; pub use maybe_signal_store::*; diff --git a/src/wave/mod.rs b/src/wave/mod.rs index 75f5a30..b2f04fd 100644 --- a/src/wave/mod.rs +++ b/src/wave/mod.rs @@ -1,28 +1,44 @@ -use crate::utils::mount_style::mount_style; -use leptos::*; +use crate::utils::{mount_style::mount_style, ComponentRef}; +use leptos::{leptos_dom::helpers::TimeoutHandle, *}; +use std::time::Duration; + +#[derive(Clone)] +pub struct WaveRef { + play: Callback<()>, +} + +impl WaveRef { + pub fn play(&self) { + self.play.call(()); + } +} #[component] -pub fn Wave(children: Children) -> impl IntoView { +pub fn Wave(#[prop(optional)] comp_ref: ComponentRef) -> impl IntoView { mount_style("wave", include_str!("./wave.css")); - let (css_vars, set_css_vars) = create_signal(String::new()); let wave_ref = create_node_ref::(); - wave_ref.on_load(move |wave| { - _ = wave.on(ev::mousedown, move |ev| { - wave_ref.on_load(move |wave| { - let rect = wave.get_bounding_client_rect(); - let client_x = f64::from(ev.client_x()); - let client_y = f64::from(ev.client_y()); - set_css_vars.set(format!( - "--x: {}px; --y: {}px", - client_x - rect.left(), - client_y - rect.top() - )); - }) - }); + let animation_timeout_handle = create_rw_signal(None::); + let play = Callback::new(move |_: ()| { + if let Some(handle) = animation_timeout_handle.get() { + handle.clear(); + animation_timeout_handle.set(None); + } + if let Some(wave_ref) = wave_ref.get() { + _ = wave_ref.offset_height(); + } + let handle = set_timeout_with_handle( + move || { + animation_timeout_handle.set(None); + }, + Duration::from_secs(1), + ); + if let Ok(handle) = handle { + animation_timeout_handle.set(Some(handle)) + } }); + comp_ref.load(WaveRef { play }); view! { -
- {children()} +
} } diff --git a/src/wave/wave.css b/src/wave/wave.css index 1e28f33..0ab001c 100644 --- a/src/wave/wave.css +++ b/src/wave/wave.css @@ -1,38 +1,8 @@ .melt-wave { - position: relative; -} -.melt-wave::before { - content: ""; - display: block; position: absolute; - width: 100%; - height: 100%; left: 0; + right: 0; top: 0; - transition: 0.2s; - background: #fff; - opacity: 0; -} -.melt-wave:active::before { - opacity: 0.2; -} -.melt-wave::after { - content: ""; - display: block; - position: absolute; - width: 200%; - height: 100%; - left: var(--x, 0); - top: var(--y, 0); - background-image: radial-gradient(circle, #fff 10%, transparent 10.01%); - background-repeat: no-repeat; - background-position: 50%; - transform: translate(-50%, -50%) scale(10); - opacity: 0; - transition: transform 0.8s, opacity 0.8s; -} -.melt-wave:active::after { - transform: translate(-50%, -50%) scale(0); - opacity: 0.3; - transition: 0s; + bottom: 0; + border-radius: inherit; }