diff --git a/demo/gh-pages b/demo/gh-pages deleted file mode 160000 index faaa4df..0000000 --- a/demo/gh-pages +++ /dev/null @@ -1 +0,0 @@ -Subproject commit faaa4dfa5547b4c3e117999f4ed4cf2332006f2b diff --git a/demo/src/app.rs b/demo/src/app.rs index 937521d..521d882 100644 --- a/demo/src/app.rs +++ b/demo/src/app.rs @@ -24,57 +24,75 @@ pub fn App() -> impl IntoView { provide_context(theme); view! { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + } } +#[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! { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } +} + #[component] fn Provider(theme: RwSignal, children: Children) -> impl IntoView { view! { - {children()} + + {children()} + } diff --git a/demo/src/pages/components.rs b/demo/src/pages/components.rs index ac2627a..52a2172 100644 --- a/demo/src/pages/components.rs +++ b/demo/src/pages/components.rs @@ -167,6 +167,10 @@ fn gen_menu_data() -> Vec { MenuGroupOption { label: "Navigation Components".into(), children: vec![ + MenuItemOption { + value: "loading-bar".into(), + label: "Loading Bar".into(), + }, MenuItemOption { value: "menu".into(), label: "Menu".into(), diff --git a/demo/src/pages/loading_bar/mod.rs b/demo/src/pages/loading_bar/mod.rs new file mode 100644 index 0000000..29859f0 --- /dev/null +++ b/demo/src/pages/loading_bar/mod.rs @@ -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! { +
+

"Loading Bar"

+ + "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." + + + + + + + + + + + + + } + "#, + "rust" + ) + > + + "" + "" + + +
+ } +} diff --git a/demo/src/pages/mod.rs b/demo/src/pages/mod.rs index 8422b70..e52e8e2 100644 --- a/demo/src/pages/mod.rs +++ b/demo/src/pages/mod.rs @@ -14,6 +14,7 @@ mod icon; mod image; mod input; mod input_number; +mod loading_bar; mod menu; mod message; mod mobile; @@ -48,6 +49,7 @@ pub use icon::*; pub use image::*; pub use input::*; pub use input_number::*; +pub use loading_bar::*; pub use menu::*; pub use message::*; pub use mobile::*; diff --git a/src/lib.rs b/src/lib.rs index 0b1048e..666a758 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ mod image; mod input; mod input_number; mod layout; +mod loading_bar; mod menu; mod message; pub mod mobile; @@ -53,6 +54,7 @@ pub use image::*; pub use input::*; pub use input_number::*; pub use layout::*; +pub use loading_bar::*; pub use menu::*; pub use message::*; pub use modal::*; diff --git a/src/loading_bar/loading-bar.css b/src/loading_bar/loading-bar.css new file mode 100644 index 0000000..5049175 --- /dev/null +++ b/src/loading_bar/loading-bar.css @@ -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; +} diff --git a/src/loading_bar/loading_bar_provider.rs b/src/loading_bar/loading_bar_provider.rs new file mode 100644 index 0000000..7f085ef --- /dev/null +++ b/src/loading_bar/loading_bar_provider.rs @@ -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::::default(); + provide_context(LoadingBarInjection { loading_bar_ref }); + + view! { + {children()} + + + + } +} + +#[derive(Clone)] +pub struct LoadingBarInjection { + loading_bar_ref: ComponentRef, +} +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::() +} diff --git a/src/loading_bar/mod.rs b/src/loading_bar/mod.rs new file mode 100644 index 0000000..3db30ec --- /dev/null +++ b/src/loading_bar/mod.rs @@ -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) -> 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::(); + 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! { +
+
+
+ } +}