From fc4574f29abe769d136aee064837d58b415ae268 Mon Sep 17 00:00:00 2001 From: luoxiao Date: Mon, 6 May 2024 22:21:17 +0800 Subject: [PATCH] feat: button disabled --- demo/src/components/site_header.rs | 1 - demo_markdown/docs/button/mod.md | 143 +++++++--------- thaw/src/button/button.css | 172 +++++++++---------- thaw/src/button/mod.rs | 204 ++++------------------- thaw/src/button/theme.rs | 50 ------ thaw/src/date_picker/panel/date_panel.rs | 2 +- thaw/src/theme/color.rs | 13 ++ thaw/src/theme/common.rs | 16 +- thaw/src/theme/mod.rs | 15 +- thaw/src/time_picker/mod.rs | 4 +- thaw_utils/src/class_list.rs | 8 +- 11 files changed, 222 insertions(+), 406 deletions(-) delete mode 100644 thaw/src/button/theme.rs diff --git a/demo/src/components/site_header.rs b/demo/src/components/site_header.rs index 2141d39..1aba86e 100644 --- a/demo/src/components/site_header.rs +++ b/demo/src/components/site_header.rs @@ -199,7 +199,6 @@ pub fn SiteHeader() -> impl IntoView { - - - - - - -} -``` - - -### Loading - -```rust demo -let loading = create_rw_signal(false); -let on_click = move |_| { - loading.set(true); - set_timeout( - move || { - loading.set(false); - }, - std::time::Duration::new(2, 0), - ); -}; - -view! { - - - - -} -``` - -### Disabled - -```rust demo -view! { - - - - - - -} -``` - ### Size ```rust demo view! { - - - - - + + + + + + + + + + + + + + + + + +} +``` + +### Disabled + +```rust demo +view! { + + + + + + + + + + + + + } ``` diff --git a/thaw/src/button/button.css b/thaw/src/button/button.css index d9a0c58..6a7fbcc 100644 --- a/thaw/src/button/button.css +++ b/thaw/src/button/button.css @@ -97,99 +97,87 @@ border-radius: var(--borderRadiusNone); } +.thaw-button--small { + min-width: 64px; + + padding: 3px var(--spacingHorizontalS); + + font-size: var(--fontSizeBase200); + line-height: var(--lineHeightBase200); + font-weight: var(--fontWeightRegular); +} + +.thaw-button--large { + min-width: 96px; + + padding: 8px var(--spacingHorizontalL); + + font-size: var(--fontSizeBase400); + line-height: var(--lineHeightBase400); + font-weight: var(--fontWeightSemibold); +} + +.thaw-button--only-icon { + max-width: 32px; + min-width: 32px; + padding: 5px; +} + +.thaw-button:not(.thaw-button--only-icon) .thaw-button__icon { + margin-right: var(--spacingHorizontalSNudge); +} + +.thaw-button__icon { + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 20px; + width: 20px; + height: 20px; +} + +.thaw-button--small.thaw-button--only-icon { + max-width: 24px; + min-width: 24px; + padding: 1px; +} + +.thaw-button--large.thaw-button--only-icon { + max-width: 40px; + min-width: 40px; + padding: 7px; +} +.thaw-button--large.thaw-button--only-icon .thaw-button__icon { + width: 24px; + height: 24px; +} + +.thaw-button--disabled:hover:active, +.thaw-button--disabled:hover, +.thaw-button--disabled { + color: var(--colorNeutralForegroundDisabled); + background-color: var(--colorNeutralBackgroundDisabled); + border-color: var(--colorNeutralStrokeDisabled); + cursor: not-allowed; +} + +.thaw-button--primary.thaw-button--disabled:hover:active, +.thaw-button--primary.thaw-button--disabled:hover, +.thaw-button--primary.thaw-button--disabled { + border-color: transparent; +} + +.thaw-button--subtle.thaw-button--disabled:hover:active, +.thaw-button--subtle.thaw-button--disabled:hover, +.thaw-button--subtle.thaw-button--disabled, +.thaw-button--transparent.thaw-button--disabled:hover:active, +.thaw-button--transparent.thaw-button--disabled:hover, +.thaw-button--transparent.thaw-button--disabled { + background-color: transparent; + border-color: transparent; +} + .thaw-button--block { display: flex; width: 100%; } - -.thaw-button--disabled:not(.thaw-button--text, .thaw-button--link) { - border-color: var(--thaw-border-color-disabled); - background-color: var(--thaw-background-color-disabled); -} - -.thaw-button.thaw-button--disabled { - color: var(--thaw-font-color-disabled); - cursor: not-allowed; -} - -/* .thaw-button:active:not(.thaw-button--disabled) { - transition: all 0.3s; - border-color: var(--thaw-border-color-hover); - background-color: var(--thaw-background-color-active); -} */ - -.thaw-button--outlined { - background-color: transparent; - color: inherit; - transition: all 0.3s; -} - -.thaw-button--outlined:hover:not(.thaw-button--disabled) { - cursor: pointer; - color: var(--thaw-font-color-hover); - border-color: var(--thaw-border-color-hover); -} - -.thaw-button--text, -.thaw-button--link { - border: none; -} - -.thaw-button--link { - background-color: transparent; - color: inherit; - height: auto; - padding: inherit; -} -.thaw-button--text:hover:not(.thaw-button--disabled), -.thaw-button--link:hover:not(.thaw-button--disabled) { - color: var(--thaw-font-color-hover); -} - -.thaw-button--round { - border-radius: var(--thaw-height); -} - -.thaw-button--circle:not(.thaw-button--link) { - width: var(--thaw-height); - padding: 0; - border-radius: var(--thaw-height); -} - -@keyframes thawLoadingCircle { - 100% { - transform: rotate(360deg); - } -} - -.thaw-button .thaw-wave { - pointer-events: none; - animation-iteration-count: 1; - animation-duration: 0.6s; - animation-timing-function: cubic-bezier(0, 0, 0.2, 1), - cubic-bezier(0, 0, 0.2, 1); -} - -.thaw-button .thaw-wave.thaw-wave--active { - opacity: 0; - z-index: 1; - animation-name: thawButtonWaveSpread, thawButtonWaveOpacity; -} - -@keyframes thawButtonWaveSpread { - from { - box-shadow: 0 0 0.5px 0 var(--thaw-ripple-color); - } - to { - box-shadow: 0 0 0.5px 6px var(--thaw-ripple-color); - } -} - -@keyframes thawButtonWaveOpacity { - from { - opacity: 0.6; - } - to { - opacity: 0; - } -} diff --git a/thaw/src/button/mod.rs b/thaw/src/button/mod.rs index 339a926..b76567f 100644 --- a/thaw/src/button/mod.rs +++ b/thaw/src/button/mod.rs @@ -1,13 +1,11 @@ mod button_group; -mod theme; pub use button_group::ButtonGroup; -pub use theme::ButtonTheme; -use crate::{icon::Icon, theme::*}; +use crate::icon::Icon; use leptos::*; -use thaw_components::{OptionComp, Wave, WaveRef}; -use thaw_utils::{class_list, mount_style, ComponentRef, OptionalMaybeSignal, OptionalProp}; +use thaw_components::OptionComp; +use thaw_utils::{class_list, mount_style, OptionalMaybeSignal, OptionalProp}; #[derive(Default, PartialEq, Clone, Copy)] pub enum ButtonAppearance { @@ -34,7 +32,7 @@ pub enum ButtonShape { #[default] Rounded, Circular, - Square + Square, } impl ButtonShape { @@ -56,37 +54,8 @@ pub enum ButtonColor { Error, } -impl ButtonColor { - fn theme_color(&self, theme: &Theme) -> String { - match self { - ButtonColor::Primary => theme.common.color_primary.clone(), - ButtonColor::Success => theme.common.color_success.clone(), - ButtonColor::Warning => theme.common.color_warning.clone(), - ButtonColor::Error => theme.common.color_error.clone(), - } - } - fn theme_color_hover(&self, theme: &Theme) -> String { - match self { - ButtonColor::Primary => theme.common.color_primary_hover.clone(), - ButtonColor::Success => theme.common.color_success_hover.clone(), - ButtonColor::Warning => theme.common.color_warning_hover.clone(), - ButtonColor::Error => theme.common.color_error_hover.clone(), - } - } - - fn theme_color_active(&self, theme: &Theme) -> String { - match self { - ButtonColor::Primary => theme.common.color_primary_active.clone(), - ButtonColor::Success => theme.common.color_success_active.clone(), - ButtonColor::Warning => theme.common.color_warning_active.clone(), - ButtonColor::Error => theme.common.color_error_active.clone(), - } - } -} - -#[derive(Default, Clone)] +#[derive(Default, PartialEq, Clone)] pub enum ButtonSize { - Tiny, Small, #[default] Medium, @@ -94,21 +63,11 @@ pub enum ButtonSize { } impl ButtonSize { - fn theme_height(&self, theme: &Theme) -> String { + fn as_str(&self) -> &str { match self { - ButtonSize::Tiny => theme.common.height_tiny.clone(), - ButtonSize::Small => theme.common.height_small.clone(), - ButtonSize::Medium => theme.common.height_medium.clone(), - ButtonSize::Large => theme.common.height_large.clone(), - } - } - - fn theme_padding(&self, theme: &Theme) -> String { - match self { - ButtonSize::Tiny => theme.button.padding_tiny.clone(), - ButtonSize::Small => theme.button.padding_small.clone(), - ButtonSize::Medium => theme.button.padding_medium.clone(), - ButtonSize::Large => theme.button.padding_large.clone(), + ButtonSize::Small => "small", + ButtonSize::Medium => "medium", + ButtonSize::Large => "large", } } } @@ -122,151 +81,58 @@ pub fn Button( #[prop(optional, into)] color: MaybeSignal, #[prop(optional, into)] size: MaybeSignal, #[prop(optional, into)] block: MaybeSignal, - #[prop(optional, into)] round: MaybeSignal, - #[prop(optional, into)] circle: MaybeSignal, #[prop(optional, into)] icon: OptionalMaybeSignal, - #[prop(optional, into)] loading: MaybeSignal, #[prop(optional, into)] disabled: MaybeSignal, + #[prop(optional, into)] disabled_focusable: MaybeSignal, #[prop(optional, into)] on_click: Option>, #[prop(optional)] children: Option, ) -> impl IntoView { - let theme = use_theme(Theme::light); - let css_vars = create_memo(move |_| { - let mut css_vars = String::new(); - theme.with(|theme| { - let bg_color = color.get().theme_color(theme); - css_vars.push_str(&format!( - "--thaw-font-color-disabled: {};", - theme.button.color_text_disabled - )); - css_vars.push_str(&format!( - "--thaw-height: {};", - size.get().theme_height(theme) - )); - css_vars.push_str(&format!( - "--thaw-padding: {};", - size.get().theme_padding(theme) - )); - - match appearance.get() { - ButtonAppearance::Secondary => {} - ButtonAppearance::Primary => { - let bg_color_hover = color.get().theme_color_hover(theme); - let bg_color_active = color.get().theme_color_active(theme); - css_vars.push_str(&format!("--thaw-background-color: {bg_color};")); - css_vars.push_str(&format!("--thaw-background-color-hover: {bg_color_hover};")); - css_vars.push_str(&format!( - "--thaw-background-color-active: {bg_color_active};" - )); - css_vars.push_str("--thaw-font-color: #fff;"); - css_vars.push_str(&format!("--thaw-border-color: {bg_color};")); - css_vars.push_str(&format!("--thaw-border-color-hover: {bg_color};")); - css_vars.push_str(&format!("--thaw-ripple-color: {bg_color};")); - - css_vars.push_str(&format!( - "--thaw-background-color-disabled: {};", - theme.button.color_background_disabled - )); - css_vars.push_str(&format!( - "--thaw-border-color-disabled: {};", - theme.button.color_border_disabled - )); - } - ButtonAppearance::Subtle => { - css_vars.push_str(&format!("--thaw-font-color-hover: {bg_color};")); - css_vars.push_str(&format!( - "--thaw-background-color-hover: {};", - theme.button.color_text_hover - )); - css_vars.push_str(&format!( - "--thaw-background-color-active: {};", - theme.button.color_text_active - )); - css_vars.push_str("--thaw-ripple-color: #0000;"); - } - ButtonAppearance::Transparent => { - css_vars.push_str(&format!("--thaw-font-color-hover: {bg_color};")); - css_vars.push_str("--thaw-ripple-color: #0000;"); - } - } - }); - - css_vars - }); mount_style("button", include_str!("./button.css")); - let icon_style = if children.is_some() { - "margin-right: var(--spacingHorizontalSNudge)" - } else { - "" - }; + let none_children = children.is_none(); + let only_icon = Memo::new(move |_| icon.with(|i| i.is_some()) && none_children); + let btn_disabled = Memo::new(move |_| disabled.get() || disabled_focusable.get()); - let disabled = create_memo(move |_| { - if loading.get() { - return true; - } - - disabled.get() - }); - - let wave_ref = ComponentRef::::default(); - - let on_click = move |event| { - if disabled.get() { + let on_click = move |e| { + if btn_disabled.get_untracked() { return; } - if let Some(wave_ref) = wave_ref.get_untracked() { - wave_ref.play(); - } + let Some(callback) = on_click.as_ref() else { return; }; - callback.call(event); + callback.call(e); }; view! { diff --git a/thaw/src/theme/color.rs b/thaw/src/theme/color.rs index 5162a46..408f0c7 100644 --- a/thaw/src/theme/color.rs +++ b/thaw/src/theme/color.rs @@ -2,9 +2,12 @@ use thaw_macro::WriteCSSVars; #[derive(Clone, WriteCSSVars)] pub struct ColorTheme { + pub color_neutral_background_disabled: String, pub color_neutral_background_1: String, pub color_neutral_background_1_hover: String, pub color_neutral_background_1_pressed: String, + + pub color_neutral_foreground_disabled: String, pub color_neutral_foreground_1: String, pub color_neutral_foreground_1_hover: String, pub color_neutral_foreground_1_pressed: String, @@ -14,6 +17,8 @@ pub struct ColorTheme { pub color_neutral_foreground_2_brand_hover: String, pub color_neutral_foreground_2_brand_pressed: String, pub color_neutral_foreground_on_brand: String, + + pub color_neutral_stroke_disabled: String, pub color_neutral_stroke_1: String, pub color_neutral_stroke_1_hover: String, pub color_neutral_stroke_1_pressed: String, @@ -31,9 +36,11 @@ pub struct ColorTheme { impl ColorTheme { pub fn light() -> Self { Self { + color_neutral_background_disabled: "#f0f0f0".into(), color_neutral_background_1: "#fff".into(), color_neutral_background_1_hover: "#f5f5f5".into(), color_neutral_background_1_pressed: "#e0e0e0".into(), + color_neutral_foreground_disabled: "#bdbdbd".into(), color_neutral_foreground_1: "#242424".into(), color_neutral_foreground_1_hover: "#242424".into(), color_neutral_foreground_1_pressed: "#242424".into(), @@ -43,6 +50,8 @@ impl ColorTheme { color_neutral_foreground_2_brand_hover: "#0f6cbd".into(), color_neutral_foreground_2_brand_pressed: "#115ea3".into(), color_neutral_foreground_on_brand: "#fff".into(), + + color_neutral_stroke_disabled: "#e0e0e0".into(), color_neutral_stroke_1: "#d1d1d1".into(), color_neutral_stroke_1_hover: "#c7c7c7".into(), color_neutral_stroke_1_pressed: "#b3b3b3".into(), @@ -60,9 +69,11 @@ impl ColorTheme { pub fn dark() -> Self { Self { + color_neutral_background_disabled: "#141414".into(), color_neutral_background_1: "#292929".into(), color_neutral_background_1_hover: "#3d3d3d".into(), color_neutral_background_1_pressed: "#1f1f1f".into(), + color_neutral_foreground_disabled: "#5c5c5c".into(), color_neutral_foreground_1: "#fff".into(), color_neutral_foreground_1_hover: "#fff".into(), color_neutral_foreground_1_pressed: "#fff".into(), @@ -72,6 +83,8 @@ impl ColorTheme { color_neutral_foreground_2_brand_hover: "#479ef5".into(), color_neutral_foreground_2_brand_pressed: "#2886de".into(), color_neutral_foreground_on_brand: "#fff".into(), + + color_neutral_stroke_disabled: "#424242".into(), color_neutral_stroke_1: "#666666".into(), color_neutral_stroke_1_hover: "#757575".into(), color_neutral_stroke_1_pressed: "#6b6b6b".into(), diff --git a/thaw/src/theme/common.rs b/thaw/src/theme/common.rs index 6aa459d..51127dd 100644 --- a/thaw/src/theme/common.rs +++ b/thaw/src/theme/common.rs @@ -20,9 +20,13 @@ pub struct CommonTheme { pub color_error_hover: String, pub color_error_active: String, + pub font_size_base_200: String, pub font_size_base_300: String, + pub font_size_base_400: String, - pub line_height_base300: String, + pub line_height_base_200: String, + pub line_height_base_300: String, + pub line_height_base_400: String, pub font_weight_regular: String, pub font_weight_semibold: String, @@ -35,7 +39,9 @@ pub struct CommonTheme { pub border_radius_circular: String, pub spacing_horizontal_s_nudge: String, + pub spacing_horizontal_s: String, pub spacing_horizontal_m: String, + pub spacing_horizontal_l: String, pub duration_faster: String, pub curve_easy_ease: String, @@ -69,9 +75,13 @@ impl CommonTheme { color_error_hover: "".into(), color_error_active: "".into(), + font_size_base_200: "12px".into(), font_size_base_300: "14px".into(), + font_size_base_400: "16px".into(), - line_height_base300: "20px".into(), + line_height_base_200: "16px".into(), + line_height_base_300: "20px".into(), + line_height_base_400: "22px".into(), font_weight_regular: "400".into(), font_weight_semibold: "600".into(), @@ -84,7 +94,9 @@ impl CommonTheme { border_radius_circular: "10000px".into(), spacing_horizontal_s_nudge: "6px".into(), + spacing_horizontal_s: "8px".into(), spacing_horizontal_m: "12px".into(), + spacing_horizontal_l: "16px".into(), duration_faster: "100ms".into(), curve_easy_ease: "cubic-bezier(0.33,0,0.67,1)".into(), diff --git a/thaw/src/theme/mod.rs b/thaw/src/theme/mod.rs index 1bf8d54..e270590 100644 --- a/thaw/src/theme/mod.rs +++ b/thaw/src/theme/mod.rs @@ -1,16 +1,16 @@ -mod common; mod color; +mod common; use self::common::CommonTheme; -pub use color::ColorTheme; use crate::{ mobile::{NavBarTheme, TabbarTheme}, AlertTheme, AnchorTheme, AutoCompleteTheme, AvatarTheme, BackTopTheme, BreadcrumbTheme, - ButtonTheme, CalendarTheme, CollapseTheme, ColorPickerTheme, DatePickerTheme, InputTheme, - MenuTheme, MessageTheme, PopoverTheme, ProgressTheme, ScrollbarTheme, SelectTheme, - SkeletionTheme, SliderTheme, SpinnerTheme, SwitchTheme, TableTheme, TagTheme, TimePickerTheme, - TypographyTheme, UploadTheme, + CalendarTheme, CollapseTheme, ColorPickerTheme, DatePickerTheme, InputTheme, MenuTheme, + MessageTheme, PopoverTheme, ProgressTheme, ScrollbarTheme, SelectTheme, SkeletionTheme, + SliderTheme, SpinnerTheme, SwitchTheme, TableTheme, TagTheme, TimePickerTheme, TypographyTheme, + UploadTheme, }; +pub use color::ColorTheme; use leptos::*; pub trait ThemeMethod { @@ -23,7 +23,6 @@ pub struct Theme { pub name: String, pub common: CommonTheme, pub color: ColorTheme, - pub button: ButtonTheme, pub input: InputTheme, pub menu: MenuTheme, pub table: TableTheme, @@ -60,7 +59,6 @@ impl Theme { name: "light".into(), common: CommonTheme::light(), color: ColorTheme::light(), - button: ButtonTheme::light(), input: InputTheme::light(), menu: MenuTheme::light(), table: TableTheme::light(), @@ -96,7 +94,6 @@ impl Theme { name: "dark".into(), common: CommonTheme::dark(), color: ColorTheme::dark(), - button: ButtonTheme::dark(), input: InputTheme::dark(), menu: MenuTheme::dark(), table: TableTheme::dark(), diff --git a/thaw/src/time_picker/mod.rs b/thaw/src/time_picker/mod.rs index 027a7bf..51009ff 100644 --- a/thaw/src/time_picker/mod.rs +++ b/thaw/src/time_picker/mod.rs @@ -286,10 +286,10 @@ fn Panel( diff --git a/thaw_utils/src/class_list.rs b/thaw_utils/src/class_list.rs index 567beac..773d3f8 100644 --- a/thaw_utils/src/class_list.rs +++ b/thaw_utils/src/class_list.rs @@ -1,6 +1,6 @@ #[cfg(not(feature = "ssr"))] use leptos::create_render_effect; -use leptos::{Attribute, IntoAttribute, Oco, RwSignal, SignalUpdate, SignalWith}; +use leptos::{Attribute, IntoAttribute, Memo, Oco, RwSignal, SignalGet, SignalUpdate, SignalWith}; use std::{collections::HashSet, rc::Rc}; pub struct ClassList(RwSignal>>); @@ -163,6 +163,12 @@ where } } +impl IntoClass for (&'static str, Memo) { + fn into_class(self) -> Class { + Class::Fn(self.0.into(), Box::new(move || self.1.get())) + } +} + impl IntoClass for (String, T) where T: Fn() -> bool + 'static,