feat: slider improvements (#40)

* feat: slider improvements

* feat: slider improvements

* feat: change the Slider step property to f64

---------

Co-authored-by: Cristobal Andrada <kandrelczyk@gmail.com>
Co-authored-by: luoxiao <luoxiaozero@163.com>
Co-authored-by: luoxiaozero <48741584+luoxiaozero@users.noreply.github.com>
This commit is contained in:
kandrelczyk 2023-12-11 14:35:20 +00:00 committed by GitHub
parent 6ab334cc94
commit 69dd0c5086
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 183 additions and 18 deletions

View file

@ -6,6 +6,8 @@ use thaw::*;
#[component] #[component]
pub fn SliderPage() -> impl IntoView { pub fn SliderPage() -> impl IntoView {
let value = create_rw_signal(0.0); let value = create_rw_signal(0.0);
let stepped_value = create_rw_signal(0.0);
let labeled_value = create_rw_signal(0.0);
view! { view! {
<div style="width: 896px; margin: 0 auto;"> <div style="width: 896px; margin: 0 auto;">
@ -25,6 +27,58 @@ pub fn SliderPage() -> impl IntoView {
</DemoCode> </DemoCode>
</Demo> </Demo>
<h3>"step"</h3>
<Demo>
<Slider step=10.0 value=stepped_value/>
<DemoCode slot>
{highlight_str!(
r#"
let value = create_rw_signal(0.0);
<Slider step=10.0 value/>
"#,
"rust"
)}
</DemoCode>
</Demo>
<h2>"SliderLabel"</h2>
<Demo>
<Slider value=labeled_value max=10.0 step=1.0>
<SliderLabel value=0.0>
"0"
</SliderLabel>
<SliderLabel value=5.0>
"5"
</SliderLabel>
<SliderLabel value=10.0>
"10"
</SliderLabel>
</Slider>
<DemoCode slot>
{highlight_str!(
r#"
let value = create_rw_signal(0.0);
<Slider value max=10.0 step=1.0>
<SliderLabel value=0>
"0"
</SliderLabel>
<SliderLabel value=5>
"5"
</SliderLabel>
<SliderLabel value=10>
"10"
</SliderLabel>
</Slider>
"#,
"rust"
)}
</DemoCode>
</Demo>
<h3>"Slider Props"</h3> <h3>"Slider Props"</h3>
<Table single_column=true> <Table single_column=true>
<thead> <thead>
@ -48,6 +102,43 @@ pub fn SliderPage() -> impl IntoView {
<td>"100"</td> <td>"100"</td>
<td>"Max value of the slider."</td> <td>"Max value of the slider."</td>
</tr> </tr>
<tr>
<td>"step"</td>
<td>"MaybeSignal<f64>"</td>
<td></td>
<td>"The step in which value is incremented."</td>
</tr>
<tr>
<td>"children"</td>
<td>"Option<Children>"</td>
<td></td>
<td>"Slider labels."</td>
</tr>
</tbody>
</Table>
<h3>"SliderLabel Props"</h3>
<Table single_column=true>
<thead>
<tr>
<th>"Name"</th>
<th>"Type"</th>
<th>"Default"</th>
<th>"Description"</th>
</tr>
</thead>
<tbody>
<tr>
<td>"value"</td>
<td>"ReadSignal<f64>"</td>
<td></td>
<td>"Value at which label will be placed."</td>
</tr>
<tr>
<td>"children"</td>
<td>"Children"</td>
<td></td>
<td>"Content of the lable."</td>
</tr>
</tbody> </tbody>
</Table> </Table>
</div> </div>

View file

@ -1,18 +1,25 @@
mod slider_label;
mod theme; mod theme;
use crate::{theme::use_theme, utils::mount_style, Theme}; use crate::{components::OptionComp, theme::use_theme, utils::mount_style, Theme};
use leptos::*; use leptos::*;
use web_sys::DomRect;
pub use slider_label::SliderLabel;
pub use theme::SliderTheme; pub use theme::SliderTheme;
#[component] #[component]
pub fn Slider( pub fn Slider(
#[prop(optional, into)] value: RwSignal<f64>, #[prop(optional, into)] value: RwSignal<f64>,
#[prop(default = MaybeSignal::Static(100f64), into)] max: MaybeSignal<f64>, #[prop(default = MaybeSignal::Static(100f64), into)] max: MaybeSignal<f64>,
#[prop(optional, into)] step: MaybeSignal<f64>,
#[prop(optional)] children: Option<Children>,
) -> impl IntoView { ) -> impl IntoView {
mount_style("slider", include_str!("./slider.css")); mount_style("slider", include_str!("./slider.css"));
let theme = use_theme(Theme::light); let theme = use_theme(Theme::light);
let css_vars = create_memo(move |_| { let css_vars = create_memo(move |_| {
let mut css_vars = String::new(); let mut css_vars = String::new();
css_vars.push_str(&format!("--thaw-slider-max: {};", max.get()));
theme.with(|theme| { theme.with(|theme| {
css_vars.push_str(&format!( css_vars.push_str(&format!(
"--thaw-background-color: {};", "--thaw-background-color: {};",
@ -37,7 +44,24 @@ pub fn Slider(
} }
}); });
let do_update_value = move |val| { let do_update_value = move |mut val| {
let step = step.get_untracked();
if step > 0.0 {
let result: f64 = val / step;
if result.fract() != 0.0 {
let prev_multiple = (result.floor() * step).abs();
let mut next_multiple = (result.ceil() * step).abs();
let max = max.get_untracked();
if next_multiple > max {
next_multiple = max;
}
if val - prev_multiple > next_multiple - val {
val = next_multiple;
} else {
val = prev_multiple;
}
}
}
value.set(val); value.set(val);
}; };
@ -48,9 +72,31 @@ pub fn Slider(
set_mouse_move.set(true); set_mouse_move.set(true);
}; };
let check_value_and_update = move |ev_x: f64, rect: DomRect| {
if ev_x <= rect.x() {
set_mouse_move_value.set(Some(0.0));
} else if ev_x >= rect.x() + rect.width() {
set_mouse_move_value.set(Some(max.get()));
} else {
set_mouse_move_value.set(Some(((ev_x - rect.x()) / rect.width()) * max.get()));
}
if let Some(value) = mouse_move_value.get_untracked() {
do_update_value(value);
}
};
let on_mouse_click = move |ev: ev::MouseEvent| {
if let Some(rail) = rail_ref.get_untracked() {
let rect = rail.get_bounding_client_rect();
let ev_x = f64::from(ev.x());
check_value_and_update(ev_x, rect);
};
};
let on_mouse_up = window_event_listener(ev::mouseup, move |_| { let on_mouse_up = window_event_listener(ev::mouseup, move |_| {
set_mouse_move.set(false); set_mouse_move.set(false);
}); });
on_cleanup(move || on_mouse_up.remove()); on_cleanup(move || on_mouse_up.remove());
let on_mouse_move = window_event_listener(ev::mousemove, move |ev| { let on_mouse_move = window_event_listener(ev::mousemove, move |ev| {
@ -58,37 +104,32 @@ pub fn Slider(
if let Some(rail) = rail_ref.get_untracked() { if let Some(rail) = rail_ref.get_untracked() {
let rect = rail.get_bounding_client_rect(); let rect = rail.get_bounding_client_rect();
let ev_x = f64::from(ev.x()); let ev_x = f64::from(ev.x());
if ev_x <= rect.x() { check_value_and_update(ev_x, rect);
set_mouse_move_value.set(Some(0.0));
} else if ev_x >= rect.x() + rect.width() {
set_mouse_move_value.set(Some(max.get()));
} else {
set_mouse_move_value.set(Some(((ev_x - rect.x()) / rect.width()) * max.get()));
}
if let Some(value) = mouse_move_value.get_untracked() {
do_update_value(value);
}
}; };
} }
}); });
on_cleanup(move || on_mouse_move.remove()); on_cleanup(move || on_mouse_move.remove());
view! { view! {
<div class="thaw-slider" style=move || css_vars.get()> <div
class="thaw-slider"
style=move || css_vars.get()
on:click=on_mouse_click
>
<div class="thaw-slider-rail" ref=rail_ref> <div class="thaw-slider-rail" ref=rail_ref>
<div <div
class="thaw-slider-rail__fill" class="thaw-slider-rail__fill"
style=move || format!("width: {}%", percentage.get()) style=move || format!("width: {}%", percentage.get())
></div> ></div>
</div> </div>
<OptionComp value=children let:children>
{children()}
</OptionComp>
<div <div
on:mousedown=on_mouse_down on:mousedown=on_mouse_down
class="thaw-slider-handle" class="thaw-slider-handle"
style=move || { style=move || {
format!( format!("left: {}%", percentage.get())
"left: {}%; transform: translateX(-{}%)", percentage.get(), percentage
.get(),
)
} }
> >
</div> </div>

View file

@ -2,17 +2,20 @@
position: relative; position: relative;
padding: 6px 0; padding: 6px 0;
cursor: pointer; cursor: pointer;
user-select: none;
} }
.thaw-slider-rail { .thaw-slider-rail {
height: 4px; height: 4px;
background-color: var(--thaw-background-color); background-color: var(--thaw-background-color);
border-radius: 2px; border-radius: 2px;
cursor: pointer;
} }
.thaw-slider-rail__fill { .thaw-slider-rail__fill {
width: 0%; width: 0%;
height: 4px; height: 4px;
background-color: var(--thaw-background-color-fill); background-color: var(--thaw-background-color-fill);
border-radius: 2px; border-radius: 2px;
cursor: pointer;
} }
.thaw-slider-handle { .thaw-slider-handle {
position: absolute; position: absolute;
@ -22,4 +25,5 @@
border-radius: 50%; border-radius: 50%;
background-color: white; background-color: white;
box-shadow: 0px 0px 4px #2224; box-shadow: 0px 0px 4px #2224;
transform: translateX(-50%);
} }

View file

@ -0,0 +1,6 @@
.thaw-slider-label {
position: absolute;
display: inline-block;
transform: translateX(-50%);
margin-top: 8px;
}

View file

@ -0,0 +1,23 @@
use leptos::*;
use crate::utils::mount_style;
#[component]
pub fn SliderLabel(
#[prop(into)] value: MaybeSignal<f64>,
children: Children,
) -> impl IntoView {
mount_style("slider-label", include_str!("./slider_label.css"));
view! {
<div
class:thaw-slider-label=true
style=move || {
format!("left: calc(calc({} / var(--thaw-slider-max)) * 100%)", value.get())
}>
{children()}
</div>
}
}