mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
refactor: Slider
This commit is contained in:
parent
add2a0bae3
commit
f5c0d2a522
5 changed files with 105 additions and 123 deletions
|
@ -1,7 +1,7 @@
|
|||
# Slider
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(0.0);
|
||||
let value = RwSignal::new(0.0);
|
||||
|
||||
view! {
|
||||
<Slider value/>
|
||||
|
@ -11,20 +11,20 @@ view! {
|
|||
### Step
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(0.0);
|
||||
let value = RwSignal::new(0.0);
|
||||
|
||||
view! {
|
||||
<Slider step=10.0 value/>
|
||||
<Slider step=25.0 value/>
|
||||
}
|
||||
```
|
||||
|
||||
## Slider Label
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(0.0);
|
||||
let value = RwSignal::new(0.0);
|
||||
|
||||
view! {
|
||||
<Slider value max=10.0 step=1.0>
|
||||
<Slider value max=10.0 step=5.0>
|
||||
<SliderLabel value=0.0>
|
||||
"0"
|
||||
</SliderLabel>
|
||||
|
|
|
@ -5,114 +5,106 @@ pub use slider_label::SliderLabel;
|
|||
use leptos::*;
|
||||
use thaw_components::OptionComp;
|
||||
use thaw_utils::{class_list, mount_style, Model, OptionalProp};
|
||||
use web_sys::DomRect;
|
||||
|
||||
#[component]
|
||||
pub fn Slider(
|
||||
#[prop(optional, into)] value: Model<f64>,
|
||||
#[prop(default = MaybeSignal::Static(100f64), into)] max: MaybeSignal<f64>,
|
||||
#[prop(optional, into)] step: MaybeSignal<f64>,
|
||||
#[prop(default = 0f64.into(), into)] min: MaybeSignal<f64>,
|
||||
#[prop(default = 100f64.into(), into)] max: MaybeSignal<f64>,
|
||||
#[prop(optional, into)] step: MaybeProp<f64>,
|
||||
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
||||
#[prop(optional)] children: Option<Children>,
|
||||
) -> impl IntoView {
|
||||
mount_style("slider", include_str!("./slider.css"));
|
||||
|
||||
let percentage = create_memo(move |_| {
|
||||
if value.get() < 0.0 || max.get() <= 0.0 {
|
||||
0f64
|
||||
} else if value.get() >= max.get() {
|
||||
100f64
|
||||
let is_chldren = children.is_some();
|
||||
let list_id = is_chldren.then(|| uuid::Uuid::new_v4().to_string());
|
||||
let current_value = Memo::new(move |_| {
|
||||
let max = max.get();
|
||||
let min = min.get();
|
||||
let v = value.get();
|
||||
if v > max {
|
||||
max
|
||||
} else if v < min {
|
||||
min
|
||||
} else {
|
||||
value.get() / max.get() * 100.0
|
||||
v
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
let on_input = move |e: ev::Event| {
|
||||
if let Ok(range_value) = event_target_value(&e).parse::<f64>() {
|
||||
value.set(range_value);
|
||||
}
|
||||
};
|
||||
|
||||
let css_vars = move || {
|
||||
let max = max.get();
|
||||
let min = min.get();
|
||||
let mut css_vars = format!(
|
||||
"--thaw-slider--direction: 90deg;--thaw-slider--progress: {:.2}%;",
|
||||
if max == min {
|
||||
0.0
|
||||
} else {
|
||||
(current_value.get() - min) / (max - min) * 100.0
|
||||
}
|
||||
);
|
||||
|
||||
if is_chldren {
|
||||
css_vars.push_str(&format!("--thaw-slider--max: {:.2};", max));
|
||||
css_vars.push_str(&format!("--thaw-slider--min: {:.2};", min));
|
||||
}
|
||||
|
||||
if let Some(step) = step.get() {
|
||||
if step > 0.0 {
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-slider--steps-percent: {:.2}%",
|
||||
step * 100.0 / (max - min)
|
||||
));
|
||||
}
|
||||
}
|
||||
value.set(val);
|
||||
css_vars
|
||||
};
|
||||
|
||||
let rail_ref = create_node_ref::<html::Div>();
|
||||
let (mouse_move_value, set_mouse_move_value) = create_signal::<Option<f64>>(None);
|
||||
let (is_mouse_move, set_mouse_move) = create_signal(false);
|
||||
let on_mouse_down = move |_| {
|
||||
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 |_| {
|
||||
set_mouse_move.set(false);
|
||||
});
|
||||
|
||||
on_cleanup(move || on_mouse_up.remove());
|
||||
|
||||
let on_mouse_move = window_event_listener(ev::mousemove, move |ev| {
|
||||
if is_mouse_move.get_untracked() {
|
||||
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);
|
||||
};
|
||||
}
|
||||
});
|
||||
on_cleanup(move || on_mouse_move.remove());
|
||||
|
||||
view! {
|
||||
<div
|
||||
class=class_list!["thaw-slider", class.map(| c | move || c.get())]
|
||||
style=move || format!("--thaw-slider--direction: 90deg;--thaw-slider--progress: {:.2}%", value.get())
|
||||
on:click=on_mouse_click
|
||||
>
|
||||
<input type="range" class="thaw-slider__input"/>
|
||||
<div class="thaw-slider__rail" ref=rail_ref>
|
||||
</div>
|
||||
<div class="thaw-slider__thumb">
|
||||
</div>
|
||||
<OptionComp value=children let:children>
|
||||
{children()}
|
||||
</OptionComp>
|
||||
<Provider value=SliderInjection { max, min }>
|
||||
<div
|
||||
on:mousedown=on_mouse_down
|
||||
class="thaw-slider-handle"
|
||||
style=move || { format!("left: {}%", percentage.get()) }
|
||||
></div>
|
||||
|
||||
</div>
|
||||
class=class_list!["thaw-slider", class.map(| c | move || c.get())]
|
||||
style=css_vars
|
||||
>
|
||||
<input
|
||||
min=move || min.get()
|
||||
max=move || max.get()
|
||||
step=move || step.get()
|
||||
type="range"
|
||||
class="thaw-slider__input"
|
||||
on:input=on_input
|
||||
value=current_value.get_untracked()
|
||||
prop:value=move || current_value.get()
|
||||
list=list_id.clone()
|
||||
/>
|
||||
<div class="thaw-slider__rail">
|
||||
</div>
|
||||
<div class="thaw-slider__thumb">
|
||||
</div>
|
||||
<OptionComp value=children let:children>
|
||||
<datalist id=list_id class="thaw-slider__datalist">
|
||||
{children()}
|
||||
</datalist>
|
||||
</OptionComp>
|
||||
</div>
|
||||
</Provider>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SliderInjection {
|
||||
pub max: MaybeSignal<f64>,
|
||||
pub min: MaybeSignal<f64>,
|
||||
}
|
||||
|
||||
impl SliderInjection {
|
||||
pub fn use_() -> Self {
|
||||
expect_context()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,26 +125,9 @@
|
|||
var(--colorNeutralStroke1);
|
||||
}
|
||||
|
||||
.thaw-slider-rail {
|
||||
height: 4px;
|
||||
background-color: var(--thaw-background-color);
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.thaw-slider-rail__fill {
|
||||
width: 0%;
|
||||
height: 4px;
|
||||
background-color: var(--thaw-background-color-fill);
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.thaw-slider-handle {
|
||||
.thaw-slider__datalist {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
box-shadow: 0px 0px 4px #2224;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
top: calc(var(--thaw-slider__thumb--size) + 4px);
|
||||
}
|
||||
|
|
|
@ -2,5 +2,4 @@
|
|||
position: absolute;
|
||||
display: inline-block;
|
||||
transform: translateX(-50%);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
|
|
@ -1,19 +1,27 @@
|
|||
use leptos::*;
|
||||
use thaw_utils::mount_style;
|
||||
|
||||
use crate::SliderInjection;
|
||||
|
||||
#[component]
|
||||
pub fn SliderLabel(#[prop(into)] value: MaybeSignal<f64>, children: Children) -> impl IntoView {
|
||||
mount_style("slider-label", include_str!("./slider_label.css"));
|
||||
|
||||
let slider = SliderInjection::use_();
|
||||
|
||||
let style = move || {
|
||||
let value = (value.get() - slider.min.get()) / (slider.max.get() - slider.min.get());
|
||||
format!("left: calc({} * (100% - var(--thaw-slider__thumb--size)) + var(--thaw-slider__thumb--size) / 2)", value)
|
||||
};
|
||||
|
||||
view! {
|
||||
<div
|
||||
<option
|
||||
class:thaw-slider-label=true
|
||||
style=move || {
|
||||
format!("left: calc(calc({} / var(--thaw-slider-max)) * 100%)", value.get())
|
||||
}
|
||||
style=style
|
||||
value=move || value.get()
|
||||
>
|
||||
|
||||
{children()}
|
||||
</div>
|
||||
</option>
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue