From 2cdf272077c85e7d9d70652d13201cf7f0b75f2f Mon Sep 17 00:00:00 2001 From: luoxiao Date: Wed, 22 Jan 2025 23:02:03 +0800 Subject: [PATCH 1/4] feat: Slider adds vertical prop --- thaw/src/slider/docs/mod.md | 11 +++++++ thaw/src/slider/mod.rs | 20 ++++++++++-- thaw/src/slider/slider.css | 62 ++++++++++++++++++++++++++++++------- 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/thaw/src/slider/docs/mod.md b/thaw/src/slider/docs/mod.md index 9dd664cd..ece72a05 100644 --- a/thaw/src/slider/docs/mod.md +++ b/thaw/src/slider/docs/mod.md @@ -18,6 +18,16 @@ view! { } ``` +### Vertical + +```rust demo +let value = RwSignal::new(6.0); + +view! { + +} +``` + ## Slider Label ```rust demo @@ -50,6 +60,7 @@ view! { | min | `Signal` | `0` | Min value of the slider. | | max | `Signal` | `100` | Max value of the slider. | | step | `Signal` | `0` | The step in which value is incremented. | +| vertical | `Signal` | `false` | Render the Slider in a vertical orientation, smallest value on the bottom. | | children | `Option` | `None` | | ### SliderLabel props diff --git a/thaw/src/slider/mod.rs b/thaw/src/slider/mod.rs index 9a2057f5..95e11d55 100644 --- a/thaw/src/slider/mod.rs +++ b/thaw/src/slider/mod.rs @@ -33,6 +33,9 @@ pub fn Slider( /// The step in which value is incremented. #[prop(optional, into)] step: MaybeProp, + /// Render the Slider in a vertical orientation, smallest value on the bottom. + #[prop(optional, into)] + vertical: Signal, #[prop(optional)] children: Option, ) -> impl IntoView { mount_style("slider", include_str!("./slider.css")); @@ -63,7 +66,7 @@ pub fn Slider( let max = max.get(); let min = min.get(); let mut css_vars = format!( - "--thaw-slider--direction: 90deg;--thaw-slider--progress: {:.2}%;", + "--thaw-slider--progress: {:.2}%;", if max == min { 0.0 } else { @@ -71,6 +74,12 @@ pub fn Slider( } ); + if vertical.get() { + css_vars.push_str("--thaw-slider--direction: 0deg;"); + } else { + css_vars.push_str("--thaw-slider--direction: 90deg;"); + } + if is_chldren { css_vars.push_str(&format!("--thaw-slider--max: {:.2};", max)); css_vars.push_str(&format!("--thaw-slider--min: {:.2};", min)); @@ -89,7 +98,14 @@ pub fn Slider( view! { -
+
Date: Wed, 22 Jan 2025 23:07:35 +0800 Subject: [PATCH 2/4] refactor: Slider --- thaw/src/slider/mod.rs | 169 +----------------------- thaw/src/slider/slider/mod.rs | 126 ++++++++++++++++++ thaw/src/slider/{ => slider}/slider.css | 0 thaw/src/slider/slider/types.rs | 46 +++++++ 4 files changed, 174 insertions(+), 167 deletions(-) create mode 100644 thaw/src/slider/slider/mod.rs rename thaw/src/slider/{ => slider}/slider.css (100%) create mode 100644 thaw/src/slider/slider/types.rs diff --git a/thaw/src/slider/mod.rs b/thaw/src/slider/mod.rs index 95e11d55..314ef569 100644 --- a/thaw/src/slider/mod.rs +++ b/thaw/src/slider/mod.rs @@ -1,172 +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, - #[prop(optional, into)] id: MaybeProp, - /// 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, - /// The rules to validate Field. - #[prop(optional, into)] - rules: Vec, - /// The current value of the controlled Slider. - #[prop(optional, into)] - value: Model, - /// Min value of the slider. - #[prop(default = 0f64.into(), into)] - min: Signal, - /// Max value of the slider. - #[prop(default = 100f64.into(), into)] - max: Signal, - /// The step in which value is incremented. - #[prop(optional, into)] - step: MaybeProp, - /// Render the Slider in a vertical orientation, smallest value on the bottom. - #[prop(optional, into)] - vertical: Signal, - #[prop(optional)] children: Option, -) -> 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::() { - 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--progress: {:.2}%;", - if max == min { - 0.0 - } else { - (current_value.get() - min) / (max - min) * 100.0 - } - ); - - if vertical.get() { - css_vars.push_str("--thaw-slider--direction: 0deg;"); - } else { - css_vars.push_str("--thaw-slider--direction: 90deg;"); - } - - 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! { - -
- -
-
- -
{children()}
-
-
-
- } -} - -#[derive(Clone)] -pub(crate) struct SliderInjection { - pub max: Signal, - pub min: Signal, -} - -impl SliderInjection { - pub fn expect_context() -> Self { - expect_context() - } -} - -#[derive(Debug, Default, PartialEq, Clone, Copy)] -pub enum SliderRuleTrigger { - #[default] - Input, -} - -pub struct SliderRule(Rule); - -impl SliderRule { - pub fn validator( - f: impl Fn(&f64, Signal>) -> 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; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/thaw/src/slider/slider/mod.rs b/thaw/src/slider/slider/mod.rs new file mode 100644 index 00000000..468313bc --- /dev/null +++ b/thaw/src/slider/slider/mod.rs @@ -0,0 +1,126 @@ +mod types; + +pub use types::*; + +use crate::{FieldInjection, Rule}; +use leptos::{context::Provider, ev, prelude::*}; +use thaw_components::OptionComp; +use thaw_utils::{class_list, mount_style, Model}; + +#[component] +pub fn Slider( + #[prop(optional, into)] class: MaybeProp, + #[prop(optional, into)] id: MaybeProp, + /// 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, + /// The rules to validate Field. + #[prop(optional, into)] + rules: Vec, + /// The current value of the controlled Slider. + #[prop(optional, into)] + value: Model, + /// Min value of the slider. + #[prop(default = 0f64.into(), into)] + min: Signal, + /// Max value of the slider. + #[prop(default = 100f64.into(), into)] + max: Signal, + /// The step in which value is incremented. + #[prop(optional, into)] + step: MaybeProp, + /// Render the Slider in a vertical orientation, smallest value on the bottom. + #[prop(optional, into)] + vertical: Signal, + #[prop(optional)] children: Option, +) -> 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::() { + 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--progress: {:.2}%;", + if max == min { + 0.0 + } else { + (current_value.get() - min) / (max - min) * 100.0 + } + ); + + if vertical.get() { + css_vars.push_str("--thaw-slider--direction: 0deg;"); + } else { + css_vars.push_str("--thaw-slider--direction: 90deg;"); + } + + 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! { + +
+ +
+
+ +
{children()}
+
+
+
+ } +} diff --git a/thaw/src/slider/slider.css b/thaw/src/slider/slider/slider.css similarity index 100% rename from thaw/src/slider/slider.css rename to thaw/src/slider/slider/slider.css diff --git a/thaw/src/slider/slider/types.rs b/thaw/src/slider/slider/types.rs new file mode 100644 index 00000000..7d986e4e --- /dev/null +++ b/thaw/src/slider/slider/types.rs @@ -0,0 +1,46 @@ +use crate::{FieldValidationState, Rule}; +use leptos::prelude::*; +use std::ops::Deref; + +#[derive(Clone)] +pub(crate) struct SliderInjection { + pub max: Signal, + pub min: Signal, +} + +impl SliderInjection { + pub fn expect_context() -> Self { + expect_context() + } +} + +#[derive(Debug, Default, PartialEq, Clone, Copy)] +pub enum SliderRuleTrigger { + #[default] + Input, +} + +pub struct SliderRule(Rule); + +impl SliderRule { + pub fn validator( + f: impl Fn(&f64, Signal>) -> 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; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} From 4eb712751ec5bc6d0494faa18245c9613d580b68 Mon Sep 17 00:00:00 2001 From: luoxiao Date: Thu, 23 Jan 2025 23:12:44 +0800 Subject: [PATCH 3/4] feat: RangeSlider adds vertical prop --- thaw/src/slider/docs/range-slider.md | 27 +++++-- thaw/src/slider/range_slider/mod.rs | 75 ++++++++++++++----- thaw/src/slider/range_slider/range-slider.css | 64 ++++++++++++---- 3 files changed, 123 insertions(+), 43 deletions(-) diff --git a/thaw/src/slider/docs/range-slider.md b/thaw/src/slider/docs/range-slider.md index c395326e..7c1164d0 100644 --- a/thaw/src/slider/docs/range-slider.md +++ b/thaw/src/slider/docs/range-slider.md @@ -21,6 +21,16 @@ view! { } ``` +### Vertical + +```rust demo +let value = RwSignal::new((6.0, 8.0)); + +view! { + +} +``` + ### SliderLabel ```rust demo @@ -43,11 +53,12 @@ view! { ### RangeSlider Props -| Name | Type | Default | Description | -| ----- | ------------------- | -------------------- | ------------------------------------------- | -| class | `MaybeProp` | `Default::default()` | | -| style | `MaybeProp` | `Default::default()` | | -| value | `Model<(f64, f64)>` | `(0.0, 0.0)` | The current value of the controlled Slider. | -| min | `Signal` | `0` | Min value of the slider. | -| max | `Signal` | `100` | Max value of the slider. | -| step | `Signal` | `0` | The step in which value is incremented. | +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| class | `MaybeProp` | `Default::default()` | | +| style | `MaybeProp` | `Default::default()` | | +| value | `Model<(f64, f64)>` | `(0.0, 0.0)` | The current value of the controlled Slider. | +| min | `Signal` | `0` | Min value of the slider. | +| max | `Signal` | `100` | Max value of the slider. | +| step | `Signal` | `0` | The step in which value is incremented. | +| vertical | `Signal` | `false` | Render the Slider in a vertical orientation, smallest value on the bottom. | diff --git a/thaw/src/slider/range_slider/mod.rs b/thaw/src/slider/range_slider/mod.rs index cb3b0abe..ca0fd0d9 100644 --- a/thaw/src/slider/range_slider/mod.rs +++ b/thaw/src/slider/range_slider/mod.rs @@ -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}; @@ -17,6 +17,9 @@ pub fn RangeSlider( /// The step in which value is incremented. #[prop(optional, into)] step: MaybeProp, + /// Render the Slider in a vertical orientation, smallest value on the bottom. + #[prop(optional, into)] + vertical: Signal, #[prop(optional)] children: Option, ) -> impl IntoView { mount_style("range-slider", include_str!("./range-slider.css")); @@ -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) }); @@ -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(); @@ -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(); @@ -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); @@ -182,7 +215,11 @@ pub fn RangeSlider( view! {
Date: Thu, 23 Jan 2025 23:55:18 +0800 Subject: [PATCH 4/4] feat: SliderLabel supports vertical display --- thaw/src/slider/range_slider/mod.rs | 6 +- thaw/src/slider/range_slider/range-slider.css | 10 +++- thaw/src/slider/slider/mod.rs | 58 ++++++++++--------- thaw/src/slider/slider/slider.css | 8 +++ thaw/src/slider/slider/types.rs | 3 +- thaw/src/slider/slider_label.css | 7 +++ thaw/src/slider/slider_label.rs | 16 ++++- 7 files changed, 76 insertions(+), 32 deletions(-) diff --git a/thaw/src/slider/range_slider/mod.rs b/thaw/src/slider/range_slider/mod.rs index ca0fd0d9..d5684e3b 100644 --- a/thaw/src/slider/range_slider/mod.rs +++ b/thaw/src/slider/range_slider/mod.rs @@ -238,7 +238,11 @@ pub fn RangeSlider( on:mousedown=on_right_mousedown >
- +
{children()}
diff --git a/thaw/src/slider/range_slider/range-slider.css b/thaw/src/slider/range_slider/range-slider.css index 597545b3..daf84b71 100644 --- a/thaw/src/slider/range_slider/range-slider.css +++ b/thaw/src/slider/range_slider/range-slider.css @@ -132,6 +132,14 @@ .thaw-range-slider__datalist { display: block; position: absolute; +} + +.thaw-range-slider--horizontal .thaw-range-slider__datalist { width: 100%; - top: 24px; + top: calc(var(--thaw-slider__thumb--size) + 4px); +} + +.thaw-range-slider--vertical .thaw-range-slider__datalist { + height: 100%; + left: calc(var(--thaw-slider__thumb--size) + 4px); } diff --git a/thaw/src/slider/slider/mod.rs b/thaw/src/slider/slider/mod.rs index 468313bc..137e6714 100644 --- a/thaw/src/slider/slider/mod.rs +++ b/thaw/src/slider/slider/mod.rs @@ -94,33 +94,37 @@ pub fn Slider( }; view! { - -
- -
-
- +
+ +
+
+ +
{children()}
-
-
- + +
+
} } diff --git a/thaw/src/slider/slider/slider.css b/thaw/src/slider/slider/slider.css index e4c35445..c372e8df 100644 --- a/thaw/src/slider/slider/slider.css +++ b/thaw/src/slider/slider/slider.css @@ -181,6 +181,14 @@ .thaw-slider__datalist { display: block; position: absolute; +} + +.thaw-slider--horizontal .thaw-slider__datalist { width: 100%; top: calc(var(--thaw-slider__thumb--size) + 4px); } + +.thaw-slider--vertical .thaw-slider__datalist { + height: 100%; + left: calc(var(--thaw-slider__thumb--size) + 4px); +} diff --git a/thaw/src/slider/slider/types.rs b/thaw/src/slider/slider/types.rs index 7d986e4e..180036fe 100644 --- a/thaw/src/slider/slider/types.rs +++ b/thaw/src/slider/slider/types.rs @@ -2,10 +2,11 @@ use crate::{FieldValidationState, Rule}; use leptos::prelude::*; use std::ops::Deref; -#[derive(Clone)] +#[derive(Clone, Copy)] pub(crate) struct SliderInjection { pub max: Signal, pub min: Signal, + pub vertical: Signal, } impl SliderInjection { diff --git a/thaw/src/slider/slider_label.css b/thaw/src/slider/slider_label.css index 725911cb..8d67e037 100644 --- a/thaw/src/slider/slider_label.css +++ b/thaw/src/slider/slider_label.css @@ -1,5 +1,12 @@ .thaw-slider-label { position: absolute; display: inline-block; +} + +.thaw-slider-label--horizontal { transform: translateX(-50%); } + +.thaw-slider-label--vertical { + transform: translateY(50%); +} diff --git a/thaw/src/slider/slider_label.rs b/thaw/src/slider/slider_label.rs index d8e4cc36..4203e6bb 100644 --- a/thaw/src/slider/slider_label.rs +++ b/thaw/src/slider/slider_label.rs @@ -16,11 +16,23 @@ pub fn SliderLabel( 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) + + if slider.vertical.get() { + format!("bottom: calc({} * (100% - var(--thaw-slider__thumb--size)) + var(--thaw-slider__thumb--size) / 2)", value) + } else { + format!("left: calc({} * (100% - var(--thaw-slider__thumb--size)) + var(--thaw-slider__thumb--size) / 2)", value) + } }; view! { -
+
{children()}