From 24ae8f898d9c03519793a0adac94dc1616521fd2 Mon Sep 17 00:00:00 2001 From: Yohan Boogaert Date: Fri, 19 Mar 2021 19:10:19 +0100 Subject: [PATCH] Spinner (#96) --- yewprint-doc/src/app.rs | 10 +++ yewprint-doc/src/lib.rs | 1 + yewprint-doc/src/spinner/example.rs | 45 ++++++++++++ yewprint-doc/src/spinner/mod.rs | 101 +++++++++++++++++++++++++++ yewprint/src/callout.rs | 4 +- yewprint/src/icon.rs | 12 ++-- yewprint/src/lib.rs | 2 + yewprint/src/spinner.rs | 102 ++++++++++++++++++++++++++++ 8 files changed, 269 insertions(+), 8 deletions(-) create mode 100644 yewprint-doc/src/spinner/example.rs create mode 100644 yewprint-doc/src/spinner/mod.rs create mode 100644 yewprint/src/spinner.rs diff --git a/yewprint-doc/src/app.rs b/yewprint-doc/src/app.rs index 416c8a9..b0921e5 100644 --- a/yewprint-doc/src/app.rs +++ b/yewprint-doc/src/app.rs @@ -10,6 +10,7 @@ use crate::icon::*; use crate::input_group::*; use crate::menu::*; use crate::progressbar::*; +use crate::spinner::*; use crate::switch::*; use crate::tabs::*; use crate::tag::*; @@ -183,6 +184,12 @@ impl Component for App { onclick=self.link .callback(|_| Msg::GoToMenu(DocMenu::ProgressBar)) /> + html!(), DocMenu::Menu => html!(), DocMenu::ProgressBar => html!(), + DocMenu::Spinner => html!(), DocMenu::Switch => html!(), DocMenu::Tabs => html!(), DocMenu::Tag => html!(), @@ -285,6 +293,8 @@ pub enum DocMenu { Menu, #[to = "/#progress-bar"] ProgressBar, + #[to = "/#spinner"] + Spinner, #[to = "/#switch"] Switch, #[to = "/#tabs"] diff --git a/yewprint-doc/src/lib.rs b/yewprint-doc/src/lib.rs index c03b755..2da0e79 100644 --- a/yewprint-doc/src/lib.rs +++ b/yewprint-doc/src/lib.rs @@ -12,6 +12,7 @@ mod icon; mod input_group; mod menu; mod progressbar; +mod spinner; mod switch; mod tabs; mod tag; diff --git a/yewprint-doc/src/spinner/example.rs b/yewprint-doc/src/spinner/example.rs new file mode 100644 index 0000000..be14c29 --- /dev/null +++ b/yewprint-doc/src/spinner/example.rs @@ -0,0 +1,45 @@ +use yew::prelude::*; +use yewprint::{Intent, Spinner}; + +pub struct Example { + props: ExampleProps, +} + +#[derive(Clone, PartialEq, Properties)] +pub struct ExampleProps { + pub intent: Option, + pub size: u32, +} + +impl Component for Example { + type Message = (); + type Properties = ExampleProps; + + fn create(props: Self::Properties, _link: ComponentLink) -> Self { + Example { props } + } + + fn update(&mut self, _msg: Self::Message) -> ShouldRender { + true + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + if self.props != props { + self.props = props; + true + } else { + false + } + } + + fn view(&self) -> Html { + html! { +
+ +
+ } + } +} diff --git a/yewprint-doc/src/spinner/mod.rs b/yewprint-doc/src/spinner/mod.rs new file mode 100644 index 0000000..9881036 --- /dev/null +++ b/yewprint-doc/src/spinner/mod.rs @@ -0,0 +1,101 @@ +mod example; + +use crate::ExampleContainer; +use example::*; +use yew::prelude::*; +use yewprint::{HtmlSelect, Intent, H1, H5}; + +pub struct SpinnerDoc { + callback: Callback, + state: ExampleProps, +} + +impl Component for SpinnerDoc { + type Message = ExampleProps; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + SpinnerDoc { + callback: link.callback(|x| x), + state: ExampleProps { + intent: None, + size: 10, + }, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + self.state = msg; + true + } + + fn change(&mut self, _props: Self::Properties) -> ShouldRender { + true + } + + fn view(&self) -> Html { + let example_props = self.state.clone(); + let source = crate::include_raw_html!( + concat!(env!("OUT_DIR"), "/", file!(), ".html"), + "bp3-code-block" + ); + + html! { +
+

{"Spinner"}

+
+ + }) + > + + +
+
+ } + } +} + +crate::build_example_prop_component! { + SpinnerProps for ExampleProps => + fn view(&self) -> Html { + html! { +
+
{"Props"}
+
+

{"Select intent:"}

+ > + options={vec![ + (None, "None".to_string()), + (Some(Intent::Primary), "Primary".to_string()), + (Some(Intent::Success), "Success".to_string()), + (Some(Intent::Warning), "Warning".to_string()), + (Some(Intent::Danger), "Danger".to_string()), + ]} + onchange=self.update_props(|props, intent| ExampleProps { + intent, + ..props + }) + /> +

{"Select Size:"}

+ + options={vec![ + (20, "Small".to_string()), + (50, "Standard".to_string()), + (100, "Large".to_string()), + ]} + onchange=self.update_props(|props, size| ExampleProps { + size, + ..props + }) + /> +
+
+ } + } +} diff --git a/yewprint/src/callout.rs b/yewprint/src/callout.rs index 21b32fc..2cfe5c5 100644 --- a/yewprint/src/callout.rs +++ b/yewprint/src/callout.rs @@ -1,4 +1,4 @@ -use crate::icon::SIZE_LARGE; +use crate::icon::ICON_SIZE_LARGE; use crate::{Icon, IconName, Intent}; use yew::prelude::*; @@ -64,7 +64,7 @@ impl Component for Callout {
{ icon.iter() - .map(|name| html!{}) + .map(|name| html!{}) .collect::() } { diff --git a/yewprint/src/icon.rs b/yewprint/src/icon.rs index 6d51aa4..6083b6a 100644 --- a/yewprint/src/icon.rs +++ b/yewprint/src/icon.rs @@ -9,8 +9,8 @@ impl Default for IconName { } } -pub const SIZE_STANDARD: i32 = 16; -pub const SIZE_LARGE: i32 = 20; +pub const ICON_SIZE_STANDARD: i32 = 16; +pub const ICON_SIZE_LARGE: i32 = 20; pub struct Icon { props: IconProps, @@ -55,15 +55,15 @@ impl Component for Icon { } fn view(&self) -> Html { - let paths = if self.props.icon_size == SIZE_STANDARD { + let paths = if self.props.icon_size == ICON_SIZE_STANDARD { icon_svg_paths_16(self.props.icon) } else { icon_svg_paths_20(self.props.icon) }; - let pixel_grid_size = if self.props.icon_size >= SIZE_LARGE { - SIZE_LARGE + let pixel_grid_size = if self.props.icon_size >= ICON_SIZE_LARGE { + ICON_SIZE_LARGE } else { - SIZE_STANDARD + ICON_SIZE_STANDARD }; let icon_string = format!("{:?}", self.props.icon); diff --git a/yewprint/src/lib.rs b/yewprint/src/lib.rs index 73831f5..c64feb6 100644 --- a/yewprint/src/lib.rs +++ b/yewprint/src/lib.rs @@ -11,6 +11,7 @@ mod icon; mod input_group; mod menu; mod progressbar; +mod spinner; mod switch; mod tabs; mod tag; @@ -33,6 +34,7 @@ pub use id_tree; pub use input_group::*; pub use menu::*; pub use progressbar::*; +pub use spinner::*; pub use switch::*; pub use tabs::*; pub use tag::*; diff --git a/yewprint/src/spinner.rs b/yewprint/src/spinner.rs new file mode 100644 index 0000000..7a52092 --- /dev/null +++ b/yewprint/src/spinner.rs @@ -0,0 +1,102 @@ +use crate::Intent; +use yew::prelude::*; + +const R: f32 = 45.0; +const PATH_LENGTH: f32 = 280.0; +const SPINNER_MIN_SIZE: f32 = 10.0; +const STROKE_WIDTH: f32 = 4.0; +const MIN_STROKE_WIDTH: f32 = 16.0; +pub const SPINNER_SIZE_SMALL: f32 = 20.0; +pub const SPINNER_SIZE_STANDARD: f32 = 50.0; +pub const SPINNER_SIZE_LARGE: f32 = 100.0; + +pub struct Spinner { + props: SpinnerProps, +} + +#[derive(Clone, PartialEq, Properties)] +pub struct SpinnerProps { + #[prop_or_default] + pub intent: Option, + #[prop_or_default] + pub class: Classes, + #[prop_or(SPINNER_SIZE_STANDARD)] + pub size: f32, + #[prop_or(0.25)] + pub value: f32, +} + +impl Component for Spinner { + type Message = (); + type Properties = SpinnerProps; + + fn create(props: Self::Properties, _link: ComponentLink) -> Self { + Spinner { props } + } + + fn update(&mut self, _msg: Self::Message) -> ShouldRender { + true + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + if self.props != props { + self.props = props; + true + } else { + false + } + } + + fn view(&self) -> Html { + let size = f32::max(SPINNER_MIN_SIZE, self.props.size); + let stroke_width = f32::min(MIN_STROKE_WIDTH, (STROKE_WIDTH * SPINNER_SIZE_LARGE) / size); + let view_box = { + let radius = R + stroke_width / 2.00; + let view_box_x = 50.00 - radius; + let view_box_width = radius * 2.00; + format!( + "{:.2} {:.2} {:.2} {:.2}", + view_box_x, view_box_x, view_box_width, view_box_width, + ) + }; + let spinner_track = format!( + "M 50,50 m 0,-{R:.0} a {R:.0},{R:.0} 0 1 1 0,{R2:.0} a {R:.0},{R:.0} 0 1 1 0,-{R2:.0}", + R = R, + R2 = R * 2.0, + ); + let stroke_offset = PATH_LENGTH - PATH_LENGTH * self.props.value.clamp(0.0, 1.0); + + html! { +
+
+ + + + +
+
+ } + } +}