feat: button add wave

This commit is contained in:
luoxiao 2023-11-02 10:16:31 +08:00
parent 4597b823e7
commit ec551cd2e6
7 changed files with 118 additions and 55 deletions

View file

@ -5,7 +5,7 @@
color: var(--font-color); color: var(--font-color);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: 5px; border-radius: 5px;
position: relative;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -54,3 +54,34 @@
transform: rotate(360deg); 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;
}
}

View file

@ -1,6 +1,12 @@
mod theme; 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::*; use leptos::*;
pub use theme::ButtonTheme; pub use theme::ButtonTheme;
@ -76,6 +82,7 @@ pub fn Button(
css_vars.push_str("--font-color: #fff;"); css_vars.push_str("--font-color: #fff;");
css_vars.push_str(&format!("--border-color: {bg_color};")); css_vars.push_str(&format!("--border-color: {bg_color};"));
css_vars.push_str(&format!("--border-color-hover: {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 { } else if variant.get() == ButtonVariant::Text {
css_vars.push_str(&format!("--font-color-hover: {bg_color};")); css_vars.push_str(&format!("--font-color-hover: {bg_color};"));
css_vars.push_str(&format!( css_vars.push_str(&format!(
@ -86,10 +93,12 @@ pub fn Button(
"--background-color-active: {};", "--background-color-active: {};",
theme.button.color_text_active theme.button.color_text_active
)); ));
css_vars.push_str(&format!("--melt-ripple-color: #0000;"));
} else { } else {
css_vars.push_str(&format!("--font-color-hover: {bg_color};")); css_vars.push_str(&format!("--font-color-hover: {bg_color};"));
css_vars.push_str("--border-color: #555a;"); css_vars.push_str("--border-color: #555a;");
css_vars.push_str("--border-color-hover: #555;"); 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() disabled.get()
}); });
let wave_ref = ComponentRef::<WaveRef>::default();
let on_click = move |event| { let on_click = move |event| {
if disabled.get() { if disabled.get() {
return; return;
} }
if let Some(wave_ref) = wave_ref.get_untracked() {
wave_ref.play();
}
let Some(callback) = on_click.as_ref() else { let Some(callback) = on_click.as_ref() else {
return; return;
}; };
@ -131,6 +145,7 @@ pub fn Button(
style=move || format!("{}{}", css_vars.get(), style.get()) style=move || format!("{}{}", css_vars.get(), style.get())
on:click=on_click on:click=on_click
> >
<Wave comp_ref=wave_ref/>
{move || { {move || {
if loading.get() { if loading.get() {
view! { view! {

View file

@ -69,4 +69,3 @@ pub use tag::*;
pub use theme::*; pub use theme::*;
pub use upload::*; pub use upload::*;
pub use utils::{mount_style::mount_style, signal::SignalWatch}; pub use utils::{mount_style::mount_style, signal::SignalWatch};
pub use wave::*;

View file

@ -0,0 +1,30 @@
use leptos::{create_rw_signal, RwSignal, SignalGetUntracked, SignalSet};
pub struct ComponentRef<T: 'static>(RwSignal<Option<T>>);
impl<T> Default for ComponentRef<T> {
fn default() -> Self {
Self(create_rw_signal(None))
}
}
impl<T> Clone for ComponentRef<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: 'static> Copy for ComponentRef<T> {}
impl<T> ComponentRef<T> {
pub fn get_untracked(&self) -> Option<T>
where
T: Clone,
{
self.0.get_untracked()
}
pub fn load(&self, comp_ref: T) {
self.0.set(Some(comp_ref));
}
}

View file

@ -1,8 +1,10 @@
// mod callback; // mod callback;
mod component_ref;
pub mod maybe_rw_signal; pub mod maybe_rw_signal;
mod maybe_signal_store; mod maybe_signal_store;
pub mod mount_style; pub mod mount_style;
pub mod signal; pub mod signal;
// pub use callback::AsyncCallback; // pub use callback::AsyncCallback;
pub use component_ref::ComponentRef;
pub use maybe_signal_store::*; pub use maybe_signal_store::*;

View file

@ -1,28 +1,44 @@
use crate::utils::mount_style::mount_style; use crate::utils::{mount_style::mount_style, ComponentRef};
use leptos::*; 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] #[component]
pub fn Wave(children: Children) -> impl IntoView { pub fn Wave(#[prop(optional)] comp_ref: ComponentRef<WaveRef>) -> impl IntoView {
mount_style("wave", include_str!("./wave.css")); mount_style("wave", include_str!("./wave.css"));
let (css_vars, set_css_vars) = create_signal(String::new());
let wave_ref = create_node_ref::<html::Div>(); let wave_ref = create_node_ref::<html::Div>();
wave_ref.on_load(move |wave| { let animation_timeout_handle = create_rw_signal(None::<TimeoutHandle>);
_ = wave.on(ev::mousedown, move |ev| { let play = Callback::new(move |_: ()| {
wave_ref.on_load(move |wave| { if let Some(handle) = animation_timeout_handle.get() {
let rect = wave.get_bounding_client_rect(); handle.clear();
let client_x = f64::from(ev.client_x()); animation_timeout_handle.set(None);
let client_y = f64::from(ev.client_y()); }
set_css_vars.set(format!( if let Some(wave_ref) = wave_ref.get() {
"--x: {}px; --y: {}px", _ = wave_ref.offset_height();
client_x - rect.left(), }
client_y - rect.top() 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! { view! {
<div class="melt-wave" ref=wave_ref style=move || css_vars.get()> <div class="melt-wave" class=("melt-wave--active", move || animation_timeout_handle.with(|handle| handle.is_some())) ref=wave_ref>
{children()}
</div> </div>
} }
} }

View file

@ -1,38 +1,8 @@
.melt-wave { .melt-wave {
position: relative;
}
.melt-wave::before {
content: "";
display: block;
position: absolute; position: absolute;
width: 100%;
height: 100%;
left: 0; left: 0;
right: 0;
top: 0; top: 0;
transition: 0.2s; bottom: 0;
background: #fff; border-radius: inherit;
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;
} }