Merge pull request #5 from thaw-ui/feat/loading-bar

Feat/loading bar
This commit is contained in:
luoxiaozero 2023-11-07 16:11:21 +08:00 committed by GitHub
commit f0e56c065f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 298 additions and 42 deletions

@ -1 +0,0 @@
Subproject commit faaa4dfa5547b4c3e117999f4ed4cf2332006f2b

View file

@ -24,57 +24,75 @@ pub fn App() -> impl IntoView {
provide_context(theme); provide_context(theme);
view! { view! {
<Provider theme> <Provider theme>
<Router base="/thaw"> <TheRouter />
<Routes base="/thaw".to_string()>
<Route path="/" view=Home/>
<Route path="/components" view=ComponentsPage>
<Route path="/menu" view=MenuPage/>
<Route path="/slider" view=SliderPage/>
<Route path="/tabbar" view=TabbarPage/>
<Route path="/nav-bar" view=NavBarPage/>
<Route path="/input" view=InputPage/>
<Route path="/image" view=ImagePage/>
<Route path="/modal" view=ModalPage/>
<Route path="/button" view=ButtonPage/>
<Route path="/checkbox" view=CheckboxPage/>
<Route path="/toast" view=ToastPage/>
<Route path="/tabs" view=TabsPage/>
<Route path="/select" view=SelectPage/>
<Route path="/space" view=SpacePage/>
<Route path="/table" view=TablePage/>
<Route path="/color-picker" view=ColorPickerPage/>
<Route path="/alert" view=AlertPage/>
<Route path="/grid" view=GridPage/>
<Route path="/auto-complete" view=AutoCompletePage/>
<Route path="/avatar" view=AvatarPage/>
<Route path="/badge" view=BadgePage/>
<Route path="/card" view=CardPage/>
<Route path="/divider" view=DividerPage/>
<Route path="/input-number" view=InputNumberPage/>
<Route path="/icon" view=IconPage/>
<Route path="/message" view=MessagePage/>
<Route path="/radio" view=RadioPage/>
<Route path="/skeleton" view=SkeletonPage/>
<Route path="/switch" view=SwitchPage/>
<Route path="/tag" view=TagPage/>
<Route path="/upload" view=UploadPage/>
</Route>
<Route path="/mobile/tabbar" view=TabbarDemoPage/>
<Route path="/mobile/nav-bar" view=NavBarDemoPage/>
<Route path="/mobile/toast" view=ToastDemoPage/>
</Routes>
</Router>
</Provider> </Provider>
} }
} }
#[component]
fn TheRouter() -> impl IntoView {
let loading_bar = use_loading_bar();
let set_is_routing = SignalSetter::map(move |is_routing| {
if is_routing {
loading_bar.start();
} else {
loading_bar.finish();
}
});
view! {
<Router base="/thaw" set_is_routing>
<Routes base="/thaw".to_string()>
<Route path="/" view=Home/>
<Route path="/components" view=ComponentsPage>
<Route path="/menu" view=MenuPage/>
<Route path="/slider" view=SliderPage/>
<Route path="/tabbar" view=TabbarPage/>
<Route path="/nav-bar" view=NavBarPage/>
<Route path="/input" view=InputPage/>
<Route path="/image" view=ImagePage/>
<Route path="/modal" view=ModalPage/>
<Route path="/button" view=ButtonPage/>
<Route path="/checkbox" view=CheckboxPage/>
<Route path="/toast" view=ToastPage/>
<Route path="/tabs" view=TabsPage/>
<Route path="/select" view=SelectPage/>
<Route path="/space" view=SpacePage/>
<Route path="/table" view=TablePage/>
<Route path="/color-picker" view=ColorPickerPage/>
<Route path="/alert" view=AlertPage/>
<Route path="/grid" view=GridPage/>
<Route path="/auto-complete" view=AutoCompletePage/>
<Route path="/avatar" view=AvatarPage/>
<Route path="/badge" view=BadgePage/>
<Route path="/card" view=CardPage/>
<Route path="/divider" view=DividerPage/>
<Route path="/input-number" view=InputNumberPage/>
<Route path="/icon" view=IconPage/>
<Route path="/message" view=MessagePage/>
<Route path="/radio" view=RadioPage/>
<Route path="/skeleton" view=SkeletonPage/>
<Route path="/switch" view=SwitchPage/>
<Route path="/tag" view=TagPage/>
<Route path="/upload" view=UploadPage/>
<Route path="/loading-bar" view=LoadingBarPage/>
</Route>
<Route path="/mobile/tabbar" view=TabbarDemoPage/>
<Route path="/mobile/nav-bar" view=NavBarDemoPage/>
<Route path="/mobile/toast" view=ToastDemoPage/>
</Routes>
</Router>
}
}
#[component] #[component]
fn Provider(theme: RwSignal<Theme>, children: Children) -> impl IntoView { fn Provider(theme: RwSignal<Theme>, children: Children) -> impl IntoView {
view! { view! {
<ThemeProvider theme> <ThemeProvider theme>
<GlobalStyle /> <GlobalStyle />
<MessageProvider> <MessageProvider>
{children()} <LoadingBarProvider>
{children()}
</LoadingBarProvider>
</MessageProvider> </MessageProvider>
</ThemeProvider> </ThemeProvider>
} }

View file

@ -167,6 +167,10 @@ fn gen_menu_data() -> Vec<MenuGroupOption> {
MenuGroupOption { MenuGroupOption {
label: "Navigation Components".into(), label: "Navigation Components".into(),
children: vec![ children: vec![
MenuItemOption {
value: "loading-bar".into(),
label: "Loading Bar".into(),
},
MenuItemOption { MenuItemOption {
value: "menu".into(), value: "menu".into(),
label: "Menu".into(), label: "Menu".into(),

View file

@ -0,0 +1,62 @@
use crate::components::{Demo, DemoCode};
use leptos::*;
use prisms::highlight_str;
use thaw::*;
#[component]
pub fn LoadingBarPage() -> impl IntoView {
let loading_bar = use_loading_bar();
let start = move |_| {
loading_bar.start();
};
let finish = move |_| {
loading_bar.finish();
};
let error = move |_| {
loading_bar.error();
};
view! {
<div style="width: 896px; margin: 0 auto;">
<h1>"Loading Bar"</h1>
<Alert variant=AlertVariant::Warning title="Prerequisite">
"If you want to use loading bar, you need to wrap the component where you call related methods inside LoadingBarProvider and use use_loading_bar to get the API."
</Alert>
<Demo>
<Space>
<Button on_click=start>"start"</Button>
<Button on_click=finish>"finish"</Button>
<Button on_click=error>"error"</Button>
</Space>
<DemoCode
slot
html=highlight_str!(
r#"
let loading_bar = use_loading_bar();
let start = move |_| {
loading_bar.start();
};
let finish = move |_| {
loading_bar.finish();
};
let error = move |_| {
loading_bar.error();
};
view! {
<Space>
<Button on_click=start>"start"</Button>
<Button on_click=finish>"finish"</Button>
<Button on_click=error>"error"</Button>
</Space>
}
"#,
"rust"
)
>
""
""
</DemoCode>
</Demo>
</div>
}
}

View file

@ -14,6 +14,7 @@ mod icon;
mod image; mod image;
mod input; mod input;
mod input_number; mod input_number;
mod loading_bar;
mod menu; mod menu;
mod message; mod message;
mod mobile; mod mobile;
@ -48,6 +49,7 @@ pub use icon::*;
pub use image::*; pub use image::*;
pub use input::*; pub use input::*;
pub use input_number::*; pub use input_number::*;
pub use loading_bar::*;
pub use menu::*; pub use menu::*;
pub use message::*; pub use message::*;
pub use mobile::*; pub use mobile::*;

View file

@ -16,6 +16,7 @@ mod image;
mod input; mod input;
mod input_number; mod input_number;
mod layout; mod layout;
mod loading_bar;
mod menu; mod menu;
mod message; mod message;
pub mod mobile; pub mod mobile;
@ -53,6 +54,7 @@ pub use image::*;
pub use input::*; pub use input::*;
pub use input_number::*; pub use input_number::*;
pub use layout::*; pub use layout::*;
pub use loading_bar::*;
pub use menu::*; pub use menu::*;
pub use message::*; pub use message::*;
pub use modal::*; pub use modal::*;

View file

@ -0,0 +1,11 @@
.thaw-loading-bar-container {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
}
.thaw-loading-bar {
height: 2px;
max-width: 0;
}

View file

@ -0,0 +1,46 @@
use super::{LoadingBar, LoadingBarRef};
use crate::{teleport::Teleport, utils::ComponentRef};
use leptos::*;
#[component]
pub fn LoadingBarProvider(children: Children) -> impl IntoView {
let loading_bar_ref = ComponentRef::<LoadingBarRef>::default();
provide_context(LoadingBarInjection { loading_bar_ref });
view! {
{children()}
<Teleport>
<LoadingBar comp_ref=loading_bar_ref/>
</Teleport>
}
}
#[derive(Clone)]
pub struct LoadingBarInjection {
loading_bar_ref: ComponentRef<LoadingBarRef>,
}
impl Copy for LoadingBarInjection {}
impl LoadingBarInjection {
pub fn start(&self) {
if let Some(loading_bar_ref) = self.loading_bar_ref.get_untracked() {
loading_bar_ref.start();
}
}
pub fn finish(&self) {
if let Some(loading_bar_ref) = self.loading_bar_ref.get_untracked() {
loading_bar_ref.finish();
}
}
pub fn error(&self) {
if let Some(loading_bar_ref) = self.loading_bar_ref.get_untracked() {
loading_bar_ref.error();
}
}
}
pub fn use_loading_bar() -> LoadingBarInjection {
expect_context::<LoadingBarInjection>()
}

112
src/loading_bar/mod.rs Normal file
View file

@ -0,0 +1,112 @@
mod loading_bar_provider;
use crate::{mount_style, use_theme, utils::ComponentRef, Theme};
use leptos::*;
pub use loading_bar_provider::{use_loading_bar, LoadingBarProvider};
#[derive(Clone)]
pub(crate) struct LoadingBarRef {
start: Callback<()>,
finish: Callback<()>,
error: Callback<()>,
}
impl LoadingBarRef {
pub fn start(&self) {
self.start.call(());
}
pub fn finish(&self) {
self.finish.call(());
}
pub fn error(&self) {
self.error.call(());
}
}
#[component]
pub(crate) fn LoadingBar(#[prop(optional)] comp_ref: ComponentRef<LoadingBarRef>) -> impl IntoView {
mount_style("loading-bar", include_str!("./loading-bar.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-background-color: {};",
theme.common.color_success
));
css_vars.push_str(&format!(
"--thaw-background-color-error: {};",
theme.common.color_error
));
});
css_vars
});
let loading_bar_ref = create_node_ref::<html::Div>();
let loading = create_rw_signal(false);
let start = Callback::new(move |_| {
loading.set(true);
if let Some(loading_bar_ref) = loading_bar_ref.get_untracked() {
let loading_bar_ref = loading_bar_ref
.style("background-color", "var(--thaw-background-color)")
.style("transition", "none")
.style("max-width", "0");
_ = loading_bar_ref.offset_width();
loading_bar_ref
.style("transition", "max-width 4s linear")
.style("max-width", "80%");
}
});
let is_on_transitionend = store_value(false);
let on_transitionend = move |_| {
if is_on_transitionend.get_value() {
is_on_transitionend.set_value(false);
loading.set(false);
}
};
let finish = Callback::new(move |_| {
if let Some(loading_bar_ref) = loading_bar_ref.get_untracked() {
loading_bar_ref
.style("background-color", "var(--thaw-background-color)")
.style("transition", "max-width 0.5s linear")
.style("max-width", "100%");
is_on_transitionend.set_value(true);
}
});
let error = Callback::new(move |_| {
if let Some(loading_bar_ref) = loading_bar_ref.get_untracked() {
if !loading.get() {
loading.set(true);
let loading_bar_ref = loading_bar_ref.clone();
let loading_bar_ref = loading_bar_ref
.style("transition", "none")
.style("max-width", "0");
_ = loading_bar_ref.offset_width();
}
loading_bar_ref
.style("background-color", "var(--thaw-background-color-error)")
.style("transition", "max-width 0.5s linear")
.style("max-width", "100%");
is_on_transitionend.set_value(true);
}
});
comp_ref.load(LoadingBarRef {
start,
finish,
error,
});
view! {
<div
class="thaw-loading-bar-container"
style=move || (!loading.get()).then(|| "display: none;")
>
<div
class="thaw-loading-bar"
ref=loading_bar_ref
style=move || css_vars.get()
on:transitionend=on_transitionend
></div>
</div>
}
}