refactor: spinner

This commit is contained in:
luoxiao 2024-06-04 14:18:15 +08:00
parent bba3f00657
commit 18bfea1731
8 changed files with 250 additions and 85 deletions

View file

@ -167,6 +167,14 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
value: "/components/icon".into(), value: "/components/icon".into(),
label: "Icon".into(), label: "Icon".into(),
}, },
MenuItemOption {
value: "/components/spin-button".into(),
label: "Spin Button".into(),
},
MenuItemOption {
value: "/components/spinner".into(),
label: "Spinner".into(),
},
MenuItemOption { MenuItemOption {
value: "/components/tag".into(), value: "/components/tag".into(),
label: "Tag".into(), label: "Tag".into(),
@ -175,10 +183,6 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
value: "/components/text".into(), value: "/components/text".into(),
label: "Text".into(), label: "Text".into(),
}, },
MenuItemOption {
value: "/components/spin-button".into(),
label: "Spin Button".into(),
},
MenuItemOption { MenuItemOption {
value: "/components/auto-complete".into(), value: "/components/auto-complete".into(),
label: "Auto Complete".into(), label: "Auto Complete".into(),
@ -279,10 +283,6 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
value: "/components/progress".into(), value: "/components/progress".into(),
label: "Progress".into(), label: "Progress".into(),
}, },
MenuItemOption {
value: "/components/spinner".into(),
label: "Spinner".into(),
},
MenuItemOption { MenuItemOption {
value: "/components/skeleton".into(), value: "/components/skeleton".into(),
label: "Skeleton".into(), label: "Skeleton".into(),

View file

@ -10,11 +10,15 @@ view! {
```rust demo ```rust demo
view! { view! {
<Space> <Space vertical=true>
<Spinner size=SpinnerSize::Tiny/> <Spinner size=SpinnerSize::ExtraTiny label="Extra Tiny Spinner"/>
<Spinner size=SpinnerSize::Small/> <Spinner size=SpinnerSize::Tiny label="Tiny Spinner"/>
<Spinner size=SpinnerSize::Medium/> <Spinner size=SpinnerSize::ExtraSmall label="Extra Small Spinner"/>
<Spinner size=SpinnerSize::Large/> <Spinner size=SpinnerSize::Small label="Small Spinner"/>
<Spinner size=SpinnerSize::Medium label="Medium Spinner"/>
<Spinner size=SpinnerSize::Large label="Large Spinner"/>
<Spinner size=SpinnerSize::ExtraLarge label="Extra Large Spinner"/>
<Spinner size=SpinnerSize::Huge label="Huge Spinner"/>
</Space> </Space>
} }
``` ```

View file

@ -1,58 +1,72 @@
mod theme;
pub use theme::SpinnerTheme;
use crate::{theme::use_theme, Theme};
use leptos::*; use leptos::*;
use thaw_utils::{class_list, mount_style, OptionalProp}; use thaw_utils::{class_list, mount_style};
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub enum SpinnerSize { pub enum SpinnerSize {
ExtraTiny,
Tiny, Tiny,
ExtraSmall,
Small, Small,
#[default] #[default]
Medium, Medium,
Large, Large,
ExtraLarge,
Huge,
} }
impl SpinnerSize { impl SpinnerSize {
fn theme_height(&self, theme: &Theme) -> String { pub fn as_str(&self) -> &'static str {
match self { match self {
SpinnerSize::Tiny => theme.common.height_tiny.clone(), SpinnerSize::ExtraTiny => "extra-tiny",
SpinnerSize::Small => theme.common.height_small.clone(), SpinnerSize::Tiny => "tiny",
SpinnerSize::Medium => theme.common.height_medium.clone(), SpinnerSize::ExtraSmall => "extra-small",
SpinnerSize::Large => theme.common.height_large.clone(), SpinnerSize::Small => "small",
SpinnerSize::Medium => "medium",
SpinnerSize::Large => "large",
SpinnerSize::ExtraLarge => "extra-large",
SpinnerSize::Huge => "huge",
} }
} }
} }
#[component] #[component]
pub fn Spinner( pub fn Spinner(
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>, #[prop(optional, into)] class: MaybeProp<String>,
#[prop(optional, into)] size: MaybeSignal<SpinnerSize>, /// An optional label for the Spinner.
#[prop(optional, into)]
label: MaybeProp<String>,
/// The size of the spinner.
#[prop(optional, into)]
size: MaybeSignal<SpinnerSize>,
) -> impl IntoView { ) -> impl IntoView {
mount_style("spinner", include_str!("./spinner.css")); mount_style("spinner", include_str!("./spinner.css"));
let theme = use_theme(Theme::light); let id = StoredValue::new(uuid::Uuid::new_v4().to_string());
let css_vars = create_memo(move |_| {
let mut css_vars = String::new(); let spinner_label = label.clone();
theme.with(|theme| { let labelledby = move || spinner_label.with(|_| true).map(|_| id.get_value());
css_vars.push_str(&format!(
"--thaw-height: {};",
size.get().theme_height(theme)
));
css_vars.push_str(&format!(
"--thaw-background-color: {};",
&theme.spinner.background_color
));
css_vars.push_str(&format!("--thaw-color: {};", &theme.common.color_primary));
});
css_vars
});
view! { view! {
<div <div
class=class_list!["thaw-spinner", class.map(| c | move || c.get())] class=class_list!["thaw-spinner", move || format!("thaw-spinner--{}", size.get().as_str()), class]
style=move || css_vars.get() role="progressbar"
></div> aria-labelledby=labelledby
>
<span class="thaw-spinner__spinner">
<span class="thaw-spinner__spinner-tail"></span>
</span>
{
move || {
if let Some(label) = label.get() {
view! {
<label class="thaw-spinner__label" id=id.get_value()>
{label}
</label>
}.into()
} else {
None
}
}
}
</div>
} }
} }

View file

@ -1,17 +1,172 @@
.thaw-spinner { .thaw-spinner {
border-color: var(--thaw-color); display: flex;
border-left-color: var(--thaw-background-color); align-items: center;
border-style: solid; justify-content: center;
border-radius: 100px; line-height: 0;
border-width: 2px; gap: 8px;
width: var(--thaw-height); overflow: hidden;
height: var(--thaw-height);
outline: 1px solid transparent;
animation: 1s linear 0s infinite normal none running spin;
box-sizing: border-box;
} }
@keyframes spin { .thaw-spinner__spinner {
0% { transform: rotate(0deg); } width: 32px;
100% { transform: rotate(360deg); } height: 32px;
position: relative;
flex-shrink: 0;
mask-image: radial-gradient(
closest-side,
transparent calc(100% - var(--thaw-spinner--stroke-width) - 1px),
white calc(100% - var(--thaw-spinner--stroke-width)) calc(100% - 1px),
transparent 100%
);
background-color: var(--colorBrandStroke2Contrast);
color: var(--colorBrandStroke1);
animation-duration: 1.5s;
animation-iteration-count: infinite;
animation-timing-function: linear;
animation-name: thaw-spinner;
--thaw-spinner--stroke-width: var(--strokeWidthThicker);
}
.thaw-spinner--extra-tiny > .thaw-spinner__spinner {
--thaw-spinner--stroke-width: var(--strokeWidthThick);
width: 16px;
height: 16px;
}
.thaw-spinner--tiny > .thaw-spinner__spinner {
--thaw-spinner--stroke-width: var(--strokeWidthThick);
width: 20px;
height: 20px;
}
.thaw-spinner--extra-small > .thaw-spinner__spinner {
--thaw-spinner--stroke-width: var(--strokeWidthThick);
width: 24px;
height: 24px;
}
.thaw-spinner--small > .thaw-spinner__spinner {
--thaw-spinner--stroke-width: var(--strokeWidthThick);
width: 28px;
height: 28px;
}
.thaw-spinner--medium > .thaw-spinner__spinner {
width: 32px;
height: 32px;
}
.thaw-spinner--large > .thaw-spinner__spinner {
width: 36px;
height: 36px;
}
.thaw-spinner--extra-large > .thaw-spinner__spinner {
width: 40px;
height: 40px;
}
.thaw-spinner--huge > .thaw-spinner__spinner {
--thaw-spinner--stroke-width: var(--strokeWidthThickest);
width: 44px;
height: 44px;
}
@keyframes thaw-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.thaw-spinner__spinner-tail {
position: absolute;
display: block;
width: 100%;
height: 100%;
mask-image: conic-gradient(transparent 105deg, white 105deg);
animation-duration: 1.5s;
animation-iteration-count: infinite;
animation-timing-function: var(--curveEasyEase);
animation-name: thaw-spinner-tail;
}
@keyframes thaw-spinner-tail {
0% {
transform: rotate(-135deg);
}
50% {
transform: rotate(0deg);
}
100% {
transform: rotate(225deg);
}
}
.thaw-spinner__spinner-tail::before,
.thaw-spinner__spinner-tail::after {
content: "";
position: absolute;
display: block;
width: 100%;
height: 100%;
animation: inherit;
background-image: conic-gradient(currentcolor 135deg, transparent 135deg);
}
.thaw-spinner__spinner-tail::before {
animation-name: thaw-spinner-tail-before;
}
@keyframes thaw-spinner-tail-before {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(105deg);
}
100% {
transform: rotate(0deg);
}
}
.thaw-spinner__spinner-tail::after {
animation-name: thaw-spinner-tail-after;
}
@keyframes thaw-spinner-tail-after {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(225deg);
}
100% {
transform: rotate(0deg);
}
}
.thaw-spinner__label {
font-family: var(--fontFamilyBase);
font-weight: var(--fontWeightSemibold);
font-size: var(--fontSizeBase400);
line-height: var(--lineHeightBase400);
color: var(--colorNeutralForeground1);
}
.thaw-spinner--extra-tiny > .thaw-spinner__label,
.thaw-spinner--tiny > .thaw-spinner__label,
.thaw-spinner--extra-small > .thaw-spinner__label,
.thaw-spinner--small > .thaw-spinner__label {
font-weight: var(--fontWeightRegular);
font-size: var(--fontSizeBase300);
line-height: var(--lineHeightBase300);
}
.thaw-spinner--huge > .thaw-spinner__label {
font-size: var(--fontSizeBase500);
line-height: var(--lineHeightBase500);
} }

View file

@ -1,20 +0,0 @@
use crate::theme::ThemeMethod;
#[derive(Clone)]
pub struct SpinnerTheme {
pub background_color: String,
}
impl ThemeMethod for SpinnerTheme {
fn light() -> Self {
Self {
background_color: "#0000000a".into(),
}
}
fn dark() -> Self {
Self {
background_color: "#2b2f31".into(),
}
}
}

View file

@ -50,6 +50,9 @@ pub struct ColorTheme {
pub color_brand_background: String, pub color_brand_background: String,
pub color_brand_background_hover: String, pub color_brand_background_hover: String,
pub color_brand_background_pressed: String, pub color_brand_background_pressed: String,
pub color_brand_stroke_1: String,
pub color_brand_stroke_2_contrast: String,
pub color_subtle_background: String, pub color_subtle_background: String,
pub color_subtle_background_hover: String, pub color_subtle_background_hover: String,
pub color_subtle_background_pressed: String, pub color_subtle_background_pressed: String,
@ -113,6 +116,8 @@ impl ColorTheme {
color_brand_background: "#0f6cbd".into(), color_brand_background: "#0f6cbd".into(),
color_brand_background_hover: "#115ea3".into(), color_brand_background_hover: "#115ea3".into(),
color_brand_background_pressed: "#0c3b5e".into(), color_brand_background_pressed: "#0c3b5e".into(),
color_brand_stroke_1: "#0f6cbd".into(),
color_brand_stroke_2_contrast: "#b4d6fa".into(),
color_subtle_background: "transparent".into(), color_subtle_background: "transparent".into(),
color_subtle_background_hover: "#f5f5f5".into(), color_subtle_background_hover: "#f5f5f5".into(),
color_subtle_background_pressed: "#e0e0e0".into(), color_subtle_background_pressed: "#e0e0e0".into(),
@ -176,6 +181,9 @@ impl ColorTheme {
color_brand_background: "#115ea3".into(), color_brand_background: "#115ea3".into(),
color_brand_background_hover: "#0f6cbd".into(), color_brand_background_hover: "#0f6cbd".into(),
color_brand_background_pressed: "#0c3b5e".into(), color_brand_background_pressed: "#0c3b5e".into(),
color_brand_stroke_1: "#479ef5".into(),
color_brand_stroke_2_contrast: "#0e4775".into(),
color_subtle_background: "transparent".into(), color_subtle_background: "transparent".into(),
color_subtle_background_hover: "#383838".into(), color_subtle_background_hover: "#383838".into(),
color_subtle_background_pressed: "#2e2e2e".into(), color_subtle_background_pressed: "#2e2e2e".into(),

View file

@ -34,12 +34,16 @@ pub struct CommonTheme {
pub line_height_base_200: String, pub line_height_base_200: String,
pub line_height_base_300: String, pub line_height_base_300: String,
pub line_height_base_400: String, pub line_height_base_400: String,
pub line_height_base_500: String,
pub font_weight_regular: String, pub font_weight_regular: String,
pub font_weight_semibold: String, pub font_weight_semibold: String,
pub font_weight_bold: String, pub font_weight_bold: String,
pub stroke_width_thin: String, pub stroke_width_thin: String,
pub stroke_width_thick: String,
pub stroke_width_thicker: String,
pub stroke_width_thickest: String,
pub border_radius_none: String, pub border_radius_none: String,
pub border_radius_medium: String, pub border_radius_medium: String,
@ -107,12 +111,16 @@ impl CommonTheme {
line_height_base_200: "16px".into(), line_height_base_200: "16px".into(),
line_height_base_300: "20px".into(), line_height_base_300: "20px".into(),
line_height_base_400: "22px".into(), line_height_base_400: "22px".into(),
line_height_base_500: "28px".into(),
font_weight_regular: "400".into(), font_weight_regular: "400".into(),
font_weight_semibold: "600".into(), font_weight_semibold: "600".into(),
font_weight_bold: "700".into(), font_weight_bold: "700".into(),
stroke_width_thin: "1px".into(), stroke_width_thin: "1px".into(),
stroke_width_thick: "2px".into(),
stroke_width_thicker: "3px".into(),
stroke_width_thickest: "4px".into(),
border_radius_none: "0".into(), border_radius_none: "0".into(),
border_radius_medium: "4px".into(), border_radius_medium: "4px".into(),

View file

@ -4,10 +4,9 @@ mod common;
use self::common::CommonTheme; use self::common::CommonTheme;
use crate::{ use crate::{
mobile::{NavBarTheme, TabbarTheme}, mobile::{NavBarTheme, TabbarTheme},
AlertTheme, AnchorTheme, AutoCompleteTheme, BackTopTheme, CalendarTheme, AlertTheme, AnchorTheme, AutoCompleteTheme, BackTopTheme, CalendarTheme, ColorPickerTheme,
ColorPickerTheme, DatePickerTheme, InputTheme, MessageTheme, PopoverTheme, ProgressTheme, DatePickerTheme, InputTheme, MessageTheme, PopoverTheme, ProgressTheme, ScrollbarTheme,
ScrollbarTheme, SelectTheme, SkeletionTheme, SpinnerTheme, TableTheme, TimePickerTheme, SelectTheme, SkeletionTheme, TableTheme, TimePickerTheme, UploadTheme,
UploadTheme,
}; };
pub use color::ColorTheme; pub use color::ColorTheme;
use leptos::*; use leptos::*;
@ -28,7 +27,6 @@ pub struct Theme {
pub skeletion: SkeletionTheme, pub skeletion: SkeletionTheme,
pub message: MessageTheme, pub message: MessageTheme,
pub select: SelectTheme, pub select: SelectTheme,
pub spinner: SpinnerTheme,
pub upload: UploadTheme, pub upload: UploadTheme,
pub nav_bar: NavBarTheme, pub nav_bar: NavBarTheme,
pub tabbar: TabbarTheme, pub tabbar: TabbarTheme,
@ -56,7 +54,6 @@ impl Theme {
skeletion: SkeletionTheme::light(), skeletion: SkeletionTheme::light(),
message: MessageTheme::light(), message: MessageTheme::light(),
select: SelectTheme::light(), select: SelectTheme::light(),
spinner: SpinnerTheme::light(),
upload: UploadTheme::light(), upload: UploadTheme::light(),
nav_bar: NavBarTheme::light(), nav_bar: NavBarTheme::light(),
tabbar: TabbarTheme::light(), tabbar: TabbarTheme::light(),
@ -83,7 +80,6 @@ impl Theme {
skeletion: SkeletionTheme::dark(), skeletion: SkeletionTheme::dark(),
message: MessageTheme::dark(), message: MessageTheme::dark(),
select: SelectTheme::dark(), select: SelectTheme::dark(),
spinner: SpinnerTheme::dark(),
upload: UploadTheme::dark(), upload: UploadTheme::dark(),
nav_bar: NavBarTheme::dark(), nav_bar: NavBarTheme::dark(),
tabbar: TabbarTheme::dark(), tabbar: TabbarTheme::dark(),