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

View file

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

View file

@ -1,58 +1,72 @@
mod theme;
pub use theme::SpinnerTheme;
use crate::{theme::use_theme, Theme};
use leptos::*;
use thaw_utils::{class_list, mount_style, OptionalProp};
use thaw_utils::{class_list, mount_style};
#[derive(Default, Clone)]
pub enum SpinnerSize {
ExtraTiny,
Tiny,
ExtraSmall,
Small,
#[default]
Medium,
Large,
ExtraLarge,
Huge,
}
impl SpinnerSize {
fn theme_height(&self, theme: &Theme) -> String {
pub fn as_str(&self) -> &'static str {
match self {
SpinnerSize::Tiny => theme.common.height_tiny.clone(),
SpinnerSize::Small => theme.common.height_small.clone(),
SpinnerSize::Medium => theme.common.height_medium.clone(),
SpinnerSize::Large => theme.common.height_large.clone(),
SpinnerSize::ExtraTiny => "extra-tiny",
SpinnerSize::Tiny => "tiny",
SpinnerSize::ExtraSmall => "extra-small",
SpinnerSize::Small => "small",
SpinnerSize::Medium => "medium",
SpinnerSize::Large => "large",
SpinnerSize::ExtraLarge => "extra-large",
SpinnerSize::Huge => "huge",
}
}
}
#[component]
pub fn Spinner(
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
#[prop(optional, into)] size: MaybeSignal<SpinnerSize>,
#[prop(optional, into)] class: MaybeProp<String>,
/// 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 {
mount_style("spinner", include_str!("./spinner.css"));
let theme = use_theme(Theme::light);
let css_vars = create_memo(move |_| {
let mut css_vars = String::new();
theme.with(|theme| {
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
});
let id = StoredValue::new(uuid::Uuid::new_v4().to_string());
let spinner_label = label.clone();
let labelledby = move || spinner_label.with(|_| true).map(|_| id.get_value());
view! {
<div
class=class_list!["thaw-spinner", class.map(| c | move || c.get())]
style=move || css_vars.get()
></div>
class=class_list!["thaw-spinner", move || format!("thaw-spinner--{}", size.get().as_str()), class]
role="progressbar"
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 {
border-color: var(--thaw-color);
border-left-color: var(--thaw-background-color);
border-style: solid;
border-radius: 100px;
border-width: 2px;
width: var(--thaw-height);
height: var(--thaw-height);
outline: 1px solid transparent;
animation: 1s linear 0s infinite normal none running spin;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
line-height: 0;
gap: 8px;
overflow: hidden;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
.thaw-spinner__spinner {
width: 32px;
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_hover: 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_hover: String,
pub color_subtle_background_pressed: String,
@ -113,6 +116,8 @@ impl ColorTheme {
color_brand_background: "#0f6cbd".into(),
color_brand_background_hover: "#115ea3".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_hover: "#f5f5f5".into(),
color_subtle_background_pressed: "#e0e0e0".into(),
@ -176,6 +181,9 @@ impl ColorTheme {
color_brand_background: "#115ea3".into(),
color_brand_background_hover: "#0f6cbd".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_hover: "#383838".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_300: String,
pub line_height_base_400: String,
pub line_height_base_500: String,
pub font_weight_regular: String,
pub font_weight_semibold: String,
pub font_weight_bold: 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_medium: String,
@ -107,12 +111,16 @@ impl CommonTheme {
line_height_base_200: "16px".into(),
line_height_base_300: "20px".into(),
line_height_base_400: "22px".into(),
line_height_base_500: "28px".into(),
font_weight_regular: "400".into(),
font_weight_semibold: "600".into(),
font_weight_bold: "700".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_medium: "4px".into(),

View file

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