From 7d99f9bd0971a139c89663712ca1b714fc1ad176 Mon Sep 17 00:00:00 2001 From: luoxiao Date: Wed, 22 May 2024 17:28:20 +0800 Subject: [PATCH] feat: SpinButton --- demo/src/app.rs | 1 + demo/src/pages/components.rs | 4 + demo_markdown/docs/spin_button/mod.md | 13 +++ demo_markdown/src/lib.rs | 1 + thaw/src/calendar/mod.rs | 2 +- thaw/src/input/input.css | 2 +- thaw/src/lib.rs | 2 + thaw/src/spin_button/mod.rs | 110 +++++++++++++++++++++++ thaw/src/spin_button/spin-button.css | 123 ++++++++++++++++++++++++++ thaw/src/theme/color.rs | 3 + 10 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 demo_markdown/docs/spin_button/mod.md create mode 100644 thaw/src/spin_button/mod.rs create mode 100644 thaw/src/spin_button/spin-button.css diff --git a/demo/src/app.rs b/demo/src/app.rs index 053d00c..fd2a8d9 100644 --- a/demo/src/app.rs +++ b/demo/src/app.rs @@ -80,6 +80,7 @@ fn TheRouter(is_routing: RwSignal) -> impl IntoView { + diff --git a/demo/src/pages/components.rs b/demo/src/pages/components.rs index 65ac9b8..af96c43 100644 --- a/demo/src/pages/components.rs +++ b/demo/src/pages/components.rs @@ -140,6 +140,10 @@ pub(crate) fn gen_menu_data() -> Vec { value: "typography".into(), label: "Typography".into(), }, + MenuItemOption { + value: "spin-button".into(), + label: "Spin Button".into(), + }, ], }, MenuGroupOption { diff --git a/demo_markdown/docs/spin_button/mod.md b/demo_markdown/docs/spin_button/mod.md new file mode 100644 index 0000000..8204702 --- /dev/null +++ b/demo_markdown/docs/spin_button/mod.md @@ -0,0 +1,13 @@ +# SpinButton + +```rust demo +let value = RwSignal::new(0); +let value_f64 = RwSignal::new(0.0); + +view! { + + + + +} +``` \ No newline at end of file diff --git a/demo_markdown/src/lib.rs b/demo_markdown/src/lib.rs index 63fb01d..c1a78ee 100644 --- a/demo_markdown/src/lib.rs +++ b/demo_markdown/src/lib.rs @@ -62,6 +62,7 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt "SkeletonMdPage" => "../docs/skeleton/mod.md", "SliderMdPage" => "../docs/slider/mod.md", "SpaceMdPage" => "../docs/space/mod.md", + "SpinButtonMdPage" => "../docs/spin_button/mod.md", "SpinnerMdPage" => "../docs/spinner/mod.md", "SwitchMdPage" => "../docs/switch/mod.md", "TableMdPage" => "../docs/table/mod.md", diff --git a/thaw/src/calendar/mod.rs b/thaw/src/calendar/mod.rs index 24c265f..98efb6f 100644 --- a/thaw/src/calendar/mod.rs +++ b/thaw/src/calendar/mod.rs @@ -2,7 +2,7 @@ mod theme; pub use theme::CalendarTheme; -use crate::{use_theme, Button, ButtonGroup, ButtonAppearance, Theme}; +use crate::{use_theme, Button, ButtonGroup, Theme}; use chrono::{Datelike, Days, Local, Month, Months, NaiveDate}; use leptos::*; use std::ops::Deref; diff --git a/thaw/src/input/input.css b/thaw/src/input/input.css index ec9adca..aa9ae1d 100644 --- a/thaw/src/input/input.css +++ b/thaw/src/input/input.css @@ -58,7 +58,7 @@ transition-delay: var(--curveDecelerateMid); } -.r1qp7m7v:focus-within:active::after { +.thaw-input:focus-within:active::after { border-bottom-color: var(--colorCompoundBrandStrokePressed); } diff --git a/thaw/src/lib.rs b/thaw/src/lib.rs index 1ea8c51..9b4455d 100644 --- a/thaw/src/lib.rs +++ b/thaw/src/lib.rs @@ -36,6 +36,7 @@ mod select; mod skeleton; mod slider; mod space; +mod spin_button; mod spinner; mod switch; mod table; @@ -83,6 +84,7 @@ pub use select::*; pub use skeleton::*; pub use slider::*; pub use space::*; +pub use spin_button::*; pub use spinner::*; pub use switch::*; pub use table::*; diff --git a/thaw/src/spin_button/mod.rs b/thaw/src/spin_button/mod.rs new file mode 100644 index 0000000..e6aa277 --- /dev/null +++ b/thaw/src/spin_button/mod.rs @@ -0,0 +1,110 @@ +use leptos::*; +use num_traits::Bounded; +use std::ops::{Add, Sub}; +use std::str::FromStr; +use thaw_utils::{mount_style, Model, StoredMaybeSignal}; + +#[component] +pub fn SpinButton( + #[prop(optional, into)] value: Model, + #[prop(into)] step_page: MaybeSignal, + #[prop(default = T::min_value().into(), into)] min: MaybeSignal, + #[prop(default = T::max_value().into(), into)] max: MaybeSignal, + #[prop(optional, into)] disabled: MaybeSignal, +) -> impl IntoView +where + T: Add + Sub + PartialOrd + Bounded, + T: Default + Clone + FromStr + ToString + 'static, +{ + mount_style("spin-button", include_str!("./spin-button.css")); + + let initialization_value = value.get_untracked().to_string(); + let step_page: StoredMaybeSignal<_> = step_page.into(); + let min: StoredMaybeSignal<_> = min.into(); + let max: StoredMaybeSignal<_> = max.into(); + let input_value = RwSignal::new(String::new()); + + Effect::new_isomorphic(move |prev| { + value.with(|value| { + if let Some(prev) = prev { + if value == &prev { + return prev; + } + } + input_value.set(value.to_string()); + value.clone() + }) + }); + + let update_value = move |new_value| { + let min = min.get_untracked(); + let max = max.get_untracked(); + if new_value < min { + value.set(min); + } else if new_value > max { + value.set(max); + } else if with!(|value| value != &new_value) { + value.set(new_value); + } + }; + + let increment_disabled = Memo::new(move |_| disabled.get() || value.get() <= min.get()); + let decrement_disabled = Memo::new(move |_| disabled.get() || value.get() >= max.get()); + + view! { + + () else { + input_value.update(|_| {}); + return; + }; + update_value(v); + } + /> + + + + } +} diff --git a/thaw/src/spin_button/spin-button.css b/thaw/src/spin_button/spin-button.css new file mode 100644 index 0000000..190ed33 --- /dev/null +++ b/thaw/src/spin_button/spin-button.css @@ -0,0 +1,123 @@ +.thaw-spin-button { + display: inline-grid; + grid-template-columns: 1fr 24px; + grid-template-rows: 1fr 1fr; + column-gap: var(--spacingHorizontalXS); + row-gap: 0px; + position: relative; + isolation: isolate; + background-color: var(--colorNeutralBackground1); + min-height: 32px; + padding: 0 0 0 var(--spacingHorizontalMNudge); + + border: 1px solid var(--colorNeutralStroke1); + border-bottom-color: var(--colorNeutralStrokeAccessible); + border-radius: var(--borderRadiusMedium); +} + +.thaw-spin-button:hover { + border-color: var(--colorNeutralStroke1Hover); + border-bottom-color: var(--colorNeutralStrokeAccessibleHover); +} + +.thaw-spin-button:focus-within { + outline: transparent solid 2px; +} + +.thaw-spin-button:active, +.thaw-spin-button:focus-within { + border-color: var(--colorNeutralStroke1Pressed); + border-bottom-color: var(--colorNeutralStrokeAccessiblePressed); +} + +.thaw-spin-button::after { + box-sizing: border-box; + content: ""; + position: absolute; + left: -1px; + bottom: -1px; + right: -1px; + height: max(2px, var(--borderRadiusMedium)); + border-bottom-left-radius: var(--borderRadiusMedium); + border-bottom-right-radius: var(--borderRadiusMedium); + border-bottom: 2px solid var(--colorCompoundBrandStroke); + clip-path: inset(calc(100% - 2px) 0px 0px); + transform: scaleX(0); + transition-property: transform; + transition-duration: var(--durationUltraFast); + transition-delay: var(--curveAccelerateMid); +} + +.thaw-spin-button:focus-within::after { + transform: scaleX(1); + transition-property: transform; + transition-duration: var(--durationNormal); + transition-delay: var(--curveDecelerateMid); +} + +.thaw-spin-button:focus-within:active::after { + border-bottom-color: var(--colorCompoundBrandStrokePressed); +} + +.thaw-spin-button__input { + grid-area: 1 / 1 / 3 / 2; + outline-style: none; + border: 0px; + padding: 0px; + color: var(--colorNeutralForeground1); + background-color: transparent; + font-family: inherit; + font-size: inherit; + font-weight: inherit; + line-height: inherit; + width: 100%; +} + +.thaw-spin-button__increment-button, +.thaw-spin-button__decrement-button { + display: inline-flex; + width: 24px; + align-items: center; + justify-content: center; + border: 0px; + position: absolute; + outline-style: none; + height: 16px; + background-color: transparent; + color: var(--colorNeutralForeground3); + grid-column-start: 2; + border-radius: 0px; + padding: 0px 5px; +} + +.thaw-spin-button__increment-button:enabled:hover, +.thaw-spin-button__decrement-button:enabled:hover { + cursor: pointer; + color: var(--colorNeutralForeground3Hover); + background-color: var(--colorSubtleBackgroundHover); +} + +.thaw-spin-button__increment-button:enabled:active, +.thaw-spin-button__decrement-button:enabled:active { + color: var(--colorNeutralForeground3Pressed); + background-color: var(--colorSubtleBackgroundPressed); +} + +.thaw-spin-button__increment-button:active, +.thaw-spin-button__decrement-button:active { + outline-style: none; +} + +.thaw-spin-button__increment-button { + grid-row-start: 1; + padding-top: 4px; + padding-bottom: 1px; + border-top-right-radius: var(--borderRadiusMedium); +} + +.thaw-spin-button__decrement-button { + padding-bottom: 4px; + padding-top: 1px; + grid-row-start: 2; + border-bottom-right-radius: var(--borderRadiusMedium); +} diff --git a/thaw/src/theme/color.rs b/thaw/src/theme/color.rs index 185b61e..aa07a79 100644 --- a/thaw/src/theme/color.rs +++ b/thaw/src/theme/color.rs @@ -39,6 +39,7 @@ pub struct ColorTheme { pub color_compound_brand_background_hover: String, pub color_compound_brand_background_pressed: String, pub color_compound_brand_stroke: String, + pub color_compound_brand_stroke_pressed: String, pub color_brand_background: String, pub color_brand_background_hover: String, @@ -93,6 +94,7 @@ impl ColorTheme { color_compound_brand_background_hover: "#115ea3".into(), color_compound_brand_background_pressed: "#0f548c".into(), color_compound_brand_stroke: "#0f6cbd".into(), + color_compound_brand_stroke_pressed: "#0f548c".into(), color_brand_background: "#0f6cbd".into(), color_brand_background_hover: "#115ea3".into(), @@ -147,6 +149,7 @@ impl ColorTheme { color_compound_brand_background_hover: "#62abf5".into(), color_compound_brand_background_pressed: "#2886de".into(), color_compound_brand_stroke: "#479ef5".into(), + color_compound_brand_stroke_pressed: "#2886de".into(), color_brand_background: "#115ea3".into(), color_brand_background_hover: "#0f6cbd".into(),