Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/slider vertical #367

Merged
merged 4 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions thaw/src/slider/docs/mod.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ view! {
}
```

### Vertical

```rust demo
let value = RwSignal::new(6.0);

view! {
<Slider value vertical=true step=2.0 min=0.0 max=10.0 />
}
```

## Slider Label

```rust demo
Expand Down Expand Up @@ -50,6 +60,7 @@ view! {
| min | `Signal<f64>` | `0` | Min value of the slider. |
| max | `Signal<f64>` | `100` | Max value of the slider. |
| step | `Signal<f64>` | `0` | The step in which value is incremented. |
| vertical | `Signal<bool>` | `false` | Render the Slider in a vertical orientation, smallest value on the bottom. |
| children | `Option<Children>` | `None` | |

### SliderLabel props
Expand Down
27 changes: 19 additions & 8 deletions thaw/src/slider/docs/range-slider.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ view! {
}
```

### Vertical

```rust demo
let value = RwSignal::new((6.0, 8.0));

view! {
<RangeSlider value vertical=true step=2.0 min=0.0 max=10.0 />
}
```

### SliderLabel

```rust demo
Expand All @@ -43,11 +53,12 @@ view! {

### RangeSlider Props

| Name | Type | Default | Description |
| ----- | ------------------- | -------------------- | ------------------------------------------- |
| class | `MaybeProp<String>` | `Default::default()` | |
| style | `MaybeProp<String>` | `Default::default()` | |
| value | `Model<(f64, f64)>` | `(0.0, 0.0)` | The current value of the controlled Slider. |
| min | `Signal<f64>` | `0` | Min value of the slider. |
| max | `Signal<f64>` | `100` | Max value of the slider. |
| step | `Signal<f64>` | `0` | The step in which value is incremented. |
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| class | `MaybeProp<String>` | `Default::default()` | |
| style | `MaybeProp<String>` | `Default::default()` | |
| value | `Model<(f64, f64)>` | `(0.0, 0.0)` | The current value of the controlled Slider. |
| min | `Signal<f64>` | `0` | Min value of the slider. |
| max | `Signal<f64>` | `100` | Max value of the slider. |
| step | `Signal<f64>` | `0` | The step in which value is incremented. |
| vertical | `Signal<bool>` | `false` | Render the Slider in a vertical orientation, smallest value on the bottom. |
153 changes: 2 additions & 151 deletions thaw/src/slider/mod.rs
Original file line number Diff line number Diff line change
@@ -1,156 +1,7 @@
mod range_slider;
mod slider;
mod slider_label;

pub use range_slider::*;
pub use slider::*;
pub use slider_label::SliderLabel;

use crate::{FieldInjection, FieldValidationState, Rule};
use leptos::{context::Provider, ev, prelude::*};
use std::ops::Deref;
use thaw_components::OptionComp;
use thaw_utils::{class_list, mount_style, Model};

#[component]
pub fn Slider(
#[prop(optional, into)] class: MaybeProp<String>,
#[prop(optional, into)] id: MaybeProp<String>,
/// A string specifying a name for the input control.
/// This name is submitted along with the control's value when the form data is submitted.
#[prop(optional, into)]
name: MaybeProp<String>,
/// The rules to validate Field.
#[prop(optional, into)]
rules: Vec<SliderRule>,
/// The current value of the controlled Slider.
#[prop(optional, into)]
value: Model<f64>,
/// Min value of the slider.
#[prop(default = 0f64.into(), into)]
min: Signal<f64>,
/// Max value of the slider.
#[prop(default = 100f64.into(), into)]
max: Signal<f64>,
/// The step in which value is incremented.
#[prop(optional, into)]
step: MaybeProp<f64>,
#[prop(optional)] children: Option<Children>,
) -> impl IntoView {
mount_style("slider", include_str!("./slider.css"));
let (id, name) = FieldInjection::use_id_and_name(id, name);
let validate = Rule::validate(rules, value, name);
let is_chldren = children.is_some();
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 {
v
}
});

let on_input = move |e: ev::Event| {
if let Ok(range_value) = event_target_value(&e).parse::<f64>() {
value.set(range_value);
validate.run(Some(SliderRuleTrigger::Input));
}
};

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)
));
}
}
css_vars
};

view! {
<Provider value=SliderInjection { max, min }>
<div class=class_list!["thaw-slider", class] style=css_vars>
<input
min=move || min.get()
max=move || max.get()
step=move || step.get()
type="range"
class="thaw-slider__input"
id=id
name=name
on:input=on_input
value=current_value.get_untracked()
prop:value=move || current_value.get()
/>
<div class="thaw-slider__rail"></div>
<div class="thaw-slider__thumb"></div>
<OptionComp value=children let:children>
<div class="thaw-slider__datalist">{children()}</div>
</OptionComp>
</div>
</Provider>
}
}

#[derive(Clone)]
pub(crate) struct SliderInjection {
pub max: Signal<f64>,
pub min: Signal<f64>,
}

impl SliderInjection {
pub fn expect_context() -> Self {
expect_context()
}
}

#[derive(Debug, Default, PartialEq, Clone, Copy)]
pub enum SliderRuleTrigger {
#[default]
Input,
}

pub struct SliderRule(Rule<f64, SliderRuleTrigger>);

impl SliderRule {
pub fn validator(
f: impl Fn(&f64, Signal<Option<String>>) -> Result<(), FieldValidationState>
+ Send
+ Sync
+ 'static,
) -> Self {
Self(Rule::validator(f))
}

pub fn with_trigger(self, trigger: SliderRuleTrigger) -> Self {
Self(Rule::with_trigger(self.0, trigger))
}
}

impl Deref for SliderRule {
type Target = Rule<f64, SliderRuleTrigger>;

fn deref(&self) -> &Self::Target {
&self.0
}
}
81 changes: 61 additions & 20 deletions thaw/src/slider/range_slider/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::super::SliderInjection;
use leptos::{context::Provider, ev, html, logging, prelude::*};
use leptos::{context::Provider, ev, html, prelude::*};
use thaw_components::OptionComp;
use thaw_utils::{class_list, mount_style, Model};

Expand All @@ -17,6 +17,9 @@ pub fn RangeSlider(
/// The step in which value is incremented.
#[prop(optional, into)]
step: MaybeProp<f64>,
/// Render the Slider in a vertical orientation, smallest value on the bottom.
#[prop(optional, into)]
vertical: Signal<bool>,
#[prop(optional)] children: Option<Children>,
) -> impl IntoView {
mount_style("range-slider", include_str!("./range-slider.css"));
Expand All @@ -35,8 +38,6 @@ pub fn RangeSlider(
left = closest_multiple(left, step, min, max);
right = closest_multiple(right, step, min, max);

logging::log!("l {} r {}", left, right);

(left, right)
});

Expand All @@ -58,6 +59,13 @@ pub fn RangeSlider(

let css_vars = move || {
let mut css_vars = style.get().unwrap_or_default();

if vertical.get() {
css_vars.push_str(";--thaw-slider--direction: 0deg;");
} else {
css_vars.push_str(";--thaw-slider--direction: 90deg;");
}

if let Some(step) = step.get() {
if step > 0.0 {
let max = max.get();
Expand Down Expand Up @@ -96,14 +104,20 @@ pub fn RangeSlider(

let on_click = move |e: web_sys::MouseEvent| {
if let Some(slider) = slider_ref.get_untracked() {
let rect = slider.get_bounding_client_rect();
let ev_x = f64::from(e.x());
let min = min.get_untracked();
let max = max.get_untracked();

let percentage = (ev_x - rect.x()) / rect.width() * (max - min);
let (left, right) = current_value.get();
let rect = slider.get_bounding_client_rect();
let percentage = if vertical.get_untracked() {
let ev_y = f64::from(e.y());
let slider_height = rect.height();
(slider_height + rect.y() - ev_y) / slider_height * (max - min)
} else {
let ev_x = f64::from(e.x());
(ev_x - rect.x()) / rect.width() * (max - min)
};

let (left, right) = current_value.get();
let left_diff = (left - percentage).abs();
let right_diff = (right - percentage).abs();

Expand Down Expand Up @@ -132,20 +146,39 @@ pub fn RangeSlider(
let on_mousemove = move || {
let mousemove = window_event_listener(ev::mousemove, move |e| {
if let Some(slider_el) = slider_ref.get_untracked() {
let min = min.get_untracked();
let max = max.get_untracked();

let slider_rect = slider_el.get_bounding_client_rect();
let slider_width = slider_rect.width();
let slider_x = slider_rect.x();
let ev_x = f64::from(e.x());
let length = if ev_x < slider_x {
0.0
} else if ev_x > slider_x + slider_width {
slider_width
let percentage = if vertical.get_untracked() {
let ev_y = f64::from(e.y());
let slider_y = slider_rect.y();
let slider_height = slider_rect.height();

let length = if ev_y < slider_y {
0.0
} else if ev_y > slider_y + slider_height {
slider_height
} else {
slider_y + slider_height - ev_y
};

length / slider_height * (max - min)
} else {
ev_x - slider_x
let ev_x = f64::from(e.x());
let slider_x = slider_rect.x();
let slider_width = slider_rect.width();

let length = if ev_x < slider_x {
0.0
} else if ev_x > slider_x + slider_width {
slider_width
} else {
ev_x - slider_x
};

length / slider_width * (max - min)
};
let min = min.get_untracked();
let max = max.get_untracked();
let percentage = length / slider_width * (max - min);

if left_mousemove.get_value() {
update_value(percentage, current_value.get_untracked().1);
Expand Down Expand Up @@ -182,7 +215,11 @@ pub fn RangeSlider(

view! {
<div
class=class_list!["thaw-range-slider", class]
class=class_list![
"thaw-range-slider",
move || format!("thaw-range-slider--{}", if vertical.get() { "vertical" } else { "horizontal" }),
class
]
on:click=on_click
node_ref=slider_ref
style=css_vars
Expand All @@ -201,7 +238,11 @@ pub fn RangeSlider(
on:mousedown=on_right_mousedown
></div>
<OptionComp value=children let:children>
<Provider value=SliderInjection { max, min }>
<Provider value=SliderInjection {
max,
min,
vertical,
}>
<div class="thaw-range-slider__datalist">{children()}</div>
</Provider>
</OptionComp>
Expand Down
Loading
Loading