Yewprint upgrade 0.19 (#131)

Co-authored-by: Cecile Tonglet <cecile.tonglet@cecton.com>
Co-authored-by: Francois Stephany <francois@tamere.eu>
This commit is contained in:
Forest Anderson 2022-06-28 04:57:14 -04:00 committed by GitHub
parent 9407209aa2
commit 3f96db6a7e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 2726 additions and 3824 deletions

766
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -18,11 +18,13 @@ default = ["tree"]
tree = ["id_tree"] tree = ["id_tree"]
[dependencies] [dependencies]
web-sys = { version = "0.3", features = ["DomRect", "Element"] } web-sys = { version = "0.3", features = ["DomRect", "Element", "Event", "HtmlSelectElement"] }
yew = "0.18" # yew = { git = "https://github.com/yewstack/yew", branch = "master" }
yew = "0.19"
id_tree = { version = "1.7", optional = true } id_tree = { version = "1.7", optional = true }
yewtil = { version = "0.4", features = ["pure"] }
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
gloo = "0.6"
[build-dependencies] [build-dependencies]
regex = { version = "1", default-features = false, features = ["std", "unicode-perl"] } regex = { version = "1", default-features = false, features = ["std", "unicode-perl"] }

View file

@ -1,10 +1,5 @@
use std::borrow::Cow;
use yew::prelude::*; use yew::prelude::*;
pub struct ButtonGroup {
props: ButtonGroupProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ButtonGroupProps { pub struct ButtonGroupProps {
#[prop_or_default] #[prop_or_default]
@ -16,49 +11,28 @@ pub struct ButtonGroupProps {
#[prop_or_default] #[prop_or_default]
pub large: bool, pub large: bool,
#[prop_or_default] #[prop_or_default]
pub style: Option<Cow<'static, str>>, pub style: Option<String>,
#[prop_or_default] #[prop_or_default]
pub children: html::Children, pub children: html::Children,
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
} }
impl Component for ButtonGroup { #[function_component(ButtonGroup)]
type Message = (); pub fn button_group(props: &ButtonGroupProps) -> Html {
type Properties = ButtonGroupProps; html! {
<div
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { class={classes!(
Self { props } "bp3-button-group",
} props.minimal.then(|| "bp3-minimal"),
props.fill.then(|| "bp3-fill"),
fn update(&mut self, _: Self::Message) -> ShouldRender { props.large.then(|| "bp3-large"),
true props.vertical.then(|| "bp3-vertical"),
} props.class.clone(),
)}
fn change(&mut self, props: Self::Properties) -> ShouldRender { style={props.style.clone()}
if self.props != props { >
self.props = props; {props.children.clone()}
true </div>
} else {
false
}
}
fn view(&self) -> Html {
html! {
<div
class=classes!(
"bp3-button-group",
self.props.minimal.then(|| "bp3-minimal"),
self.props.fill.then(|| "bp3-fill"),
self.props.large.then(|| "bp3-large"),
self.props.vertical.then(|| "bp3-vertical"),
self.props.class.clone(),
)
style=self.props.style.clone()
>
{self.props.children.clone()}
</div>
}
} }
} }

View file

@ -1,11 +1,6 @@
use crate::{Icon, IconName, Intent, Spinner, ICON_SIZE_LARGE}; use crate::{Icon, IconName, Intent, Spinner, ICON_SIZE_LARGE};
use std::borrow::Cow;
use yew::prelude::*; use yew::prelude::*;
pub struct Button {
props: ButtonProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ButtonProps { pub struct ButtonProps {
#[prop_or_default] #[prop_or_default]
@ -35,84 +30,62 @@ pub struct ButtonProps {
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
#[prop_or_default] #[prop_or_default]
pub style: Option<Cow<'static, str>>, pub style: Option<String>,
#[prop_or_default] #[prop_or_default]
pub children: html::Children, pub children: html::Children,
} }
impl Component for Button { #[function_component(Button)]
type Message = (); pub fn button(props: &ButtonProps) -> Html {
type Properties = ButtonProps; html! {
<button
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { class={classes!(
Button { props } "bp3-button",
} props.fill.then(|| "bp3-fill"),
props.minimal.then(|| "bp3-minimal"),
fn update(&mut self, _msg: Self::Message) -> ShouldRender { props.small.then(|| "bp3-small"),
true props.outlined.then(|| "bp3-outlined"),
} props.loading.then(|| "bp3-loading"),
props.large.then(|| "bp3-large"),
fn change(&mut self, props: Self::Properties) -> ShouldRender { (props.active && !props.disabled).then(|| "bp3-active"),
if self.props != props { props.disabled.then(|| "bp3-disabled"),
self.props = props; props.intent,
true props.class.clone(),
} else { )}
false style={props.style.clone()}
} onclick={(!props.disabled).then(|| props.onclick.clone())}
} >
{
fn view(&self) -> Html { props
html! { .loading
<button .then(|| html! {
class=classes!( <Spinner
"bp3-button", class={classes!("bp3-button-spinner")}
self.props.fill.then(|| "bp3-fill"), size={ICON_SIZE_LARGE as f32}
self.props.minimal.then(|| "bp3-minimal"), />
self.props.small.then(|| "bp3-small"), })
self.props.outlined.then(|| "bp3-outlined"), .unwrap_or_default()
self.props.loading.then(|| "bp3-loading"), }
self.props.large.then(|| "bp3-large"), {
(self.props.active && !self.props.disabled).then(|| "bp3-active"), if let Some(icon) = props.icon {
self.props.disabled.then(|| "bp3-disabled"), html! {
self.props.intent, <Icon icon={icon} />
self.props.class.clone(), }
) } else {
style=self.props.style.clone() html!()
onclick=(!self.props.disabled).then(|| self.props.onclick.clone())
>
{
self
.props
.loading
.then(|| html! {
<Spinner
class=classes!("bp3-button-spinner")
size=ICON_SIZE_LARGE as f32
/>
})
.unwrap_or_default()
} }
{ }
if let Some(icon) = self.props.icon { {
html! { if props.children.is_empty() {
<Icon icon=icon /> html! ()
} } else {
} else { html! {
html!() <span class="bp3-button-text">
{for props.children.iter()}
</span>
} }
} }
{ }
if self.props.children.is_empty() { </button>
html! ()
} else {
html! {
<span class="bp3-button-text">
{for self.props.children.iter()}
</span>
}
}
}
</button>
}
} }
} }

View file

@ -1,12 +1,7 @@
use crate::icon::ICON_SIZE_LARGE; use crate::icon::ICON_SIZE_LARGE;
use crate::{Icon, IconName, Intent}; use crate::{Icon, IconName, Intent};
use std::borrow::Cow;
use yew::prelude::*; use yew::prelude::*;
pub struct Callout {
props: CalloutProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct CalloutProps { pub struct CalloutProps {
#[prop_or_default] #[prop_or_default]
@ -18,63 +13,43 @@ pub struct CalloutProps {
#[prop_or_default] #[prop_or_default]
pub intent: Option<Intent>, pub intent: Option<Intent>,
#[prop_or_default] #[prop_or_default]
pub title: Option<Cow<'static, str>>, pub title: Option<String>,
pub children: html::Children, pub children: html::Children,
} }
impl Component for Callout { #[function_component(Callout)]
type Message = (); pub fn callout(props: &CalloutProps) -> Html {
type Properties = CalloutProps; let icon = if props.without_icon {
None
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { } else {
Self { props } props.icon.or_else(|| {
} props.intent.map(|intent| match intent {
Intent::Primary => IconName::InfoSign,
fn update(&mut self, _msg: Self::Message) -> ShouldRender { Intent::Success => IconName::Tick,
true Intent::Warning => IconName::WarningSign,
} Intent::Danger => IconName::Error,
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props == props {
return false;
}
self.props = props;
true
}
fn view(&self) -> Html {
let icon = if self.props.without_icon {
None
} else {
self.props.icon.or_else(|| {
self.props.intent.map(|intent| match intent {
Intent::Primary => IconName::InfoSign,
Intent::Success => IconName::Tick,
Intent::Warning => IconName::WarningSign,
Intent::Danger => IconName::Error,
})
}) })
}; })
let classes = classes!( };
self.props.class.clone(), let classes = classes!(
"bp3-callout", props.class.clone(),
icon.map(|_| "bp3-callout-icon"), "bp3-callout",
self.props.intent, icon.map(|_| "bp3-callout-icon"),
); props.intent,
html! { );
<div class=classes> html! {
{ <div class={classes}>
icon.iter() {
.map(|name| html!{<Icon icon=*name icon_size=ICON_SIZE_LARGE/>}) icon.iter()
.collect::<Html>() .map(|name| html!{<Icon icon={*name} icon_size={ICON_SIZE_LARGE}/>})
} .collect::<Html>()
{ }
self.props.title.iter() {
.map(|title| html!{<h4 class={"bp3-heading"}>{title}</h4>}) props.title.iter()
.collect::<Html>() .map(|title| html!{<h4 class={"bp3-heading"}>{title}</h4>})
} .collect::<Html>()
{ self.props.children.clone() } }
</div> { props.children.clone() }
} </div>
} }
} }

View file

@ -14,42 +14,17 @@ pub struct CardProps {
pub children: html::Children, pub children: html::Children,
} }
pub struct Card { #[function_component(Card)]
props: CardProps, pub fn card(props: &CardProps) -> Html {
} html! {
<div class={classes!(
impl Component for Card { "bp3-card",
type Message = (); props.class.clone(),
type Properties = CardProps; props.elevation,
props.interactive.then(|| "bp3-interactive"),
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { )}
Self { props } onclick={props.onclick.clone()}>
} {props.children.clone()}
</div>
fn update(&mut self, _msg: Self::Message) -> bool {
true
}
fn change(&mut self, props: Self::Properties) -> bool {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<div class=classes!(
"bp3-card",
self.props.class.clone(),
self.props.elevation,
self.props.interactive.then(|| "bp3-interactive"),
)
onclick={self.props.onclick.clone()}>
{self.props.children.clone()}
</div>
}
} }
} }

View file

@ -1,11 +1,7 @@
use yew::prelude::*; use yew::prelude::*;
pub struct Checkbox {
props: Props,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct Props { pub struct CheckboxProps {
#[prop_or_default] #[prop_or_default]
pub disabled: bool, pub disabled: bool,
#[prop_or_default] #[prop_or_default]
@ -15,56 +11,35 @@ pub struct Props {
#[prop_or_default] #[prop_or_default]
pub checked: bool, pub checked: bool,
#[prop_or_default] #[prop_or_default]
pub onchange: Callback<ChangeData>, pub onchange: Callback<Event>,
#[prop_or_default] #[prop_or_default]
pub label: yew::virtual_dom::VNode, pub label: yew::virtual_dom::VNode,
#[prop_or_default] #[prop_or_default]
pub indeterminate_state: bool, pub indeterminate_state: bool,
} }
impl Component for Checkbox { #[function_component(Checkbox)]
type Message = (); pub fn checkbox(props: &CheckboxProps) -> Html {
type Properties = Props; html! {
<label
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { class={classes!(
Self { props } "bp3-control", "bp3-checkbox",
} props.disabled.then(|| "bp3-disabled"),
props.inline.then(|| "bp3-inline"),
fn update(&mut self, _msg: Self::Message) -> ShouldRender { props.large.then(|| "bp3-large")
true )}
} >
<input
fn change(&mut self, props: Self::Properties) -> ShouldRender { type="checkbox"
if self.props != props { checked={props.checked}
self.props = props; onchange={props.onchange.clone()}
true disabled={props.disabled}
} else { />
false <span
} class="bp3-control-indicator"
}
fn view(&self) -> Html {
html! {
<label
class=classes!(
"bp3-control", "bp3-checkbox",
self.props.disabled.then(|| "bp3-disabled"),
self.props.inline.then(|| "bp3-inline"),
self.props.large.then(|| "bp3-large")
)
> >
<input </span>
type="checkbox" {props.label.clone()}
checked={self.props.checked} </label>
onchange={self.props.onchange.clone()}
disabled=self.props.disabled
/>
<span
class="bp3-control-indicator"
>
</span>
{self.props.label.clone()}
</label>
}
} }
} }

View file

@ -1,7 +1,7 @@
use std::time::Duration; use gloo::timers::callback::Timeout;
use std::{convert::TryInto, time::Duration};
use web_sys::Element; use web_sys::Element;
use yew::prelude::*; use yew::prelude::*;
use yew::services::*;
pub struct Collapse { pub struct Collapse {
height: Height, height: Height,
@ -11,10 +11,7 @@ pub struct Collapse {
height_when_open: Option<String>, height_when_open: Option<String>,
animation_state: AnimationState, animation_state: AnimationState,
contents_ref: NodeRef, contents_ref: NodeRef,
callback_delayed_state_change: Callback<()>, handle_delayed_state_change: Option<Timeout>,
handle_delayed_state_change: Option<Box<dyn Task>>,
props: CollapseProps,
link: ComponentLink<Self>,
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
@ -52,77 +49,83 @@ impl Component for Collapse {
type Message = (); type Message = ();
type Properties = CollapseProps; type Properties = CollapseProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
Collapse { Collapse {
height: if props.is_open { height: if ctx.props().is_open {
Height::Auto Height::Auto
} else { } else {
Height::Zero Height::Zero
}, },
overflow_visible: false, overflow_visible: false,
translated: false, translated: false,
render_children: props.is_open || props.keep_children_mounted, render_children: ctx.props().is_open || ctx.props().keep_children_mounted,
height_when_open: None, height_when_open: None,
animation_state: if props.is_open { animation_state: if ctx.props().is_open {
AnimationState::Open AnimationState::Open
} else { } else {
AnimationState::Closed AnimationState::Closed
}, },
contents_ref: NodeRef::default(), contents_ref: NodeRef::default(),
callback_delayed_state_change: link.callback(|_| ()),
handle_delayed_state_change: None, handle_delayed_state_change: None,
props,
link,
} }
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn changed(&mut self, ctx: &Context<Self>) -> bool {
if self.props != props { if ctx.props().is_open {
if props.is_open { match self.animation_state {
match self.animation_state { AnimationState::Open | AnimationState::Opening => {}
AnimationState::Open | AnimationState::Opening => {} _ => {
_ => { self.animation_state = AnimationState::OpenStart;
self.animation_state = AnimationState::OpenStart; self.render_children = true;
self.render_children = true; self.translated = false;
self.translated = false;
}
}
} else {
match self.animation_state {
AnimationState::Closed | AnimationState::Closing => {}
_ => {
self.animation_state = AnimationState::ClosingStart;
self.height = Height::Full;
self.translated = true;
}
} }
} }
self.props = props;
true
} else { } else {
false match self.animation_state {
AnimationState::Closed | AnimationState::Closing => {}
_ => {
self.animation_state = AnimationState::ClosingStart;
self.height = Height::Full;
self.translated = true;
}
}
} }
true
} }
fn update(&mut self, _msg: Self::Message) -> ShouldRender { fn update(&mut self, ctx: &Context<Self>, _msg: Self::Message) -> bool {
match self.animation_state { match self.animation_state {
AnimationState::OpenStart => { AnimationState::OpenStart => {
let link = ctx.link().clone();
self.animation_state = AnimationState::Opening; self.animation_state = AnimationState::Opening;
self.height = Height::Full; self.height = Height::Full;
self.handle_delayed_state_change = Some(Box::new(TimeoutService::spawn( self.handle_delayed_state_change = Some(Timeout::new(
self.props.transition_duration, ctx.props()
self.callback_delayed_state_change.clone(), .transition_duration
))); .as_millis()
.try_into()
.unwrap(),
move || {
link.send_message(());
},
));
true true
} }
AnimationState::ClosingStart => { AnimationState::ClosingStart => {
let link = ctx.link().clone();
self.animation_state = AnimationState::Closing; self.animation_state = AnimationState::Closing;
self.height = Height::Zero; self.height = Height::Zero;
self.handle_delayed_state_change = Some(Box::new(TimeoutService::spawn( self.handle_delayed_state_change = Some(Timeout::new(
self.props.transition_duration, ctx.props()
self.callback_delayed_state_change.clone(), .transition_duration
))); .as_millis()
.try_into()
.unwrap(),
move || {
link.send_message(());
},
));
true true
} }
AnimationState::Opening => { AnimationState::Opening => {
@ -132,7 +135,7 @@ impl Component for Collapse {
} }
AnimationState::Closing => { AnimationState::Closing => {
self.animation_state = AnimationState::Closed; self.animation_state = AnimationState::Closed;
if !self.props.keep_children_mounted { if !ctx.props().keep_children_mounted {
self.render_children = false; self.render_children = false;
} }
true true
@ -141,19 +144,19 @@ impl Component for Collapse {
} }
} }
fn rendered(&mut self, _first_render: bool) { fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
if self.render_children { if self.render_children {
let client_height = self.contents_ref.cast::<Element>().unwrap().client_height(); let client_height = self.contents_ref.cast::<Element>().unwrap().client_height();
self.height_when_open = Some(format!("{}px", client_height)); self.height_when_open = Some(format!("{}px", client_height));
} }
match self.animation_state { match self.animation_state {
AnimationState::OpenStart | AnimationState::ClosingStart => self.link.send_message(()), AnimationState::OpenStart | AnimationState::ClosingStart => ctx.link().send_message(()),
_ => {} _ => {}
} }
} }
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
let mut container_style = String::with_capacity(30); let mut container_style = String::with_capacity(30);
match (self.height, self.height_when_open.as_ref()) { match (self.height, self.height_when_open.as_ref()) {
(Height::Zero, _) => container_style.push_str("height: 0px; "), (Height::Zero, _) => container_style.push_str("height: 0px; "),
@ -179,19 +182,19 @@ impl Component for Collapse {
} }
html! { html! {
<div class=classes!("bp3-collapse") style={container_style}> <div class={classes!("bp3-collapse")} style={container_style}>
<div <div
class=classes!( class={classes!(
"bp3-collapse-body", "bp3-collapse-body",
self.props.class.clone(), ctx.props().class.clone(),
) )}
style={content_style} style={content_style}
aria-hidden={(!self.render_children).then(|| "true")} aria-hidden={(!self.render_children).then(|| "true")}
ref={self.contents_ref.clone()} ref={self.contents_ref.clone()}
> >
{ {
if self.render_children { if self.render_children {
self.props.children.clone() ctx.props().children.clone()
} else { } else {
Default::default() Default::default()
} }

View file

@ -1,9 +1,5 @@
use yew::prelude::*; use yew::prelude::*;
pub struct ControlGroup {
props: ControlGroupProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ControlGroupProps { pub struct ControlGroupProps {
#[prop_or_default] #[prop_or_default]
@ -18,39 +14,18 @@ pub struct ControlGroupProps {
pub class: Classes, pub class: Classes,
} }
impl Component for ControlGroup { #[function_component(ControlGroup)]
type Message = (); pub fn control_group(props: &ControlGroupProps) -> Html {
type Properties = ControlGroupProps; html! {
<div
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { class={classes!(
Self { props } "bp3-control-group",
} props.fill.then(|| "bp3-fill"),
props.vertical.then(|| "bp3-vertical"),
fn update(&mut self, _: Self::Message) -> ShouldRender { props.class.clone(),
true )}
} >
{props.children.clone()}
fn change(&mut self, props: Self::Properties) -> ShouldRender { </div>
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<div
class=classes!(
"bp3-control-group",
self.props.fill.then(|| "bp3-fill"),
self.props.vertical.then(|| "bp3-vertical"),
self.props.class.clone(),
)
>
{self.props.children.clone()}
</div>
}
} }
} }

View file

@ -1,9 +1,5 @@
use yew::prelude::*; use yew::prelude::*;
pub struct Divider {
props: DividerProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct DividerProps { pub struct DividerProps {
#[prop_or_default] #[prop_or_default]
@ -14,36 +10,15 @@ pub struct DividerProps {
pub class: Classes, pub class: Classes,
} }
impl Component for Divider { #[function_component(Divider)]
type Message = (); pub fn view(props: &DividerProps) -> Html {
type Properties = DividerProps; html! {
<span
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { class={classes!(
Self { props } "bp3-divider",
} props.vertical.then(|| "bp3-vertical"),
props.class.clone(),
fn update(&mut self, _: 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! {
<span
class=classes!(
"bp3-divider",
self.props.vertical.then(|| "bp3-vertical"),
self.props.class.clone(),
)
/>
}
} }
} }

View file

@ -1,40 +1,36 @@
use yew::prelude::*; use yew::prelude::*;
use yewtil::{Pure, PureComponent};
#[derive(Properties, PartialEq)]
pub struct ChildrenOnlyProps {
#[prop_or_default]
pub class: Classes,
#[prop_or_default]
pub children: html::Children,
}
macro_rules! build_component { macro_rules! build_component {
($name:ident, $props_name:ident, $tag:tt, $class:literal) => { ($name:ident, $tag:tt, $class:literal) => {
pub type $name = Pure<$props_name>; #[function_component($name)]
pub fn $tag(props: &ChildrenOnlyProps) -> Html {
#[derive(Debug, Clone, PartialEq, Properties)] html! {
pub struct $props_name { <$tag class={classes!($class, props.class.clone())}>
#[prop_or_default] { props.children.clone() }
pub class: Classes, </$tag>
#[prop_or_default]
pub children: html::Children,
}
impl PureComponent for $props_name {
fn render(&self) -> Html {
html! {
<$tag class=classes!($class, self.class.clone())>
{self.children.clone()}
</$tag>
}
} }
} }
}; };
} }
build_component!(H1, H1Props, h1, "bp3-heading"); build_component!(H1, h1, "bp3-heading");
build_component!(H2, H2Props, h2, "bp3-heading"); build_component!(H2, h2, "bp3-heading");
build_component!(H3, H3Props, h3, "bp3-heading"); build_component!(H3, h3, "bp3-heading");
build_component!(H4, H4Props, h4, "bp3-heading"); build_component!(H4, h4, "bp3-heading");
build_component!(H5, H5Props, h5, "bp3-heading"); build_component!(H5, h5, "bp3-heading");
build_component!(H6, H6Props, h6, "bp3-heading"); build_component!(H6, h6, "bp3-heading");
build_component!(Blockquote, BlockquoteProps, blockquote, "bp3-blockquote"); build_component!(Blockquote, blockquote, "bp3-blockquote");
build_component!(Code, CodeProps, code, "bp3-code"); build_component!(Code, code, "bp3-code");
build_component!(Label, LabelProps, label, "bp3-label"); build_component!(Label, label, "bp3-label");
build_component!(Pre, PreProps, pre, "bp3-pre"); build_component!(Pre, pre, "bp3-pre");
build_component!(Ol, OlProps, ol, "bp3-ol"); build_component!(Ol, ol, "bp3-ol");
build_component!(Ul, UlProps, ul, "bp3-ul"); build_component!(Ul, ul, "bp3-ul");

View file

@ -1,9 +1,11 @@
use std::marker::PhantomData;
use crate::{Icon, IconName}; use crate::{Icon, IconName};
use web_sys::HtmlSelectElement;
use yew::prelude::*; use yew::prelude::*;
pub struct HtmlSelect<T: Clone + PartialEq + 'static> { pub struct HtmlSelect<T: Clone + PartialEq + 'static> {
props: HtmlSelectProps<T>, phantom: PhantomData<T>,
link: ComponentLink<Self>,
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
@ -30,51 +32,44 @@ pub struct HtmlSelectProps<T: Clone + PartialEq + 'static> {
} }
impl<T: Clone + PartialEq + 'static> Component for HtmlSelect<T> { impl<T: Clone + PartialEq + 'static> Component for HtmlSelect<T> {
type Message = ChangeData; type Message = Event;
type Properties = HtmlSelectProps<T>; type Properties = HtmlSelectProps<T>;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Self { props, link } Self {
phantom: PhantomData,
}
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
let i = if let ChangeData::Select(select) = msg { let i = if let Some(select) = msg.target_dyn_into::<HtmlSelectElement>() {
select.selected_index() select.selected_index()
} else { } else {
unreachable!("unexpected ChangeData variant: {:?}", msg); unreachable!("unexpected Event: {:?}", msg);
}; };
if i >= 0 { if i >= 0 {
let i = i as usize; let i = i as usize;
let variant = self.props.options[i].0.clone(); let variant = ctx.props().options[i].0.clone();
self.props.onchange.emit(variant); ctx.props().onchange.emit(variant);
} }
false false
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props { let option_children = ctx
self.props = props; .props()
true
} else {
false
}
}
fn view(&self) -> Html {
let option_children = self
.props
.options .options
.iter() .iter()
.map(|(value, label)| { .map(|(value, label)| {
let selected = self let selected = ctx
.props .props()
.value .value
.as_ref() .as_ref()
.map(|x| value == x) .map(|x| value == x)
.unwrap_or_default(); .unwrap_or_default();
html! { html! {
<option selected=selected> <option selected={selected}>
{label} {label}
</option> </option>
} }
@ -83,24 +78,24 @@ impl<T: Clone + PartialEq + 'static> Component for HtmlSelect<T> {
html! { html! {
<div <div
class=classes!( class={classes!(
"bp3-html-select", "bp3-html-select",
self.props.minimal.then(|| "bp3-minimal"), ctx.props().minimal.then(|| "bp3-minimal"),
self.props.large.then(|| "bp3-large"), ctx.props().large.then(|| "bp3-large"),
self.props.fill.then(|| "bp3-fill"), ctx.props().fill.then(|| "bp3-fill"),
self.props.disabled.then(|| "bp3-disabled"), ctx.props().disabled.then(|| "bp3-disabled"),
self.props.class.clone(), ctx.props().class.clone(),
) )}
> >
<select <select
disabled=self.props.disabled disabled={ctx.props().disabled}
onchange={self.link.callback(|x| x)} onchange={ctx.link().callback(|x| x)}
title={self.props.title.clone()} title={ctx.props().title.clone()}
value={"".to_string()} value={"".to_string()}
> >
{option_children} {option_children}
</select> </select>
<Icon icon=IconName::DoubleCaretVertical/> <Icon icon={IconName::DoubleCaretVertical}/>
</div> </div>
} }
} }

View file

@ -12,10 +12,6 @@ impl Default for IconName {
pub const ICON_SIZE_STANDARD: i32 = 16; pub const ICON_SIZE_STANDARD: i32 = 16;
pub const ICON_SIZE_LARGE: i32 = 20; pub const ICON_SIZE_LARGE: i32 = 20;
pub struct Icon {
props: IconProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct IconProps { pub struct IconProps {
pub icon: IconName, pub icon: IconName,
@ -33,62 +29,41 @@ pub struct IconProps {
pub onclick: Callback<MouseEvent>, pub onclick: Callback<MouseEvent>,
} }
impl Component for Icon { #[function_component(Icon)]
type Message = (); pub fn icon(props: &IconProps) -> Html {
type Properties = IconProps; let paths = if props.icon_size == ICON_SIZE_STANDARD {
icon_svg_paths_16(props.icon)
} else {
icon_svg_paths_20(props.icon)
};
let pixel_grid_size = if props.icon_size >= ICON_SIZE_LARGE {
ICON_SIZE_LARGE
} else {
ICON_SIZE_STANDARD
};
let icon_string = format!("{:?}", props.icon);
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { html! {
Icon { props } <span
} class={classes!("bp3-icon", props.class.clone(), props.intent)}
onclick={props.onclick.clone()}
fn update(&mut self, _msg: Self::Message) -> ShouldRender { >
true <svg
} fill={props.color.clone()}
data-icon={icon_string.clone()}
fn change(&mut self, props: Self::Properties) -> ShouldRender { width={props.icon_size.to_string()}
if self.props != props { height={props.icon_size.to_string()}
self.props = props; viewBox={format!("0 0 {x} {x}", x=pixel_grid_size)}
true
} else {
false
}
}
fn view(&self) -> Html {
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 >= ICON_SIZE_LARGE {
ICON_SIZE_LARGE
} else {
ICON_SIZE_STANDARD
};
let icon_string = format!("{:?}", self.props.icon);
html! {
<span
class=classes!("bp3-icon", self.props.class.clone(), self.props.intent)
onclick=self.props.onclick.clone()
> >
<svg <desc>{props.title.clone().unwrap_or(icon_string)}</desc>
fill={self.props.color.clone()} {
data-icon={icon_string.clone()} paths.iter()
width={self.props.icon_size.to_string()} .map(|x| html! {
height={self.props.icon_size.to_string()} <path d={*x} fillRule="evenodd" />
viewBox={format!("0 0 {x} {x}", x=pixel_grid_size)} })
> .collect::<Html>()
<desc>{self.props.title.clone().unwrap_or(icon_string)}</desc> }
{ </svg>
paths.iter() </span>
.map(|x| html! {
<path d=*x fillRule="evenodd" />
})
.collect::<Html>()
}
</svg>
</span>
}
} }
} }

View file

@ -4,8 +4,6 @@ use yew::prelude::*;
const MIN_HORIZONTAL_PADDING: i32 = 10; const MIN_HORIZONTAL_PADDING: i32 = 10;
pub struct InputGroup { pub struct InputGroup {
props: InputGroupProps,
link: ComponentLink<Self>,
left_element_ref: NodeRef, left_element_ref: NodeRef,
left_element_width: Option<i32>, left_element_width: Option<i32>,
right_element_ref: NodeRef, right_element_ref: NodeRef,
@ -74,7 +72,7 @@ pub struct InputGroupProps {
#[prop_or_default] #[prop_or_default]
pub input_type: TextInputType, pub input_type: TextInputType,
#[prop_or_default] #[prop_or_default]
pub oninput: Callback<InputData>, pub oninput: Callback<InputEvent>,
#[prop_or_default] #[prop_or_default]
pub onkeyup: Callback<KeyboardEvent>, pub onkeyup: Callback<KeyboardEvent>,
#[prop_or_default] #[prop_or_default]
@ -91,10 +89,8 @@ impl Component for InputGroup {
type Message = (); type Message = ();
type Properties = InputGroupProps; type Properties = InputGroupProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Self { Self {
props,
link,
left_element_ref: Default::default(), left_element_ref: Default::default(),
left_element_width: Default::default(), left_element_width: Default::default(),
right_element_ref: Default::default(), right_element_ref: Default::default(),
@ -102,20 +98,11 @@ impl Component for InputGroup {
} }
} }
fn update(&mut self, _: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, _: Self::Message) -> bool {
true true
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
let input_style = match (self.left_element_width, self.right_element_width) { let input_style = match (self.left_element_width, self.right_element_width) {
(Some(left), None) => format!("padding-left:{}px", left.max(MIN_HORIZONTAL_PADDING)), (Some(left), None) => format!("padding-left:{}px", left.max(MIN_HORIZONTAL_PADDING)),
(None, Some(right)) => format!("padding-right:{}px", right.max(MIN_HORIZONTAL_PADDING)), (None, Some(right)) => format!("padding-right:{}px", right.max(MIN_HORIZONTAL_PADDING)),
@ -129,53 +116,53 @@ impl Component for InputGroup {
html! { html! {
<div <div
class=classes!( class={classes!(
"bp3-input-group", "bp3-input-group",
self.props.disabled.then(|| "bp3-disabled"), ctx.props().disabled.then(|| "bp3-disabled"),
self.props.fill.then(|| "bp3-fill"), ctx.props().fill.then(|| "bp3-fill"),
self.props.large.then(|| "bp3-large"), ctx.props().large.then(|| "bp3-large"),
self.props.small.then(|| "bp3-small"), ctx.props().small.then(|| "bp3-small"),
self.props.round.then(|| "bp3-round"), ctx.props().round.then(|| "bp3-round"),
self.props.placeholder.clone(), ctx.props().placeholder.clone(),
self.props.class.clone(), ctx.props().class.clone(),
) )}
> >
{ {
if let Some(left_element) = self.props.left_element.clone() { if let Some(left_element) = ctx.props().left_element.clone() {
html! { html! {
<span <span
class="bp3-input-left-container" class="bp3-input-left-container"
ref=self.left_element_ref.clone() ref={self.left_element_ref.clone()}
> >
{left_element} {left_element}
</span> </span>
} }
} else if let Some(icon) = self.props.left_icon { } else if let Some(icon) = ctx.props().left_icon {
html! { html! {
<Icon icon=icon /> <Icon icon={icon} />
} }
} else { } else {
html!() html!()
} }
} }
<input <input
ref=self.props.input_ref.clone() ref={ctx.props().input_ref.clone()}
class="bp3-input" class="bp3-input"
type=self.props.input_type.as_str() type={ctx.props().input_type.as_str()}
placeholder=self.props.placeholder.clone() placeholder={ctx.props().placeholder.clone()}
disabled=self.props.disabled disabled={ctx.props().disabled}
oninput={self.props.oninput.clone()} oninput={ctx.props().oninput.clone()}
onkeyup={self.props.onkeyup.clone()} onkeyup={ctx.props().onkeyup.clone()}
onkeydown={self.props.onkeydown.clone()} onkeydown={ctx.props().onkeydown.clone()}
value=self.props.value.clone() value={ctx.props().value.clone()}
style=input_style style={input_style}
/> />
{ {
if let Some(right_element) = self.props.right_element.clone() { if let Some(right_element) = ctx.props().right_element.clone() {
html! { html! {
<span <span
class="bp3-input-action" class="bp3-input-action"
ref=self.right_element_ref.clone() ref={self.right_element_ref.clone()}
> >
{right_element} {right_element}
</span> </span>
@ -188,7 +175,7 @@ impl Component for InputGroup {
} }
} }
fn rendered(&mut self, _first_render: bool) { fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
let left_old_value = self.left_element_width.take(); let left_old_value = self.left_element_width.take();
self.left_element_width = self self.left_element_width = self
.left_element_ref .left_element_ref
@ -203,7 +190,7 @@ impl Component for InputGroup {
if left_old_value != self.left_element_width || right_old_value != self.right_element_width if left_old_value != self.left_element_width || right_old_value != self.right_element_width
{ {
self.link.send_message(()); ctx.link().send_message(());
} }
} }
} }

View file

@ -1,11 +1,6 @@
use crate::{Icon, IconName, Intent, H6}; use crate::{Icon, IconName, Intent, H6};
use std::borrow::Cow;
use yew::prelude::*; use yew::prelude::*;
pub struct Menu {
props: MenuProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct MenuProps { pub struct MenuProps {
#[prop_or_default] #[prop_or_default]
@ -17,45 +12,20 @@ pub struct MenuProps {
pub children: html::Children, pub children: html::Children,
} }
impl Component for Menu { #[function_component(Menu)]
type Message = (); pub fn menu(props: &MenuProps) -> Html {
type Properties = MenuProps; html! {
<ul
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { class={classes!(
Menu { props } "bp3-menu",
props.large.then(|| "bp3-large"),
props.class.clone(),
)}
ref={props.r#ref.clone()}
>
{props.children.clone()}
</ul>
} }
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! {
<ul
class=classes!(
"bp3-menu",
self.props.large.then(|| "bp3-large"),
self.props.class.clone(),
)
ref={self.props.r#ref.clone()}
>
{self.props.children.clone()}
</ul>
}
}
}
pub struct MenuItem {
props: MenuItemProps,
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
@ -71,7 +41,7 @@ pub struct MenuItemProps {
#[prop_or_default] #[prop_or_default]
pub disabled: bool, pub disabled: bool,
#[prop_or_default] #[prop_or_default]
pub href: Option<Cow<'static, str>>, pub href: Option<String>,
#[prop_or_default] #[prop_or_default]
pub label: Option<yew::virtual_dom::VNode>, pub label: Option<yew::virtual_dom::VNode>,
#[prop_or_default] #[prop_or_default]
@ -89,127 +59,81 @@ pub struct MenuItemProps {
// TODO: pub children: html::Children, // TODO: pub children: html::Children,
} }
impl Component for MenuItem { #[function_component(MenuItem)]
type Message = (); pub fn menu_item(props: &MenuItemProps) -> Html {
type Properties = MenuItemProps; html! {
<li>
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { <a
MenuItem { props } class={classes!(
} "bp3-menu-item",
props.active.then(|| "bp3-active"),
fn update(&mut self, _msg: Self::Message) -> ShouldRender { props.disabled.then(|| "bp3-disabled"),
true props.intent
} .or_else(|| props.active.then(|| Intent::Primary)),
props.class.clone(),
fn change(&mut self, props: Self::Properties) -> ShouldRender { )}
if self.props != props { href={(!props.disabled).then(|| props.href.clone()).flatten()}
self.props = props; tabIndex={(!props.disabled).then(|| "0")}
true onclick={(!props.disabled).then(|| props.onclick.clone())}
} else { >
false {
} if let Some(icon_name) = props.icon {
} html! {
<Icon icon={icon_name} />
fn view(&self) -> Html { }
html! { } else if let Some(html) = props.icon_html.clone() {
<li> html
<a } else {
class=classes!( html! {
"bp3-menu-item", <Icon icon={IconName::Blank} />
self.props.active.then(|| "bp3-active"),
self.props.disabled.then(|| "bp3-disabled"),
self.props.intent
.or_else(|| self.props.active.then(|| Intent::Primary)),
self.props.class.clone(),
)
href={(!self.props.disabled).then(|| self.props.href.clone())}.flatten()
tabIndex={(!self.props.disabled).then(|| "0")}
onclick={(!self.props.disabled).then(|| self.props.onclick.clone())}
>
{
if let Some(icon_name) = self.props.icon {
html! {
<Icon icon={icon_name} />
}
} else if let Some(html) = self.props.icon_html.clone() {
html
} else {
html! {
<Icon icon=IconName::Blank />
}
} }
} }
<div class=classes!("bp3-text", "bp3-fill", self.props.text_class.clone())> }
{self.props.text.clone()} <div class={classes!("bp3-text", "bp3-fill", props.text_class.clone())}>
</div> {props.text.clone()}
{ </div>
if let Some(label) = self.props.label.clone() { {
html! { if let Some(label) = props.label.clone() {
<span html! {
class=classes!( <span
"bp3-menu-item-label", class={classes!(
self.props.label_class.clone()) "bp3-menu-item-label",
> props.label_class.clone())}
{label} >
</span> {label}
} </span>
} else {
html!()
} }
} else {
html!()
} }
}
</a> </a>
</li> </li>
}
} }
} }
pub struct MenuDivider {
props: MenuDividerProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct MenuDividerProps { pub struct MenuDividerProps {
#[prop_or_default] #[prop_or_default]
pub title: Option<yew::virtual_dom::VNode>, pub title: Option<yew::virtual_dom::VNode>,
} }
impl Component for MenuDivider { #[function_component(MenuDivider)]
type Message = (); pub fn menu_divider(props: &MenuDividerProps) -> Html {
type Properties = MenuDividerProps; html! {
{if let Some(title) = props.title.clone() {
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { html! {
Self { props } <li
} class={classes!("bp3-menu-header")}
>
fn update(&mut self, _msg: Self::Message) -> ShouldRender { <H6>{title}</H6>
true </li>
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
if let Some(title) = self.props.title.clone() {
html! {
<li
class=classes!("bp3-menu-header")
>
<H6>{title}</H6>
</li>
}
} else {
html! {
<li class=classes!("bp3-menu-divider") />
}
} }
} } else {
html! {
<li class={classes!("bp3-menu-divider")} />
}
}}
} }
} }

View file

@ -1,7 +1,9 @@
use crate::{Button, ButtonGroup, ControlGroup, IconName, InputGroup, Intent}; use crate::{Button, ButtonGroup, ControlGroup, IconName, InputGroup, Intent};
use std::fmt::Display; use std::fmt::Display;
use std::marker::PhantomData;
use std::ops::{Add, Bound, RangeBounds, Sub}; use std::ops::{Add, Bound, RangeBounds, Sub};
use std::str::FromStr; use std::str::FromStr;
use web_sys::HtmlInputElement;
use yew::html::IntoPropValue; use yew::html::IntoPropValue;
use yew::prelude::*; use yew::prelude::*;
@ -16,9 +18,8 @@ where
+ PartialOrd + PartialOrd
+ 'static, + 'static,
{ {
props: NumericInputProps<T>,
link: ComponentLink<Self>,
input: String, input: String,
phantom: PhantomData<T>,
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
@ -84,42 +85,46 @@ where
type Message = Msg; type Message = Msg;
type Properties = NumericInputProps<T>; type Properties = NumericInputProps<T>;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Self { Self {
props,
link,
input: Default::default(), input: Default::default(),
phantom: PhantomData,
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
let mut update_value = |new_value| {
let new_value = ctx.props().bounds.clamp(new_value, ctx.props().increment);
if new_value != ctx.props().value {
self.input = new_value.to_string();
ctx.props().onchange.emit(new_value);
true
} else {
false
}
};
match msg { match msg {
Msg::InputUpdate(new_value) => { Msg::InputUpdate(new_value) => {
if let Ok(new_value) = new_value.trim().parse::<T>() { if let Ok(new_value) = new_value.trim().parse::<T>() {
self.update_value(new_value) update_value(new_value)
} else { } else {
false false
} }
} }
Msg::Up => self.update_value(self.props.value + self.props.increment), Msg::Up => update_value(ctx.props().value + ctx.props().increment),
Msg::Down => self.update_value(self.props.value - self.props.increment), Msg::Down => update_value(ctx.props().value - ctx.props().increment),
Msg::Noop => false, Msg::Noop => false,
} }
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn changed(&mut self, ctx: &Context<Self>) -> bool {
if self.props != props { self.input = ctx.props().value.to_string();
if self.props.value != props.value { true
self.input = props.value.to_string();
}
self.props = props;
true
} else {
false
}
} }
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
let NumericInputProps { let NumericInputProps {
value, value,
increment, increment,
@ -127,8 +132,9 @@ where
disable_buttons, disable_buttons,
buttons_on_the_left, buttons_on_the_left,
.. ..
} = self.props; } = *ctx.props();
let bounds = &self.props.bounds;
let bounds = &ctx.props().bounds;
let button_up_disabled = disabled || bounds.clamp(value + increment, increment) == value; let button_up_disabled = disabled || bounds.clamp(value + increment, increment) == value;
let button_down_disabled = disabled || bounds.clamp(value - increment, increment) == value; let button_down_disabled = disabled || bounds.clamp(value - increment, increment) == value;
@ -136,16 +142,16 @@ where
html!() html!()
} else { } else {
html! { html! {
<ButtonGroup vertical=true class=classes!("bp3-fixed")> <ButtonGroup vertical=true class={classes!("bp3-fixed")}>
<Button <Button
icon=IconName::ChevronUp icon={IconName::ChevronUp}
disabled=button_up_disabled disabled={button_up_disabled}
onclick=self.link.callback(|_| Msg::Up) onclick={ctx.link().callback(|_| Msg::Up)}
/> />
<Button <Button
icon=IconName::ChevronDown icon={IconName::ChevronDown}
disabled=button_down_disabled disabled={button_down_disabled}
onclick=self.link.callback(|_| Msg::Down) onclick={ctx.link().callback(|_| Msg::Down)}
/> />
</ButtonGroup> </ButtonGroup>
} }
@ -153,15 +159,18 @@ where
let input_group = html! { let input_group = html! {
<InputGroup <InputGroup
placeholder=self.props.placeholder.clone() placeholder={ctx.props().placeholder.clone()}
large=self.props.large large={ctx.props().large}
disabled=self.props.disabled disabled={ctx.props().disabled}
left_icon=self.props.left_icon left_icon={ctx.props().left_icon}
left_element=self.props.left_element.clone() left_element={ctx.props().left_element.clone()}
right_element=self.props.right_element.clone() right_element={ctx.props().right_element.clone()}
value=self.input.clone() value={self.input.clone()}
oninput=self.link.callback(|e: InputData| Msg::InputUpdate(e.value)) oninput={ctx.link().callback(|e: InputEvent| {
onkeydown=self.link.callback(|e: KeyboardEvent| { let value = e.target_unchecked_into::<HtmlInputElement>().value();
Msg::InputUpdate(value)
})}
onkeydown={ctx.link().callback(|e: KeyboardEvent| {
if e.key() == "ArrowUp" { if e.key() == "ArrowUp" {
Msg::Up Msg::Up
} else if e.key() == "ArrowDown" { } else if e.key() == "ArrowDown" {
@ -169,16 +178,16 @@ where
} else { } else {
Msg::Noop Msg::Noop
} }
}) })}
/> />
}; };
if buttons_on_the_left { if buttons_on_the_left {
html! { html! {
<ControlGroup <ControlGroup
class=classes!("bp3-numeric-input") class={classes!("bp3-numeric-input")}
fill=self.props.fill fill={ctx.props().fill}
large=self.props.large large={ctx.props().large}
> >
{buttons} {buttons}
{input_group} {input_group}
@ -187,9 +196,9 @@ where
} else { } else {
html! { html! {
<ControlGroup <ControlGroup
class=classes!("bp3-numeric-input") class={classes!("bp3-numeric-input")}
fill=self.props.fill fill={ctx.props().fill}
large=self.props.large large={ctx.props().large}
> >
{input_group} {input_group}
{buttons} {buttons}
@ -199,30 +208,6 @@ where
} }
} }
impl<T> NumericInput<T>
where
T: Add<Output = T>
+ Sub<Output = T>
+ Copy
+ Display
+ FromStr
+ PartialEq
+ PartialOrd
+ 'static,
{
fn update_value(&mut self, new_value: T) -> ShouldRender {
let new_value = self.props.bounds.clamp(new_value, self.props.increment);
if new_value != self.props.value {
self.input = new_value.to_string();
self.props.onchange.emit(new_value);
true
} else {
false
}
}
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct NumericInputRangeBounds<T> { pub struct NumericInputRangeBounds<T> {
pub start: Bound<T>, pub start: Bound<T>,

View file

@ -1,11 +1,9 @@
use crate::{Button, IconName}; use crate::{Button, IconName};
use std::borrow::Cow; use gloo::timers::callback::Timeout;
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt; use std::fmt;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration;
use yew::prelude::*; use yew::prelude::*;
use yew::services::timeout::{TimeoutService, TimeoutTask};
pub struct PanelBuilder<F: Fn(Option<Html>, I) -> O, I, O> { pub struct PanelBuilder<F: Fn(Option<Html>, I) -> O, I, O> {
title: Option<Html>, title: Option<Html>,
@ -119,9 +117,11 @@ impl From<StateAction> for Classes {
} }
pub struct PanelStack { pub struct PanelStack {
timeout_task: Option<TimeoutTask>, timeout_task: Option<Timeout>,
props: PanelStackProps,
link: ComponentLink<Self>, // We keep track of the latest action to perform from the PanelStackState
// because we need a mutable access to the action.
action_to_perform: Option<StateAction>,
} }
#[derive(Debug, Clone, PartialEq, Properties)] #[derive(Debug, Clone, PartialEq, Properties)]
@ -141,47 +141,36 @@ impl Component for PanelStack {
type Message = PanelStackMessage; type Message = PanelStackMessage;
type Properties = PanelStackProps; type Properties = PanelStackProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Self { Self {
timeout_task: None, timeout_task: None,
props, action_to_perform: None,
link,
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
PanelStackMessage::PopPanel => { PanelStackMessage::PopPanel => {
self.props.state.opened_panels.borrow_mut().pop(); ctx.props().state.opened_panels.borrow_mut().pop();
true true
} }
} }
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props { let opened_panels = ctx.props().state.opened_panels.borrow();
self.props = props; let last = match self.action_to_perform {
true
} else {
false
}
}
fn view(&self) -> Html {
let opened_panels = self.props.state.opened_panels.borrow();
let action = self.props.state.action;
let last = match action {
Some(StateAction::Pop) => opened_panels.len() - 2, Some(StateAction::Pop) => opened_panels.len() - 2,
_ => opened_panels.len() - 1, _ => opened_panels.len() - 1,
}; };
html! { html! {
<div <div
class=classes!( class={classes!(
"bp3-panel-stack2", "bp3-panel-stack2",
action, self.action_to_perform,
self.props.class.clone(), ctx.props().class.clone(),
) )}
> >
{ {
opened_panels opened_panels
@ -190,9 +179,9 @@ impl Component for PanelStack {
.rev() .rev()
.map(|(i, (title, content))| html! { .map(|(i, (title, content))| html! {
<Panel <Panel
title=title.clone() title={title.clone()}
animation={ animation={
match action { match self.action_to_perform {
_ if i == last => Animation::EnterStart, _ if i == last => Animation::EnterStart,
Some(StateAction::Push) if i == last - 1 => Some(StateAction::Push) if i == last - 1 =>
Animation::ExitStart, Animation::ExitStart,
@ -201,8 +190,8 @@ impl Component for PanelStack {
_ => Animation::Exited, _ => Animation::Exited,
} }
} }
onclose=(i > 0).then(|| self.props.onclose.clone()).flatten() onclose={(i > 0).then(|| ctx.props().onclose.clone()).flatten()}
key=i key={i}
> >
// TODO the state of content doesn't seem to be kept when re-opening // TODO the state of content doesn't seem to be kept when re-opening
// a panel using the same components // a panel using the same components
@ -215,21 +204,24 @@ impl Component for PanelStack {
} }
} }
fn rendered(&mut self, _first_render: bool) { fn changed(&mut self, ctx: &Context<Self>) -> bool {
if self.props.state.action.take() == Some(StateAction::Pop) { self.action_to_perform = ctx.props().state.action;
self.timeout_task.replace(TimeoutService::spawn( true
Duration::from_millis(400), }
self.link.callback(|_| PanelStackMessage::PopPanel),
)); fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
if self.action_to_perform.take() == Some(StateAction::Pop) {
let link = ctx.link().clone();
self.timeout_task.replace(Timeout::new(400, move || {
link.send_message(PanelStackMessage::PopPanel)
}));
} }
} }
} }
struct Panel { struct Panel {
animation: Animation, animation: Animation,
timeout_task: Option<TimeoutTask>, timeout_task: Option<Timeout>,
props: PanelProps,
link: ComponentLink<Self>,
} }
#[derive(Debug, Clone, PartialEq, Properties)] #[derive(Debug, Clone, PartialEq, Properties)]
@ -249,16 +241,14 @@ impl Component for Panel {
type Message = PanelMessage; type Message = PanelMessage;
type Properties = PanelProps; type Properties = PanelProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
Self { Self {
animation: props.animation, animation: ctx.props().animation,
timeout_task: None, timeout_task: None,
props,
link,
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
PanelMessage::UpdateAnimation(animation) => { PanelMessage::UpdateAnimation(animation) => {
self.animation = animation; self.animation = animation;
@ -267,17 +257,7 @@ impl Component for Panel {
} }
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props {
self.animation = props.animation;
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
let style = if self.animation == Animation::Exited { let style = if self.animation == Animation::Exited {
"display:none" "display:none"
} else { } else {
@ -294,15 +274,15 @@ impl Component for Panel {
Animation::Exited => None, Animation::Exited => None,
} }
); );
let back_button = self.props.onclose.clone().map(|onclose| { let back_button = ctx.props().onclose.clone().map(|onclose| {
html! { html! {
<Button <Button
class=classes!("bp3-panel-stack-header-back") class={classes!("bp3-panel-stack-header-back")}
style=Cow::Borrowed("padding-right:0") style={"padding-right:0"}
icon=IconName::ChevronLeft icon={IconName::ChevronLeft}
minimal=true minimal={true}
small=true small={true}
onclick=onclose.reform(|_| ()) onclick={onclose.reform(|_| ())}
> >
// TODO: I get a lot of "VComp is not mounted" if I try to use the title // TODO: I get a lot of "VComp is not mounted" if I try to use the title
// of the previous panel // of the previous panel
@ -311,47 +291,48 @@ impl Component for Panel {
}); });
html! { html! {
<div class=classes style=style> <div class={classes} style={style}>
<div class="bp3-panel-stack-header"> <div class="bp3-panel-stack-header">
<span>{back_button.unwrap_or_default()}</span> <span>{back_button.unwrap_or_default()}</span>
{self.props.title.clone().unwrap_or_default()} {ctx.props().title.clone().unwrap_or_default()}
<span/> <span/>
</div> </div>
{for self.props.children.iter()} {for ctx.props().children.iter()}
</div> </div>
} }
} }
fn rendered(&mut self, _first_render: bool) { fn changed(&mut self, ctx: &Context<Self>) -> bool {
self.animation = ctx.props().animation;
true
}
fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
match self.animation { match self.animation {
Animation::EnterStart => { Animation::EnterStart => {
self.timeout_task.replace(TimeoutService::spawn( let link = ctx.link().clone();
Duration::from_millis(0), self.timeout_task.replace(Timeout::new(0, move || {
self.link link.send_message(PanelMessage::UpdateAnimation(Animation::Entering));
.callback(|_| PanelMessage::UpdateAnimation(Animation::Entering)), }));
));
} }
Animation::Entering => { Animation::Entering => {
self.timeout_task.replace(TimeoutService::spawn( let link = ctx.link().clone();
Duration::from_millis(400), self.timeout_task.replace(Timeout::new(400, move || {
self.link link.send_message(PanelMessage::UpdateAnimation(Animation::Entered));
.callback(|_| PanelMessage::UpdateAnimation(Animation::Entered)), }));
));
} }
Animation::Entered => {} Animation::Entered => {}
Animation::ExitStart => { Animation::ExitStart => {
self.timeout_task.replace(TimeoutService::spawn( let link = ctx.link().clone();
Duration::from_millis(0), self.timeout_task.replace(Timeout::new(0, move || {
self.link link.send_message(PanelMessage::UpdateAnimation(Animation::Exiting));
.callback(|_| PanelMessage::UpdateAnimation(Animation::Exiting)), }));
));
} }
Animation::Exiting => { Animation::Exiting => {
self.timeout_task.replace(TimeoutService::spawn( let link = ctx.link().clone();
Duration::from_millis(400), self.timeout_task.replace(Timeout::new(400, move || {
self.link link.send_message(PanelMessage::UpdateAnimation(Animation::Exited));
.callback(|_| PanelMessage::UpdateAnimation(Animation::Exited)), }));
));
} }
Animation::Exited => {} Animation::Exited => {}
} }

View file

@ -1,10 +1,6 @@
use crate::Intent; use crate::Intent;
use yew::prelude::*; use yew::prelude::*;
pub struct ProgressBar {
props: ProgressBarProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ProgressBarProps { pub struct ProgressBarProps {
#[prop_or_default] #[prop_or_default]
@ -19,48 +15,27 @@ pub struct ProgressBarProps {
pub class: Classes, pub class: Classes,
} }
impl Component for ProgressBar { #[function_component(ProgressBar)]
type Message = (); pub fn progress_bar(props: &ProgressBarProps) -> Html {
type Properties = ProgressBarProps; let width = if let Some(value) = props.value {
// NOTE: nightly, issue #44095 for f32::clamp
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { // let percent = ((1000. * value).ceil() / 10.).clamp(0.,100.);
Self { props } let percent = ((1000. * value).ceil() / 10.).max(0.).min(100.);
} format!("width: {}%;", percent)
} else {
fn update(&mut self, _msg: Self::Message) -> ShouldRender { "".into()
true };
} html! {
<div
fn change(&mut self, props: Self::Properties) -> ShouldRender { class={classes!(
if self.props != props { "bp3-progress-bar",
self.props = props; props.intent,
true (!props.animate).then(|| "bp3-no-animation"),
} else { (!props.stripes).then(|| "bp3-no-stripes"),
false props.class.clone(),
} )}
} >
<div class={classes!("bp3-progress-meter")} style={{width}}/>
fn view(&self) -> Html { </div>
let width = if let Some(value) = self.props.value {
// NOTE: nightly, issue #44095 for f32::clamp
// let percent = ((1000. * value).ceil() / 10.).clamp(0.,100.);
let percent = ((1000. * value).ceil() / 10.).max(0.).min(100.);
format!("width: {}%;", percent)
} else {
"".into()
};
html! {
<div
class=classes!(
"bp3-progress-bar",
self.props.intent,
(!self.props.animate).then(|| "bp3-no-animation"),
(!self.props.stripes).then(|| "bp3-no-stripes"),
self.props.class.clone(),
)
>
<div class=classes!("bp3-progress-meter") style={{width}}/>
</div>
}
} }
} }

View file

@ -1,9 +1,5 @@
use yew::prelude::*; use yew::prelude::*;
pub struct Radio {
props: RadioProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct RadioProps { pub struct RadioProps {
#[prop_or_default] #[prop_or_default]
@ -17,59 +13,38 @@ pub struct RadioProps {
#[prop_or_default] #[prop_or_default]
pub name: Option<String>, pub name: Option<String>,
#[prop_or_default] #[prop_or_default]
pub onchange: Option<Callback<ChangeData>>, pub onchange: Option<Callback<Event>>,
#[prop_or_default] #[prop_or_default]
pub label: yew::virtual_dom::VNode, pub label: yew::virtual_dom::VNode,
#[prop_or_default] #[prop_or_default]
pub value: Option<String>, pub value: Option<String>,
} }
impl Component for Radio { #[function_component(Radio)]
type Message = (); pub fn radio(props: &RadioProps) -> Html {
type Properties = RadioProps; html! {
<label
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { class={classes!(
Self { props } "bp3-control",
} "bp3-radio",
props.disabled.then(|| "bp3-disabled"),
fn update(&mut self, _msg: Self::Message) -> ShouldRender { props.inline.then(|| "bp3-inline"),
true props.large.then(|| "bp3-large"),
} )}
>
fn change(&mut self, props: Self::Properties) -> ShouldRender { <input
if self.props != props { type="radio"
self.props = props; onchange={props.onchange.clone().unwrap_or_default()}
true disabled={props.disabled}
} else { value={props.value.clone().unwrap_or_default()}
false checked={props.checked.unwrap_or(false)}
} name={props.name.clone().unwrap_or_default()}
} />
<span
fn view(&self) -> Html { class={classes!("bp3-control-indicator")}
html! {
<label
class=classes!(
"bp3-control",
"bp3-radio",
self.props.disabled.then(|| "bp3-disabled"),
self.props.inline.then(|| "bp3-inline"),
self.props.large.then(|| "bp3-large"),
)
> >
<input </span>
type="radio" {props.label.clone()}
onchange={self.props.onchange.clone().unwrap_or_default()} </label>
disabled=self.props.disabled
value={self.props.value.clone().unwrap_or_default()}
checked=self.props.checked.unwrap_or(false)
name={self.props.name.clone().unwrap_or_default()}
/>
<span
class=classes!("bp3-control-indicator")
>
</span>
{self.props.label.clone()}
</label>
}
} }
} }

View file

@ -1,10 +1,6 @@
use crate::Radio; use crate::Radio;
use yew::prelude::*; use yew::prelude::*;
pub struct RadioGroup<T: Clone + PartialEq + 'static> {
props: RadioGroupProps<T>,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct RadioGroupProps<T: Clone + PartialEq + 'static> { pub struct RadioGroupProps<T: Clone + PartialEq + 'static> {
#[prop_or_default] #[prop_or_default]
@ -24,71 +20,46 @@ pub struct RadioGroupProps<T: Clone + PartialEq + 'static> {
pub class: Classes, pub class: Classes,
} }
impl<T: Clone + PartialEq + 'static> Component for RadioGroup<T> { // impl<T: Clone + PartialEq + 'static> Component for RadioGroup<T> {
type Message = ();
type Properties = RadioGroupProps<T>;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { #[function_component(RadioGroup)]
Self { props } pub fn radio_group<T: Clone + PartialEq + 'static>(props: &RadioGroupProps<T>) -> Html {
} let option_children = props
.options
.iter()
.map(|(value, label)| {
let checked = props.value.as_ref().map(|x| value == x).unwrap_or_default();
let value = value.clone();
fn update(&mut self, _: Self::Message) -> ShouldRender { html! {
true <Radio
} value={"".to_string()}
label={html!(label)}
checked={checked}
onchange={props.onchange.reform(move |_| value.clone())}
inline={props.inline}
disabled={props.disabled}
large={props.large}
/>
}
})
.collect::<Html>();
fn change(&mut self, props: Self::Properties) -> ShouldRender { html! {
if self.props != props { <div
self.props = props; class={classes!(
true "bp3-radio-group",
} else { props.class.clone(),
false )}
} >
} {
if let Some(label) = props.label.clone() {
fn view(&self) -> Html { label
let option_children = self } else {
.props html!()
.options
.iter()
.map(|(value, label)| {
let checked = self
.props
.value
.as_ref()
.map(|x| value == x)
.unwrap_or_default();
let value = value.clone();
html! {
<Radio
value="".to_string()
label=html!(label)
checked=checked
onchange=self.props.onchange.reform(move |_| value.clone())
inline=self.props.inline
disabled=self.props.disabled
large=self.props.large
/>
} }
}) }
.collect::<Html>(); {option_children}
</div>
html! {
<div
class=classes!(
"bp3-radio-group",
self.props.class.clone(),
)
>
{
if let Some(label) = self.props.label.clone() {
label
} else {
html!()
}
}
{option_children}
</div>
}
} }
} }

View file

@ -1,19 +1,19 @@
use crate::Intent; use crate::Intent;
use std::borrow::Cow; use std::borrow::Cow;
use std::marker::PhantomData;
use wasm_bindgen::closure::Closure; use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use web_sys::Element; use web_sys::Element;
use yew::prelude::*; use yew::prelude::*;
pub struct Slider<T: Clone + PartialEq + 'static> { pub struct Slider<T: Clone + PartialEq + 'static> {
props: SliderProps<T>,
mouse_move: Closure<dyn FnMut(MouseEvent)>, mouse_move: Closure<dyn FnMut(MouseEvent)>,
mouse_up: Closure<dyn FnMut(MouseEvent)>, mouse_up: Closure<dyn FnMut(MouseEvent)>,
link: ComponentLink<Self>,
handle_ref: NodeRef, handle_ref: NodeRef,
track_ref: NodeRef, track_ref: NodeRef,
is_moving: bool, is_moving: bool,
focus_handle: bool, focus_handle: bool,
phantom: PhantomData<T>,
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
@ -42,35 +42,34 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
type Message = Msg; type Message = Msg;
type Properties = SliderProps<T>; type Properties = SliderProps<T>;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
let mouse_move = { let mouse_move = {
let link = link.clone(); let link = ctx.link().clone();
Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
link.send_message(Msg::Mouse(event)); link.send_message(Msg::Mouse(event));
}) as Box<dyn FnMut(_)>) }) as Box<dyn FnMut(_)>)
}; };
let mouse_up = { let mouse_up = {
let link = link.clone(); let link = ctx.link().clone();
Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| { Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
link.send_message(Msg::StopChange); link.send_message(Msg::StopChange);
}) as Box<dyn FnMut(_)>) }) as Box<dyn FnMut(_)>)
}; };
Self { Self {
props,
mouse_move, mouse_move,
mouse_up, mouse_up,
link,
handle_ref: NodeRef::default(), handle_ref: NodeRef::default(),
track_ref: NodeRef::default(), track_ref: NodeRef::default(),
is_moving: false, is_moving: false,
focus_handle: false, focus_handle: false,
phantom: PhantomData,
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
Msg::StartChange if self.props.values.len() > 1 => { Msg::StartChange if ctx.props().values.len() > 1 => {
let document = yew::utils::document(); let document = gloo::utils::document();
let event_target: &web_sys::EventTarget = document.as_ref(); let event_target: &web_sys::EventTarget = document.as_ref();
self.is_moving = true; self.is_moving = true;
event_target event_target
@ -89,23 +88,23 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
true true
} }
Msg::StartChange => false, Msg::StartChange => false,
Msg::Mouse(event) if self.props.values.len() > 1 => { Msg::Mouse(event) if ctx.props().values.len() > 1 => {
if event.buttons() == crate::MOUSE_EVENT_BUTTONS_PRIMARY { if event.buttons() == crate::MOUSE_EVENT_BUTTONS_PRIMARY {
let track_rect = self.track_ref.cast::<Element>().expect("no track ref"); let track_rect = self.track_ref.cast::<Element>().expect("no track ref");
let tick_size = (track_rect.client_width() as f64) let tick_size = (track_rect.client_width() as f64)
/ self.props.values.len().saturating_sub(1) as f64; / ctx.props().values.len().saturating_sub(1) as f64;
let pixel_delta = let pixel_delta =
(event.client_x() as f64) - track_rect.get_bounding_client_rect().left(); (event.client_x() as f64) - track_rect.get_bounding_client_rect().left();
let position = (pixel_delta / tick_size).round() as usize; let position = (pixel_delta / tick_size).round() as usize;
let (value, _) = let (value, _) =
self.props.values.get(position).unwrap_or_else(|| { ctx.props().values.get(position).unwrap_or_else(|| {
self.props.values.last().expect("No value in the vec") ctx.props().values.last().expect("No value in the vec")
}); });
if Some(value) != self.props.selected.as_ref() { if Some(value) != ctx.props().selected.as_ref() {
self.props.onchange.emit(value.clone()); ctx.props().onchange.emit(value.clone());
} }
true true
@ -115,7 +114,7 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
} }
Msg::Mouse(_) => false, Msg::Mouse(_) => false,
Msg::StopChange => { Msg::StopChange => {
let document = yew::utils::document(); let document = gloo::utils::document();
let event_target: &web_sys::EventTarget = document.as_ref(); let event_target: &web_sys::EventTarget = document.as_ref();
self.is_moving = false; self.is_moving = false;
event_target event_target
@ -132,43 +131,43 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
.expect("No event listener to remove"); .expect("No event listener to remove");
true true
} }
Msg::Keyboard(event) if self.props.values.len() > 1 => match event.key().as_str() { Msg::Keyboard(event) if ctx.props().values.len() > 1 => match event.key().as_str() {
"ArrowDown" | "ArrowLeft" => { "ArrowDown" | "ArrowLeft" => {
self.focus_handle = true; self.focus_handle = true;
event.prevent_default(); event.prevent_default();
let index = self let index = ctx
.props .props()
.values .values
.iter() .iter()
.position(|(value, _)| Some(value) == self.props.selected.as_ref()) .position(|(value, _)| Some(value) == ctx.props().selected.as_ref())
.map(|i| i.saturating_sub(1)) .map(|i| i.saturating_sub(1))
.unwrap_or(0); .unwrap_or(0);
let (value, _) = self.props.values[index].clone(); let (value, _) = ctx.props().values[index].clone();
self.props.onchange.emit(value); ctx.props().onchange.emit(value);
true true
} }
"ArrowUp" | "ArrowRight" => { "ArrowUp" | "ArrowRight" => {
self.focus_handle = true; self.focus_handle = true;
event.prevent_default(); event.prevent_default();
let index = self let index = ctx
.props .props()
.values .values
.iter() .iter()
.position(|(value, _)| Some(value) == self.props.selected.as_ref()) .position(|(value, _)| Some(value) == ctx.props().selected.as_ref())
.map(|i| i.saturating_add(1)) .map(|i| i.saturating_add(1))
.unwrap_or(0); .unwrap_or(0);
let (value, _) = self let (value, _) = ctx
.props .props()
.values .values
.get(index) .get(index)
.unwrap_or_else(|| { .unwrap_or_else(|| {
self.props.values.last().expect( ctx.props().values.last().expect(
"Already check, \ "Already check, \
there are at least 2 values in self.props.options; qed", there are at least 2 values in ctx.props().options; qed",
) )
}) })
.clone(); .clone();
self.props.onchange.emit(value); ctx.props().onchange.emit(value);
true true
} }
_ => false, _ => false,
@ -177,23 +176,14 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
} }
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props { let value_index = ctx
self.props = props; .props()
true
} else {
false
}
}
fn view(&self) -> Html {
let value_index = self
.props
.values .values
.iter() .iter()
.position(|(value, _)| Some(value) == self.props.selected.as_ref()); .position(|(value, _)| Some(value) == ctx.props().selected.as_ref());
let labels = if self.props.values.len() > 1 { let labels = if ctx.props().values.len() > 1 {
self.props ctx.props()
.values .values
.iter() .iter()
.enumerate() .enumerate()
@ -201,11 +191,11 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
label.clone().map(|x| { label.clone().map(|x| {
html! { html! {
<div <div
class=classes!("bp3-slider-label") class={classes!("bp3-slider-label")}
style=format!( style={format!(
"left: {}%;", (i as f64) * 100.0 "left: {}%;", (i as f64) * 100.0
/ ((self.props.values.len() as f64) - 1.0) / ((ctx.props().values.len() as f64) - 1.0)
) )}
> >
{x} {x}
</div> </div>
@ -213,11 +203,11 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
}) })
}) })
.collect::<Html>() .collect::<Html>()
} else if let Some((_, Some(label))) = self.props.values.first() { } else if let Some((_, Some(label))) = ctx.props().values.first() {
html! { html! {
<div <div
class=classes!("bp3-slider-label") class={classes!("bp3-slider-label")}
style="left: 50%;" style={"left: 50%;"}
> >
{label} {label}
</div> </div>
@ -225,9 +215,9 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
} else { } else {
html!() html!()
}; };
let value_label = self.props.value_label.clone().map(|x| { let value_label = ctx.props().value_label.clone().map(|x| {
html! { html! {
<span class=classes!("bp3-slider-label")> <span class={classes!("bp3-slider-label")}>
{x} {x}
</span> </span>
} }
@ -235,12 +225,12 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
html! { html! {
<div <div
class=classes!( class={classes!(
"bp3-slider", "bp3-slider",
self.props.vertical.then(|| "bp3-vertical"), ctx.props().vertical.then(|| "bp3-vertical"),
) )}
onmousedown=(self.props.values.len() > 1).then( onmousedown={(ctx.props().values.len() > 1).then(
|| self.link.batch_callback( || ctx.link().batch_callback(
|event: MouseEvent| { |event: MouseEvent| {
if event.buttons() == if event.buttons() ==
crate::MOUSE_EVENT_BUTTONS_PRIMARY crate::MOUSE_EVENT_BUTTONS_PRIMARY
@ -251,19 +241,19 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
} }
} }
) )
) )}
> >
<div <div
class=classes!("bp3-slider-track") class={classes!("bp3-slider-track")}
ref={self.track_ref.clone()} ref={self.track_ref.clone()}
> >
{ {
if value_index.is_none() && !self.props.values.is_empty() { if value_index.is_none() && !ctx.props().values.is_empty() {
html! { html! {
<div <div
class=classes!("bp3-slider-progress") class={classes!("bp3-slider-progress")}
style="top: 0px;" style="top: 0px;"
onkeydown=self.link.callback(|event| Msg::Keyboard(event)) onkeydown={ctx.link().callback(|event| Msg::Keyboard(event))}
tabindex=0 tabindex=0
> >
</div> </div>
@ -271,7 +261,7 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
} else { } else {
html! { html! {
<div <div
class=classes!("bp3-slider-progress") class={classes!("bp3-slider-progress")}
style="top: 0px;" style="top: 0px;"
> >
</div> </div>
@ -280,18 +270,18 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
} }
{ {
match value_index { match value_index {
Some(index) if self.props.values.len() > 1 Some(index) if ctx.props().values.len() > 1
&& self.props.intent.is_some() => { && ctx.props().intent.is_some() => {
html! { html! {
<div <div
class=classes!("bp3-slider-progress", self.props.intent) class={classes!("bp3-slider-progress", ctx.props().intent)}
style=format!( style={format!(
"left: 0%; right: {}%; top: 0px;", "left: 0%; right: {}%; top: 0px;",
100.0 - ( 100.0 - (
100.0 * (index as f64) 100.0 * (index as f64)
/ (self.props.values.len() as f64 - 1.0) / (ctx.props().values.len() as f64 - 1.0)
) )
) )}
> >
</div> </div>
} }
@ -300,34 +290,34 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
} }
} }
</div> </div>
<div class=classes!("bp3-slider-axis")> <div class={classes!("bp3-slider-axis")}>
{labels} {labels}
</div> </div>
{ {
match value_index { match value_index {
Some(index) if self.props.values.len() > 1 => Some(index) if ctx.props().values.len() > 1 =>
{ {
html! { html! {
<span <span
class=classes!( class={classes!(
"bp3-slider-handle", "bp3-slider-handle",
self.is_moving.then(|| "bp3-active"), self.is_moving.then(|| "bp3-active"),
) )}
ref={self.handle_ref.clone()} ref={self.handle_ref.clone()}
style=format!( style={format!(
"left: calc({}% - 8px);", "left: calc({}% - 8px);",
100.0 * (index as f64) 100.0 * (index as f64)
/ (self.props.values.len() as f64 - 1.0), / (ctx.props().values.len() as f64 - 1.0),
) )}
onmousedown=self.link.batch_callback( onmousedown={ctx.link().batch_callback(
|event: MouseEvent| { |event: MouseEvent| {
if event.buttons() == crate::MOUSE_EVENT_BUTTONS_PRIMARY { if event.buttons() == crate::MOUSE_EVENT_BUTTONS_PRIMARY {
vec![Msg::StartChange] vec![Msg::StartChange]
} else { } else {
vec![] vec![]
} }
}) })}
onkeydown=self.link.callback(|event| Msg::Keyboard(event)) onkeydown={ctx.link().callback(|event| Msg::Keyboard(event))}
tabindex=0 tabindex=0
> >
{value_label.clone().unwrap_or_default()} {value_label.clone().unwrap_or_default()}
@ -337,10 +327,10 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
Some(_) => { Some(_) => {
html! { html! {
<span <span
class=classes!( class={classes!(
"bp3-slider-handle", "bp3-slider-handle",
self.is_moving.then(|| "bp3-active"), self.is_moving.then(|| "bp3-active"),
) )}
ref={self.handle_ref.clone()} ref={self.handle_ref.clone()}
style="left: calc(50% - 8px);" style="left: calc(50% - 8px);"
> >
@ -355,7 +345,7 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
} }
} }
fn rendered(&mut self, _: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _: bool) {
if self.focus_handle { if self.focus_handle {
if let Some(element) = self.handle_ref.cast::<web_sys::HtmlElement>() { if let Some(element) = self.handle_ref.cast::<web_sys::HtmlElement>() {
let _ = element.focus(); let _ = element.focus();

View file

@ -10,10 +10,6 @@ pub const SPINNER_SIZE_SMALL: f32 = 20.0;
pub const SPINNER_SIZE_STANDARD: f32 = 50.0; pub const SPINNER_SIZE_STANDARD: f32 = 50.0;
pub const SPINNER_SIZE_LARGE: f32 = 100.0; pub const SPINNER_SIZE_LARGE: f32 = 100.0;
pub struct Spinner {
props: SpinnerProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct SpinnerProps { pub struct SpinnerProps {
#[prop_or_default] #[prop_or_default]
@ -26,77 +22,56 @@ pub struct SpinnerProps {
pub value: f32, pub value: f32,
} }
impl Component for Spinner { #[function_component(Spinner)]
type Message = (); pub fn spinner(props: &SpinnerProps) -> Html {
type Properties = SpinnerProps; let size = f32::max(SPINNER_MIN_SIZE, 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 * props.value.clamp(0.0, 1.0);
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { html! {
Spinner { props } <div
} class={classes!(
"bp3-spinner",
fn update(&mut self, _msg: Self::Message) -> ShouldRender { props.intent,
true props.class.clone(),
} )}
>
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! {
<div <div
class=classes!( class={classes!("bp3-spinner-animation")}
"bp3-spinner",
self.props.intent,
self.props.class.clone(),
)
> >
<div <svg
class=classes!("bp3-spinner-animation") width={size.to_string()}
height={size.to_string()}
stroke-width={stroke_width.to_string()}
viewBox={view_box}
> >
<svg <path
width=size.to_string() class={classes!("bp3-spinner-track")}
height=size.to_string() d={spinner_track.clone()}
stroke-width=stroke_width.to_string() />
viewBox=view_box <path
> class={classes!("bp3-spinner-head")}
<path d={spinner_track}
class=classes!("bp3-spinner-track") pathLength={PATH_LENGTH.to_string()}
d=spinner_track.clone() stroke-dasharray={format!("{} {}", PATH_LENGTH, PATH_LENGTH)}
/> stroke-dashoffset={stroke_offset.to_string()}
<path />
class=classes!("bp3-spinner-head") </svg>
d=spinner_track
pathLength=PATH_LENGTH.to_string()
stroke-dasharray=format!("{} {}", PATH_LENGTH, PATH_LENGTH)
stroke-dashoffset=stroke_offset.to_string()
/>
</svg>
</div>
</div> </div>
} </div>
} }
} }

View file

@ -1,9 +1,5 @@
use yew::prelude::*; use yew::prelude::*;
pub struct Switch {
props: SwitchProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct SwitchProps { pub struct SwitchProps {
#[prop_or_default] #[prop_or_default]
@ -28,85 +24,64 @@ pub struct SwitchProps {
pub align_right: bool, pub align_right: bool,
} }
impl Component for Switch { #[function_component(Switch)]
type Message = (); pub fn switch(props: &SwitchProps) -> Html {
type Properties = SwitchProps; let display_label = {
if props.inner_label.is_some() || props.inner_label_checked.is_some() {
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { let inner_label = props.inner_label.as_deref().unwrap_or_default();
Self { props } let inner_label_checked = props.inner_label_checked.as_ref();
} html! {
<>
fn update(&mut self, _msg: Self::Message) -> ShouldRender { <div class={classes!("bp3-control-indicator-child")}>
true <div class={classes!("bp3-switch-inner-text")}>
} {
if let Some(label_checked) = inner_label_checked {
fn change(&mut self, props: Self::Properties) -> ShouldRender { label_checked.clone()
if self.props != props { } else {
self.props = props; inner_label.to_string()
true
} else {
false
}
}
fn view(&self) -> Html {
let display_label = {
if self.props.inner_label.is_some() || self.props.inner_label_checked.is_some() {
let inner_label = self.props.inner_label.as_deref().unwrap_or_default();
let inner_label_checked = self.props.inner_label_checked.as_ref();
html! {
<>
<div class=classes!("bp3-control-indicator-child")>
<div class=classes!("bp3-switch-inner-text")>
{
if let Some(label_checked) = inner_label_checked {
label_checked.clone()
} else {
inner_label.to_string()
}
} }
</div> }
</div> </div>
<div class=classes!("bp3-control-indicator-child")> </div>
<div class=classes!("bp3-switch-inner-text")> <div class={classes!("bp3-control-indicator-child")}>
{inner_label.to_string()} <div class={classes!("bp3-switch-inner-text")}>
</div> {inner_label.to_string()}
</div> </div>
</> </div>
} </>
} else {
Html::default()
} }
}; } else {
html! { Html::default()
<label
class=classes!(
"bp3-control",
"bp3-switch",
self.props.disabled.then(|| "bp3-disabled"),
self.props.inline.then(|| "bp3-inline"),
self.props.large.then(|| "bp3-large"),
self.props.class.clone(),
if self.props.align_right {
"bp3-align-right"
} else {
"bp3-align-left"
},
)
>
<input
type="checkbox"
checked={self.props.checked}
onclick={self.props.onclick.clone()}
disabled=self.props.disabled
/>
<span
class=classes!("bp3-control-indicator")
>
{display_label}
</span>
{self.props.label.clone()}
</label>
} }
};
html! {
<label
class={classes!(
"bp3-control",
"bp3-switch",
props.disabled.then(|| "bp3-disabled"),
props.inline.then(|| "bp3-inline"),
props.large.then(|| "bp3-large"),
props.class.clone(),
if props.align_right {
"bp3-align-right"
} else {
"bp3-align-left"
},
)}
>
<input
type="checkbox"
checked={props.checked}
onclick={props.onclick.clone()}
disabled={props.disabled}
/>
<span
class={classes!("bp3-control-indicator")}
>
{display_label}
</span>
{props.label.clone()}
</label>
} }
} }

View file

@ -1,12 +1,13 @@
use std::collections::{hash_map::DefaultHasher, HashMap}; use std::collections::{hash_map::DefaultHasher, HashMap};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use web_sys::HtmlElement; use web_sys::HtmlElement;
use yew::prelude::*; use yew::prelude::*;
pub struct Tabs<T: Clone + PartialEq + Hash + 'static> { pub struct Tabs<T: Clone + PartialEq + Hash + 'static> {
props: TabsProps<T>,
tab_refs: HashMap<u64, NodeRef>, tab_refs: HashMap<u64, NodeRef>,
indicator_ref: NodeRef, indicator_ref: NodeRef,
phantom: PhantomData<T>,
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
@ -34,8 +35,9 @@ impl<T: Clone + PartialEq + Hash + 'static> Component for Tabs<T> {
type Message = (); type Message = ();
type Properties = TabsProps<T>; type Properties = TabsProps<T>;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
let tab_refs = props let tab_refs = ctx
.props()
.tabs .tabs
.iter() .iter()
.map(|x| { .map(|x| {
@ -46,62 +48,53 @@ impl<T: Clone + PartialEq + Hash + 'static> Component for Tabs<T> {
}) })
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
Tabs { Self {
props,
tab_refs, tab_refs,
indicator_ref: Default::default(), indicator_ref: Default::default(),
phantom: PhantomData,
} }
} }
fn update(&mut self, _msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
true true
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props { let tabs = ctx
self.props = props; .props()
true
} else {
false
}
}
fn view(&self) -> Html {
let tabs = self
.props
.tabs .tabs
.iter() .iter()
.map(|x| { .map(|x| {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
x.id.hash(&mut hasher); x.id.hash(&mut hasher);
let id = hasher.finish(); let id = hasher.finish();
let title_id = format!("bp3-tab-title_{}_{}", self.props.id, id); let title_id = format!("bp3-tab-title_{}_{}", ctx.props().id, id);
let panel_id = format!("bp3-tab-panel_{}_{}", self.props.id, id); let panel_id = format!("bp3-tab-panel_{}_{}", ctx.props().id, id);
let selected = self.props.selected_tab_id == x.id; let selected = ctx.props().selected_tab_id == x.id;
(x, id, title_id, panel_id, selected) (x, id, title_id, panel_id, selected)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
html! { html! {
<div <div
class=classes!( class={classes!(
"bp3-tabs", "bp3-tabs",
self.props.vertical.then(|| "bp3-vertical"), ctx.props().vertical.then(|| "bp3-vertical"),
self.props.class.clone(), ctx.props().class.clone(),
) )}
> >
<div <div
class=classes!( class={classes!(
"bp3-tab-list", "bp3-tab-list",
self.props.large.then(|| "bp3-large"), ctx.props().large.then(|| "bp3-large"),
) )}
> >
{ {
if self.props.animate { if ctx.props().animate {
html! { html! {
<div <div
class="bp3-tab-indicator-wrapper" class="bp3-tab-indicator-wrapper"
ref=self.indicator_ref.clone() ref={self.indicator_ref.clone()}
> >
<div class="bp3-tab-indicator" /> <div class="bp3-tab-indicator" />
</div> </div>
@ -115,27 +108,27 @@ impl<T: Clone + PartialEq + Hash + 'static> Component for Tabs<T> {
.iter() .iter()
.map(|(props, id, title_id, panel_id, selected)| html! { .map(|(props, id, title_id, panel_id, selected)| html! {
<div <div
class=classes!( class={classes!(
"bp3-tab", "bp3-tab",
props.title_class.clone(), props.title_class.clone(),
) )}
aria-disabled=props.disabled.then(|| "true") aria-disabled={props.disabled.then(|| "true")}
aria-expanded=selected.to_string() aria-expanded={selected.to_string()}
aria-selected=selected.to_string() aria-selected={selected.to_string()}
role="tab" role="tab"
tabIndex={(!props.disabled).then(|| "0")} tabIndex={(!props.disabled).then(|| "0")}
id=title_id.to_string() id={title_id.to_string()}
aria-controls=panel_id.to_string() aria-controls={panel_id.to_string()}
data-tab-id=id.to_string() data-tab-id={id.to_string()}
onclick={(!props.disabled).then(|| { onclick={(!props.disabled).then(|| {
let tab_id = props.id.clone(); let tab_id = props.id.clone();
self ctx
.props .props()
.onchange .onchange
.reform(move |_| tab_id.clone()) .reform(move |_| tab_id.clone())
})} })}
key=*id key={*id}
ref=self.tab_refs[id].clone() ref={self.tab_refs[id].clone()}
> >
{ props.title.clone() } { props.title.clone() }
</div> </div>
@ -147,19 +140,19 @@ impl<T: Clone + PartialEq + Hash + 'static> Component for Tabs<T> {
tabs tabs
.iter() .iter()
.filter(|(_, _, _, _, selected)| { .filter(|(_, _, _, _, selected)| {
!self.props.render_active_panel_only || *selected !ctx.props().render_active_panel_only || *selected
}) })
.map(|(props, id, title_id, panel_id, selected)| html! { .map(|(props, id, title_id, panel_id, selected)| html! {
<div <div
class=classes!( class={classes!(
"bp3-tab-panel", "bp3-tab-panel",
selected.then(|| props.panel_class.clone()), selected.then(|| props.panel_class.clone()),
) )}
aria-labelledby=title_id.to_string() aria-labelledby={title_id.to_string()}
aria-hidden=(!selected).then(|| "true") aria-hidden={(!selected).then(|| "true")}
role="tabpanel" role="tabpanel"
id=panel_id.to_string() id={panel_id.to_string()}
key=*id key={*id}
> >
{ props.panel.clone() } { props.panel.clone() }
</div> </div>
@ -170,10 +163,10 @@ impl<T: Clone + PartialEq + Hash + 'static> Component for Tabs<T> {
} }
} }
fn rendered(&mut self, _first_render: bool) { fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
if self.props.animate { if ctx.props().animate {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
self.props.selected_tab_id.hash(&mut hasher); ctx.props().selected_tab_id.hash(&mut hasher);
let id = hasher.finish(); let id = hasher.finish();
let indicator = self.indicator_ref.cast::<HtmlElement>().unwrap(); let indicator = self.indicator_ref.cast::<HtmlElement>().unwrap();

View file

@ -1,11 +1,6 @@
use crate::{if_html, Icon, IconName, Intent, Text}; use crate::{if_html, Icon, IconName, Intent, Text};
use std::borrow::Cow;
use yew::prelude::*; use yew::prelude::*;
pub struct Tag {
props: TagProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct TagProps { pub struct TagProps {
#[prop_or_default] #[prop_or_default]
@ -36,81 +31,60 @@ pub struct TagProps {
#[prop_or_default] #[prop_or_default]
pub round: bool, pub round: bool,
#[prop_or_default] #[prop_or_default]
pub title: Option<Cow<'static, str>>, pub title: Option<String>,
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
#[prop_or_default] #[prop_or_default]
pub style: Option<Cow<'static, str>>, pub style: Option<String>,
} }
impl Component for Tag { #[function_component(Tag)]
type Message = (); pub fn tag(props: &TagProps) -> Html {
type Properties = TagProps; let icon = if_html!(let Some(icon) = props.icon => <Icon icon={icon} />);
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { let right_icon =
Tag { props } if_html!(let Some(right_icon) = props.right_icon => <Icon icon={right_icon} />);
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender { let remove_button = if_html! {
true let Some(callback) = props.onremove.clone() =>
} html!(
<button
fn change(&mut self, props: Self::Properties) -> ShouldRender { class={classes!("bp3-tag-remove")}
if self.props != props { onclick={callback}
self.props = props; tabindex={props.interactive.then(|| "0")}
true
} else {
false
}
}
fn view(&self) -> Html {
let icon = if_html!(let Some(icon) = self.props.icon => <Icon icon=icon />);
let right_icon =
if_html!(let Some(right_icon) = self.props.right_icon => <Icon icon=right_icon />);
let remove_button = if_html! {
let Some(callback) = self.props.onremove.clone() =>
html!(
<button
class=classes!("bp3-tag-remove")
onclick={callback}
tabindex={self.props.interactive.then(|| "0")}
>
<Icon icon=IconName::SmallCross />
</button>
)
};
html! {
<span
class=classes!(
"bp3-tag",
self.props.intent,
self.props.active.then(|| "bp3-active"),
self.props.fill.then(|| "bp3-fill"),
self.props.interactive.then(|| "bp3-interactive"),
self.props.large.then(|| "bp3-large"),
self.props.minimal.then(|| "bp3-minimal"),
self.props.round.then(|| "bp3-round"),
self.props.class.clone(),
)
style=self.props.style.clone()
onclick={self.props.onclick.clone()}
> >
{icon} <Icon icon={IconName::SmallCross} />
<Text </button>
class=classes!("bp3-fill") )
ellipsize={!self.props.multiline} };
title=self.props.title.clone()
inline=true html! {
> <span
{self.props.children.clone()} class={classes!(
</Text> "bp3-tag",
{right_icon} props.intent,
{remove_button} props.active.then(|| "bp3-active"),
</span> props.fill.then(|| "bp3-fill"),
} props.interactive.then(|| "bp3-interactive"),
props.large.then(|| "bp3-large"),
props.minimal.then(|| "bp3-minimal"),
props.round.then(|| "bp3-round"),
props.class.clone(),
)}
style={props.style.clone()}
onclick={props.onclick.clone()}
>
{icon}
<Text
class={classes!("bp3-fill")}
ellipsize={!props.multiline}
title={props.title.clone()}
inline=true
>
{props.children.clone()}
</Text>
{right_icon}
{remove_button}
</span>
} }
} }

View file

@ -1,10 +1,5 @@
use std::borrow::Cow;
use yew::prelude::*; use yew::prelude::*;
pub struct Text {
props: TextProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct TextProps { pub struct TextProps {
#[prop_or_default] #[prop_or_default]
@ -17,44 +12,23 @@ pub struct TextProps {
#[prop_or_default] #[prop_or_default]
pub inline: bool, pub inline: bool,
#[prop_or_default] #[prop_or_default]
pub title: Option<Cow<'static, str>>, pub title: Option<String>,
#[prop_or_default] #[prop_or_default]
pub style: Option<Cow<'static, str>>, pub style: Option<String>,
} }
impl Component for Text { #[function_component(Text)]
type Message = (); pub fn text(props: &TextProps) -> Html {
type Properties = TextProps; html! {
<@{if props.inline { "span" } else { "div"}}
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { class={classes!(
Text { props } props.class.clone(),
} props.ellipsize.then (|| "bp3-text-overflow-ellipsis"),
)}
fn update(&mut self, _msg: Self::Message) -> ShouldRender { style={props.style.clone()}
true title={props.title.clone()}
} >
{props.children.clone()}
fn change(&mut self, props: Self::Properties) -> ShouldRender { </@>
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<@{if self.props.inline { "span" } else { "div"}}
class=classes!(
self.props.class.clone(),
self.props.ellipsize.then (|| "bp3-text-overflow-ellipsis"),
)
style=self.props.style.clone()
title=self.props.title.clone()
>
{self.props.children.clone()}
</@>
}
} }
} }

View file

@ -1,10 +1,6 @@
use crate::Intent; use crate::Intent;
use yew::prelude::*; use yew::prelude::*;
pub struct TextArea {
props: TextAreaProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct TextAreaProps { pub struct TextAreaProps {
#[prop_or_default] #[prop_or_default]
@ -22,44 +18,23 @@ pub struct TextAreaProps {
#[prop_or_default] #[prop_or_default]
pub small: bool, pub small: bool,
#[prop_or_default] #[prop_or_default]
pub onchange: Option<Callback<ChangeData>>, pub onchange: Option<Callback<Event>>,
} }
impl Component for TextArea { #[function_component(TextArea)]
type Message = (); pub fn text_area(props: &TextAreaProps) -> Html {
type Properties = TextAreaProps; let classes = classes!(
"bp3-input",
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { props.intent,
TextArea { props } props.class.clone(),
} props.fill.then(|| "bp3-fill"),
props.small.then(|| "bp3-small"),
fn update(&mut self, _msg: Self::Message) -> ShouldRender { props.large.then(|| "bp3-large"),
true );
} html! {
<textarea
fn change(&mut self, props: Self::Properties) -> ShouldRender { class={classes}
if self.props != props { onchange={props.onchange.clone()}
self.props = props; />
true
} else {
false
}
}
fn view(&self) -> Html {
let classes = classes!(
"bp3-input",
self.props.intent,
self.props.class.clone(),
self.props.fill.then(|| "bp3-fill"),
self.props.small.then(|| "bp3-small"),
self.props.large.then(|| "bp3-large"),
);
html! {
<textarea
class=classes
onchange=self.props.onchange.clone()
/>
}
} }
} }

View file

@ -6,6 +6,7 @@ use std::cell::{Ref, RefCell, RefMut};
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::rc::Rc; use std::rc::Rc;
use yew::prelude::*; use yew::prelude::*;
@ -41,13 +42,13 @@ impl<T> From<id_tree::Tree<NodeData<T>>> for TreeData<T> {
} }
} }
pub struct Tree<T: Clone> { pub struct Tree<T: Clone + PartialEq> {
props: TreeProps<T>,
previous_expanded_state: RefCell<HashMap<u64, bool>>, previous_expanded_state: RefCell<HashMap<u64, bool>>,
phantom: PhantomData<T>,
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct TreeProps<T: Clone> { pub struct TreeProps<T: Clone + PartialEq> {
#[prop_or_default] #[prop_or_default]
pub is_expanded: bool, pub is_expanded: bool,
pub tree: TreeData<T>, pub tree: TreeData<T>,
@ -112,41 +113,32 @@ impl<T: Clone + PartialEq + 'static> Component for Tree<T> {
type Message = (); type Message = ();
type Properties = TreeProps<T>; type Properties = TreeProps<T>;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Tree { Self {
props,
previous_expanded_state: Default::default(), previous_expanded_state: Default::default(),
phantom: PhantomData,
} }
} }
fn update(&mut self, _msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
true true
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props { let tree = ctx.props().tree.borrow();
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
let tree = self.props.tree.borrow();
let nodes = if let Some(root_id) = tree.root_node_id() { let nodes = if let Some(root_id) = tree.root_node_id() {
self.render_children(root_id, 0) self.render_children(ctx, root_id, 0)
} else { } else {
html!() html!()
}; };
html! { html! {
<div class=classes!( <div class={classes!(
"bp3-tree", "bp3-tree",
self.props.class.clone(), ctx.props().class.clone(),
)> )}>
<ul class=classes!("bp3-tree-node-list")> <ul class={classes!("bp3-tree-node-list")}>
{nodes} {nodes}
</ul> </ul>
</div> </div>
@ -154,9 +146,15 @@ impl<T: Clone + PartialEq + 'static> Component for Tree<T> {
} }
} }
impl<T: Clone> Tree<T> { // FIXME: The 'static bound here is probably wrong. Fix this at the end of PR.
fn render_children(&self, node_id: &NodeId, depth: u32) -> yew::virtual_dom::VNode { impl<T: 'static + Clone + PartialEq> Tree<T> {
let tree = self.props.tree.borrow(); fn render_children(
&self,
ctx: &Context<Self>,
node_id: &NodeId,
depth: u32,
) -> yew::virtual_dom::VNode {
let tree = ctx.props().tree.borrow();
let node = tree.get(node_id).unwrap(); let node = tree.get(node_id).unwrap();
let children = node.children(); let children = node.children();
@ -177,26 +175,26 @@ impl<T: Clone> Tree<T> {
let inner_nodes = if !data.is_expanded && !previous_is_expanded.unwrap_or(true) { let inner_nodes = if !data.is_expanded && !previous_is_expanded.unwrap_or(true) {
Default::default() Default::default()
} else { } else {
self.render_children(node_id, depth + 1) self.render_children(ctx, node_id, depth + 1)
}; };
html! { html! {
<TreeNode <TreeNode
disabled=data.disabled disabled={data.disabled}
has_caret=data.has_caret has_caret={data.has_caret}
icon=data.icon icon={data.icon}
icon_color=data.icon_color.clone() icon_color={data.icon_color.clone()}
icon_intent=data.icon_intent icon_intent={data.icon_intent}
is_expanded=data.is_expanded is_expanded={data.is_expanded}
is_selected=data.is_selected is_selected={data.is_selected}
label=data.label.clone() label={data.label.clone()}
secondary_label=data.secondary_label.clone() secondary_label={data.secondary_label.clone()}
on_collapse=self.props.on_collapse.clone() on_collapse={ctx.props().on_collapse.clone()}
on_expand=self.props.on_expand.clone() on_expand={ctx.props().on_expand.clone()}
onclick=self.props.onclick.clone() onclick={ctx.props().onclick.clone()}
depth=depth depth={depth}
node_id=node_id.clone() node_id={node_id.clone()}
key=key key={key}
> >
{inner_nodes} {inner_nodes}
</TreeNode> </TreeNode>
@ -207,7 +205,6 @@ impl<T: Clone> Tree<T> {
} }
struct TreeNode { struct TreeNode {
props: TreeNodeProps,
handler_caret_click: Callback<MouseEvent>, handler_caret_click: Callback<MouseEvent>,
handler_click: Callback<MouseEvent>, handler_click: Callback<MouseEvent>,
} }
@ -260,34 +257,33 @@ impl Component for TreeNode {
type Message = TreeNodeMessage; type Message = TreeNodeMessage;
type Properties = TreeNodeProps; type Properties = TreeNodeProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
TreeNode { TreeNode {
handler_caret_click: link.callback(TreeNodeMessage::CaretClick), handler_caret_click: ctx.link().callback(TreeNodeMessage::CaretClick),
handler_click: link.callback(TreeNodeMessage::Click), handler_click: ctx.link().callback(TreeNodeMessage::Click),
props,
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
if self.props.disabled { if ctx.props().disabled {
return false; return false;
} }
match msg { match msg {
TreeNodeMessage::CaretClick(event) => { TreeNodeMessage::CaretClick(event) => {
if self.props.is_expanded { if ctx.props().is_expanded {
if let Some(on_collapse) = self.props.on_collapse.as_ref() { if let Some(on_collapse) = ctx.props().on_collapse.as_ref() {
event.stop_propagation(); event.stop_propagation();
on_collapse.emit((self.props.node_id.clone(), event)); on_collapse.emit((ctx.props().node_id.clone(), event));
} }
} else if let Some(on_expand) = self.props.on_expand.as_ref() { } else if let Some(on_expand) = ctx.props().on_expand.as_ref() {
event.stop_propagation(); event.stop_propagation();
on_expand.emit((self.props.node_id.clone(), event)); on_expand.emit((ctx.props().node_id.clone(), event));
} }
} }
TreeNodeMessage::Click(event) => { TreeNodeMessage::Click(event) => {
if let Some(onclick) = self.props.onclick.as_ref() { if let Some(onclick) = ctx.props().onclick.as_ref() {
onclick.emit((self.props.node_id.clone(), event)); onclick.emit((ctx.props().node_id.clone(), event));
} }
} }
} }
@ -295,38 +291,23 @@ impl Component for TreeNode {
false false
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props { let content_style = format!("padding-left: {}px;", 23 * ctx.props().depth);
// crate::log!(
// "rerender {:?} {} {:?}",
// self.props.node_id,
// self.props.children == props.children,
// self.props.icon,
// );
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
let content_style = format!("padding-left: {}px;", 23 * self.props.depth);
html! { html! {
<li class=classes!( <li class={classes!(
"bp3-tree-node", "bp3-tree-node",
self.props.is_selected.then(|| "bp3-tree-node-selected") ctx.props().is_selected.then(|| "bp3-tree-node-selected")
)> )}>
<div <div
class="bp3-tree-node-content" class="bp3-tree-node-content"
style=content_style style={content_style}
onclick=self.handler_click.clone() onclick={self.handler_click.clone()}
> >
{ {
if self.props.has_caret { if ctx.props().has_caret {
let mut class = Classes::from("bp3-tree-node-caret"); let mut class = Classes::from("bp3-tree-node-caret");
class.push(if self.props.is_expanded { class.push(if ctx.props().is_expanded {
"bp3-tree-node-caret-open" "bp3-tree-node-caret-open"
} else { } else {
"bp3-tree-node-caret-closed" "bp3-tree-node-caret-closed"
@ -334,9 +315,9 @@ impl Component for TreeNode {
html! { html! {
<Icon <Icon
class=classes!(class.to_string()) class={classes!(class.to_string())}
icon=IconName::ChevronRight icon={IconName::ChevronRight}
onclick=self.handler_caret_click.clone() onclick={self.handler_caret_click.clone()}
/> />
} }
} else { } else {
@ -346,16 +327,16 @@ impl Component for TreeNode {
} }
} }
<Icon <Icon
class=classes!("bp3-tree-node-icon") class={classes!("bp3-tree-node-icon")}
icon=self.props.icon.unwrap_or_default() icon={ctx.props().icon.unwrap_or_default()}
color=self.props.icon_color.clone() color={ctx.props().icon_color.clone()}
intent=self.props.icon_intent intent={ctx.props().icon_intent}
/> />
<span class=classes!("bp3-tree-node-label")>{self.props.label.clone()}</span> <span class={classes!("bp3-tree-node-label")}>{ctx.props().label.clone()}</span>
{ {
if let Some(label) = self.props.secondary_label.clone() { if let Some(label) = ctx.props().secondary_label.clone() {
html!( html!(
<span class=classes!("bp3-tree-node-secondary-label")> <span class={classes!("bp3-tree-node-secondary-label")}>
{label} {label}
</span> </span>
) )
@ -364,9 +345,9 @@ impl Component for TreeNode {
} }
} }
</div> </div>
<Collapse is_open=self.props.is_expanded> <Collapse is_open={ctx.props().is_expanded}>
<ul class=classes!("bp3-tree-node-list")> <ul class={classes!("bp3-tree-node-list")}>
{self.props.children.clone()} {ctx.props().children.clone()}
</ul> </ul>
</Collapse> </Collapse>
</li> </li>

View file

@ -2,7 +2,7 @@
name = "yewprint-doc" name = "yewprint-doc"
version = "0.1.0" version = "0.1.0"
authors = ["Cecile Tonglet <cecile.tonglet@cecton.com>"] authors = ["Cecile Tonglet <cecile.tonglet@cecton.com>"]
edition = "2018" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -10,11 +10,14 @@ edition = "2018"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
wasm-bindgen = "=0.2" wasm-bindgen = "0.2"
yew = "0.18" web-sys = { version = "0.3", features = ["Window", "MediaQueryList", "Event", "HtmlInputElement"] }
web-sys = { version = "0.3", features = ["Window", "MediaQueryList"] } gloo = "0.6"
yew = "0.19"
yew-router = "0.16"
# yew = { git = "https://github.com/yewstack/yew", branch = "master" }
# yew-router = { git = "https://github.com/yewstack/yew", branch = "master" }
yewprint = { path = ".." } yewprint = { path = ".." }
yew-router = "0.15"
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size # `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default # compared to the default allocator's ~10K. It is slower than the default

View file

@ -22,19 +22,21 @@ use crate::tag::*;
use crate::text::*; use crate::text::*;
use crate::text_area::*; use crate::text_area::*;
use crate::tree::*; use crate::tree::*;
use std::borrow::Cow;
use yew::prelude::*; use yew::prelude::*;
use yew_router::{ use yew_router::prelude::*;
agent::{RouteAgentDispatcher, RouteRequest},
router::Router,
Switch,
};
use yewprint::{IconName, Menu, MenuItem}; use yewprint::{IconName, Menu, MenuItem};
#[function_component(AppRoot)]
pub fn app_root() -> Html {
html! {
<BrowserRouter>
<App></App>
</BrowserRouter>
}
}
pub struct App { pub struct App {
link: ComponentLink<Self>,
dark_theme: bool, dark_theme: bool,
route_dispatcher: RouteAgentDispatcher,
} }
pub enum Msg { pub enum Msg {
@ -46,33 +48,30 @@ impl Component for App {
type Message = Msg; type Message = Msg;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
App { App {
dark_theme: web_sys::window() dark_theme: web_sys::window()
.and_then(|x| x.match_media("(prefers-color-scheme: dark)").ok().flatten()) .and_then(|x| x.match_media("(prefers-color-scheme: dark)").ok().flatten())
.map(|x| x.matches()) .map(|x| x.matches())
.unwrap_or(true), .unwrap_or(true),
link,
route_dispatcher: RouteAgentDispatcher::new(),
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
Msg::ToggleLight => self.dark_theme ^= true, Msg::ToggleLight => self.dark_theme ^= true,
Msg::GoToMenu(doc_menu) => { Msg::GoToMenu(doc_menu) => {
self.route_dispatcher if let Some(history) = ctx.link().history() {
.send(RouteRequest::ChangeRoute(doc_menu.into())); history.push(doc_menu);
} else {
gloo::console::warn!("Could not get history from Context");
}
} }
} }
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let netlify_badge = if self.dark_theme { let netlify_badge = if self.dark_theme {
"https://www.netlify.com/img/global/badges/netlify-color-accent.svg" "https://www.netlify.com/img/global/badges/netlify-color-accent.svg"
} else { } else {
@ -89,223 +88,178 @@ impl Component for App {
IconName::Moon IconName::Moon
}; };
html! { let menu = html! {
<div class=classes!("docs-root", self.dark_theme.then(|| "bp3-dark"))> <Menu>
<div class=classes!("docs-app")> <MenuItem
<div class=classes!("docs-nav-wrapper")> text={html!(go_to_theme_label)}
<div class=classes!("docs-nav")> onclick={ctx.link()
<div class=classes!("docs-nav-title")> .callback(|_| Msg::ToggleLight)}
<a class=classes!("docs-logo") href="/"> icon={go_to_theme_icon}
{crate::include_raw_html!("logo.svg")} />
</a> <MenuItem
<div> text={html!("Button")}
<div class=classes!("bp3-navbar-heading", "docs-heading")> onclick={ctx.link()
{"Yewprint"} .callback(|_| Msg::GoToMenu(DocMenu::Button))}
</div> />
<a <MenuItem
class=classes!("bp3-text-muted") text={html!("ButtonGroup")}
href="https://github.com/yewprint/yewprint" onclick={ctx.link()
target="_blank" .callback(|_| Msg::GoToMenu(DocMenu::ButtonGroup))}
> />
<small>{"View on GitHub"}</small> <MenuItem
</a> text={html!("Callout")}
</div> onclick={ctx.link()
</div> .callback(|_| Msg::GoToMenu(DocMenu::Callout))}
<Menu> />
<MenuItem <MenuItem
text={html!(go_to_theme_label)} text={html!("Card")}
onclick=self.link onclick={ctx.link()
.callback(|_| Msg::ToggleLight) .callback(|_| Msg::GoToMenu(DocMenu::Card))}
icon=go_to_theme_icon />
/> <MenuItem
<MenuItem text={html!("Checkbox")}
text={html!("Button")} onclick={ctx.link()
href=Cow::Borrowed("#button") .callback(|_| Msg::GoToMenu(DocMenu::Checkbox))}
onclick=self.link />
.callback(|_| Msg::GoToMenu(DocMenu::Button)) <MenuItem
/> text={html!("Collapse")}
<MenuItem onclick={ctx.link()
text={html!("ButtonGroup")} .callback(|_| Msg::GoToMenu(DocMenu::Collapse))}
href=Cow::Borrowed("#button-group") />
onclick=self.link <MenuItem
.callback(|_| Msg::GoToMenu(DocMenu::ButtonGroup)) text={html!("ControlGroup")}
/> onclick={ctx.link()
<MenuItem .callback(|_| Msg::GoToMenu(DocMenu::ControlGroup))}
text={html!("Callout")} />
href=Cow::Borrowed("#callout") <MenuItem
onclick=self.link text={html!("Divider")}
.callback(|_| Msg::GoToMenu(DocMenu::Callout)) onclick={ctx.link()
/> .callback(|_| Msg::GoToMenu(DocMenu::Divider))}
<MenuItem />
text={html!("Card")} <MenuItem
href=Cow::Borrowed("#card") text={html!("HtmlSelect")}
onclick=self.link onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::Card)) .callback(|_| Msg::GoToMenu(DocMenu::HtmlSelect))}
/> />
<MenuItem <MenuItem
text={html!("Checkbox")} text={html!("Icon")}
href=Cow::Borrowed("#checkbox") onclick={ctx.link()
onclick=self.link .callback(|_| Msg::GoToMenu(DocMenu::Icon))}
.callback(|_| Msg::GoToMenu(DocMenu::Checkbox)) />
/> <MenuItem
<MenuItem text={html!("InputGroup")}
text={html!("Collapse")} onclick={ctx.link()
href=Cow::Borrowed("#collapse") .callback(|_| Msg::GoToMenu(DocMenu::InputGroup))}
onclick=self.link />
.callback(|_| Msg::GoToMenu(DocMenu::Collapse)) <MenuItem
/> text={html!("Menu")}
<MenuItem onclick={ctx.link()
text={html!("ControlGroup")} .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
href=Cow::Borrowed("#control-group") />
onclick=self.link <MenuItem
.callback(|_| Msg::GoToMenu(DocMenu::ControlGroup)) text={html!("NumericInput")}
/> onclick={ctx.link()
<MenuItem .callback(|_| Msg::GoToMenu(DocMenu::NumericInput))}
text={html!("Divider")} />
href=Cow::Borrowed("#divider") <MenuItem
onclick=self.link text={html!("PanelStack")}
.callback(|_| Msg::GoToMenu(DocMenu::Divider)) onclick={ctx.link()
/> .callback(|_| Msg::GoToMenu(DocMenu::PanelStack))}
<MenuItem />
text={html!("HtmlSelect")} <MenuItem
href=Cow::Borrowed("#html-select") text={html!("ProgressBar")}
onclick=self.link onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::HtmlSelect)) .callback(|_| Msg::GoToMenu(DocMenu::ProgressBar))}
/> />
<MenuItem <MenuItem
text={html!("Icon")} text={html!("Radio")}
href=Cow::Borrowed("#icon") onclick={ctx.link()
onclick=self.link .callback(|_| Msg::GoToMenu(DocMenu::Radio))}
.callback(|_| Msg::GoToMenu(DocMenu::Icon)) />
/> <MenuItem
<MenuItem text={html!("Slider")}
text={html!("InputGroup")} onclick={ctx.link().callback(|_| Msg::GoToMenu(DocMenu::Slider))}
href=Cow::Borrowed("#input-group") />
onclick=self.link <MenuItem
.callback(|_| Msg::GoToMenu(DocMenu::InputGroup)) text={html!("Spinner")}
/> onclick={ctx.link()
<MenuItem .callback(|_| Msg::GoToMenu(DocMenu::Spinner))}
text={html!("Menu")} />
href=Cow::Borrowed("#menu") <MenuItem
onclick=self.link text={html!("Switch")}
.callback(|_| Msg::GoToMenu(DocMenu::Menu)) onclick={ctx.link()
/> .callback(|_| Msg::GoToMenu(DocMenu::Switch))}
<MenuItem />
text={html!("NumericInput")} <MenuItem
href=Cow::Borrowed("#numeric-input") text={html!("Tabs")}
onclick=self.link onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::NumericInput)) .callback(|_| Msg::GoToMenu(DocMenu::Tabs))}
/> />
<MenuItem <MenuItem
text={html!("PanelStack")} text={html!("Tag")}
href=Cow::Borrowed("#panel-stack") onclick={ctx.link()
onclick=self.link .callback(|_| Msg::GoToMenu(DocMenu::Tag))}
.callback(|_| Msg::GoToMenu(DocMenu::PanelStack)) />
/> <MenuItem
<MenuItem text={html!("Text")}
text={html!("ProgressBar")} onclick={ctx.link()
href=Cow::Borrowed("#progress-bar") .callback(|_| Msg::GoToMenu(DocMenu::Text))}
onclick=self.link />
.callback(|_| Msg::GoToMenu(DocMenu::ProgressBar)) <MenuItem
/> text={html!("TextArea")}
<MenuItem onclick={ctx.link()
text={html!("Radio")} .callback(|_| Msg::GoToMenu(DocMenu::TextArea))}
href=Cow::Borrowed("#radio") />
onclick=self.link <MenuItem
.callback(|_| Msg::GoToMenu(DocMenu::Radio)) text={html!("Tree")}
/> onclick={ctx.link()
<MenuItem .callback(|_| Msg::GoToMenu(DocMenu::Tree))}
text={html!("Slider")} />
href=Cow::Borrowed("#slider") // NOTE: thanks to keep this list of <MenuItem> sorted
onclick=self.link.callback(|_| Msg::GoToMenu(DocMenu::Slider)) // alphabetically (except for the light switch)
/> </Menu>
<MenuItem };
text={html!("Spinner")}
href=Cow::Borrowed("#spinner") let navigation = html! {
onclick=self.link <div class={classes!("docs-nav-wrapper")}>
.callback(|_| Msg::GoToMenu(DocMenu::Spinner)) <div class={classes!("docs-nav")}>
/> <div class={classes!("docs-nav-title")}>
<MenuItem <a class={classes!("docs-logo")} href="/">
text={html!("Switch")} {crate::include_raw_html!("logo.svg")}
href=Cow::Borrowed("#switch") </a>
onclick=self.link <div>
.callback(|_| Msg::GoToMenu(DocMenu::Switch)) <div class={classes!("bp3-navbar-heading", "docs-heading")}>
/> {"Yewprint"}
<MenuItem
text={html!("Tabs")}
href=Cow::Borrowed("#tabs")
onclick=self.link
.callback(|_| Msg::GoToMenu(DocMenu::Tabs))
/>
<MenuItem
text={html!("Tag")}
href=Cow::Borrowed("#tag")
onclick=self.link
.callback(|_| Msg::GoToMenu(DocMenu::Tag))
/>
<MenuItem
text={html!("Text")}
href=Cow::Borrowed("#text")
onclick=self.link
.callback(|_| Msg::GoToMenu(DocMenu::Text))
/>
<MenuItem
text={html!("TextArea")}
href=Cow::Borrowed("#textarea")
onclick=self.link
.callback(|_| Msg::GoToMenu(DocMenu::TextArea))
/>
<MenuItem
text={html!("Tree")}
href=Cow::Borrowed("#tree")
onclick=self.link
.callback(|_| Msg::GoToMenu(DocMenu::Tree))
/>
// NOTE: thanks to keep this list of <MenuItem> sorted
// alphabetically (except for the light switch)
</Menu>
<div class="docs-nav-sponsors">
<a href=Cow::Borrowed("https://www.netlify.com")>
<img
src=netlify_badge
alt="Deploys by Netlify"
/>
</a>
</div> </div>
<a
class={classes!("bp3-text-muted")}
href="https://github.com/yewprint/yewprint"
target="_blank"
>
<small>{"View on GitHub"}</small>
</a>
</div> </div>
</div> </div>
<main class=classes!("docs-content-wrapper") role="main"> {{ menu }}
<div class=classes!("docs-page")> <div class="docs-nav-sponsors">
<Router<DocMenu, ()> <a href={"https://www.netlify.com"}>
render=Router::render(|switch: DocMenu| { <img
match switch { src={netlify_badge}
DocMenu::Button | DocMenu::Home => html! (<ButtonDoc />), alt="Deploys by Netlify"
DocMenu::ButtonGroup => html! (<ButtonGroupDoc />),
DocMenu::Callout => html!(<CalloutDoc />),
DocMenu::Card => html!(<CardDoc />),
DocMenu::Checkbox => html!(<CheckboxDoc />),
DocMenu::Collapse => html!(<CollapseDoc />),
DocMenu::ControlGroup => html!(<ControlGroupDoc />),
DocMenu::Divider => html!(<DividerDoc />),
DocMenu::HtmlSelect => html!(<HtmlSelectDoc />),
DocMenu::Icon => html!(<IconDoc />),
DocMenu::InputGroup => html!(<InputGroupDoc />),
DocMenu::Menu => html!(<MenuDoc />),
DocMenu::NumericInput => html!(<NumericInputDoc />),
DocMenu::PanelStack => html!(<PanelStackDoc />),
DocMenu::ProgressBar => html!(<ProgressBarDoc />),
DocMenu::Radio => html!(<RadioDoc />),
DocMenu::Slider => html!(<SliderDoc />),
DocMenu::Spinner => html!(<SpinnerDoc />),
DocMenu::Switch => html!(<SwitchDoc />),
DocMenu::Tabs => html!(<TabsDoc />),
DocMenu::Tag => html!(<TagDoc />),
DocMenu::Text => html!(<TextDoc />),
DocMenu::TextArea => html!(<TextAreaDoc />),
DocMenu::Tree => html!(<TreeDoc />),
}
})
/> />
</a>
</div>
</div>
</div>
};
html! {
<div class={classes!("docs-root", self.dark_theme.then(|| "bp3-dark"))}>
<div class={classes!("docs-app")}>
{{ navigation }}
<main class={classes!("docs-content-wrapper")} role="main">
<div class={classes!("docs-page")}>
<Switch<DocMenu> render={Switch::render(switch)} />
</div> </div>
</main> </main>
</div> </div>
@ -314,56 +268,85 @@ impl Component for App {
} }
} }
#[derive(Debug, Copy, Clone, Switch)] fn switch(route: &DocMenu) -> Html {
match route {
DocMenu::Button | DocMenu::Home => html! (<ButtonDoc />),
DocMenu::ButtonGroup => html! (<ButtonGroupDoc />),
DocMenu::Callout => html!(<CalloutDoc />),
DocMenu::Card => html!(<CardDoc />),
DocMenu::Checkbox => html!(<CheckboxDoc />),
DocMenu::Collapse => html!(<CollapseDoc />),
DocMenu::ControlGroup => html!(<ControlGroupDoc />),
DocMenu::Divider => html!(<DividerDoc />),
DocMenu::HtmlSelect => html!(<HtmlSelectDoc />),
DocMenu::Icon => html!(<IconDoc />),
DocMenu::InputGroup => html!(<InputGroupDoc />),
DocMenu::Menu => html!(<MenuDoc />),
DocMenu::NumericInput => html!(<NumericInputDoc />),
DocMenu::PanelStack => html!(<PanelStackDoc />),
DocMenu::ProgressBar => html!(<ProgressBarDoc />),
DocMenu::Radio => html!(<RadioDoc />),
DocMenu::Slider => html!(<SliderDoc />),
DocMenu::Spinner => html!(<SpinnerDoc />),
DocMenu::Switch => html!(<SwitchDoc />),
DocMenu::Tabs => html!(<TabsDoc />),
DocMenu::Tag => html!(<TagDoc />),
DocMenu::Text => html!(<TextDoc />),
DocMenu::TextArea => html!(<TextAreaDoc />),
DocMenu::Tree => html!(<TreeDoc />),
}
}
#[derive(PartialEq, Clone, Routable)]
pub enum DocMenu { pub enum DocMenu {
#[to = "/#button-group"] #[at("/button-group")]
ButtonGroup, ButtonGroup,
#[to = "/#button"] #[at("/button")]
Button, Button,
#[to = "/#callout"] #[at("/callout")]
Callout, Callout,
#[to = "/#card"] #[at("/card")]
Card, Card,
#[to = "/#checkbox"] #[at("/checkbox")]
Checkbox, Checkbox,
#[to = "/#collapse"] #[at("/collapse")]
Collapse, Collapse,
#[to = "/#control-group"] #[at("/control-group")]
ControlGroup, ControlGroup,
#[to = "/#html-select"] #[at("/html-select")]
HtmlSelect, HtmlSelect,
#[to = "/#divider"] #[at("/divider")]
Divider, Divider,
#[to = "/#icon"] #[at("/icon")]
Icon, Icon,
#[to = "/#input-group"] #[at("/input-group")]
InputGroup, InputGroup,
#[to = "/#menu"] #[at("/menu")]
Menu, Menu,
#[to = "/#numeric-input"] #[at("/numeric-input")]
NumericInput, NumericInput,
#[to = "/#panel-stack"] #[at("/panel-stack")]
PanelStack, PanelStack,
#[to = "/#progress-bar"] #[at("/progress-bar")]
ProgressBar, ProgressBar,
#[to = "/#radio"] #[at("/radio")]
Radio, Radio,
#[to = "/#slider"] #[at("/slider")]
Slider, Slider,
#[to = "/#spinner"] #[at("/spinner")]
Spinner, Spinner,
#[to = "/#switch"] #[at("/switch")]
Switch, Switch,
#[to = "/#tabs"] #[at("/tabs")]
Tabs, Tabs,
#[to = "/#tag"] #[at("/tag")]
Tag, Tag,
#[to = "/#textarea"] #[at("/textarea")]
TextArea, TextArea,
#[to = "/#text"] #[at("/text")]
Text, Text,
#[to = "/#tree"] #[at("/tree")]
Tree, Tree,
#[to = "/"] #[at("/")]
Home, Home,
} }

View file

@ -1,11 +1,6 @@
use std::borrow::Cow;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, ButtonGroup, IconName}; use yewprint::{Button, ButtonGroup, IconName};
pub struct Example {
props: ExampleProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub minimal: bool, pub minimal: bool,
@ -14,40 +9,19 @@ pub struct ExampleProps {
pub vertical: bool, pub vertical: bool,
} }
impl Component for Example { #[function_component(Example)]
type Message = (); pub fn example(props: &ExampleProps) -> Html {
type Properties = ExampleProps; html! {
<ButtonGroup
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { minimal={props.minimal}
Example { props } fill={props.fill}
} large={props.large}
vertical={props.vertical}
fn update(&mut self, _msg: Self::Message) -> ShouldRender { style={"margin:0;"}
true >
} <Button icon={IconName::Database}> {"Queries"}</Button>
<Button icon={IconName::Function}>{"Functions"}</Button>
fn change(&mut self, props: Self::Properties) -> ShouldRender { <Button icon={IconName::Cog}>{"Options"}</Button>
if self.props != props { </ButtonGroup>
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<ButtonGroup
minimal=self.props.minimal
fill=self.props.fill
large=self.props.large
vertical=self.props.vertical
style=Cow::Borrowed("margin:0;")
>
<Button icon=IconName::Database>{"Queries"}</Button>
<Button icon=IconName::Function>{"Functions"}</Button>
<Button icon=IconName::Cog>{"Options"}</Button>
</ButtonGroup>
}
} }
} }

View file

@ -14,9 +14,9 @@ impl Component for ButtonGroupDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
ButtonGroupDoc { ButtonGroupDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
minimal: false, minimal: false,
fill: false, fill: false,
@ -26,16 +26,12 @@ impl Component for ButtonGroupDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -44,19 +40,19 @@ impl Component for ButtonGroupDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Button Group"}</H1> <H1 class={classes!("docs-title")}>{"Button Group"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<ButtonGroupProps <ButtonGroupProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
> >
</ButtonGroupProps> </ButtonGroupProps>
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
} }
@ -65,41 +61,41 @@ impl Component for ButtonGroupDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
ButtonGroupProps for ExampleProps => ButtonGroupProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
minimal: !props.minimal, minimal: !props.minimal,
..props ..props
}) })}
checked=self.props.minimal checked={ctx.props().example_props.minimal}
label=html!("Minimal") label={html!("Minimal")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps{ onclick={self.update_props(ctx.props(), |props, _| ExampleProps{
fill: !props.fill, fill: !props.fill,
..props ..props
}) })}
checked=self.props.fill checked={ctx.props().example_props.fill}
label=html!("Fill") label={html!("Fill")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps{ onclick={self.update_props(ctx.props(), |props, _| ExampleProps{
large: !props.large, large: !props.large,
..props ..props
}) })}
checked=self.props.large checked={ctx.props().example_props.large}
label=html!("Large") label={html!("Large")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
vertical: !props.vertical, vertical: !props.vertical,
..props ..props
}) })}
checked=self.props.vertical checked={ctx.props().example_props.vertical}
label=html!("Vertical") label={html!("Vertical")}
/> />
</div> </div>
} }

View file

@ -2,9 +2,7 @@ use yew::prelude::*;
use yewprint::Button; use yewprint::Button;
pub struct Example { pub struct Example {
link: ComponentLink<Self>,
counter: i64, counter: i64,
props: ExampleProps,
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
@ -27,45 +25,32 @@ impl Component for Example {
type Message = Msg; type Message = Msg;
type Properties = ExampleProps; type Properties = ExampleProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Example { Example { counter: 0 }
counter: 0,
link,
props,
}
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
Msg::AddOne => self.counter += 1, Msg::AddOne => self.counter += 1,
} }
true true
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! { html! {
<div> <div>
<p>{"Counter: "}{self.counter}</p> <p>{"Counter: "}{self.counter}</p>
<div> <div>
<Button <Button
onclick=self.link.callback(|_| Msg::AddOne) onclick={ctx.link().callback(|_| Msg::AddOne)}
minimal=self.props.minimal minimal={ctx.props().minimal}
fill=self.props.fill fill={ctx.props().fill}
small=self.props.small small={ctx.props().small}
outlined=self.props.outlined outlined={ctx.props().outlined}
loading=self.props.loading loading={ctx.props().loading}
large=self.props.large large={ctx.props().large}
active=self.props.active active={ctx.props().active}
disabled=self.props.disabled disabled={ctx.props().disabled}
> >
{"Add 1"} {"Add 1"}
</Button> </Button>

View file

@ -14,9 +14,9 @@ impl Component for ButtonDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
ButtonDoc { ButtonDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
minimal: false, minimal: false,
fill: false, fill: false,
@ -30,37 +30,35 @@ impl Component for ButtonDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
"bp3-code-block" "bp3-code-block"
); );
let props_component = html! {
<ButtonProps
callback={self.callback.clone()}
example_props={example_props.clone()}
/>
};
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Button"}</H1> <H1 class={classes!("docs-title")}>{"Button"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<div> <div>
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(props_component)}
<ButtonProps
callback={self.callback.clone()}
props=example_props.clone()
/>
})
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
</div> </div>
@ -70,77 +68,77 @@ impl Component for ButtonDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
ButtonProps for ExampleProps => ButtonProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
minimal: !props.minimal, minimal: !props.minimal,
..props ..props
}) })}
checked=self.props.minimal checked={ctx.props().example_props.minimal}
label=html!("Minimal") label={html!("Minimal")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
fill: !props.fill, fill: !props.fill,
..props ..props
}) })}
checked=self.props.fill checked={ctx.props().example_props.fill}
label=html!("Fill") label={html!("Fill")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
small: !props.small, small: !props.small,
..props ..props
}) })}
checked=self.props.small checked={ctx.props().example_props.small}
label=html!("Small") label={html!("Small")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
outlined: !props.outlined, outlined: !props.outlined,
..props ..props
}) })}
checked=self.props.outlined checked={ctx.props().example_props.outlined}
label=html!("Outlined") label={html!("Outlined")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
loading: !props.loading, loading: !props.loading,
..props ..props
}) })}
checked=self.props.loading checked={ctx.props().example_props.loading}
label=html!("Loading") label={html!("Loading")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
large: !props.large, large: !props.large,
..props ..props
}) })}
checked=self.props.large checked={ctx.props().example_props.large}
label=html!("Large") label={html!("Large")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
active: !props.active, active: !props.active,
..props ..props
}) })}
checked=self.props.active checked={ctx.props().example_props.active}
label=html!("Active") label={html!("Active")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
disabled: !props.disabled, disabled: !props.disabled,
..props ..props
}) })}
checked=self.props.disabled checked={ctx.props().example_props.disabled}
label=html!("Disabled") label={html!("Disabled")}
/> />
</div> </div>
}
} }
}
} }
crate::build_source_code_component!(); crate::build_source_code_component!();

View file

@ -1,11 +1,6 @@
use std::borrow::Cow;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Callout, Intent}; use yewprint::{Callout, Intent};
pub struct Example {
props: ExampleProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub intent: Option<Intent>, pub intent: Option<Intent>,
@ -13,36 +8,15 @@ pub struct ExampleProps {
pub show_title: bool, pub show_title: bool,
} }
impl Component for Example { #[function_component(Example)]
type Message = (); pub fn example(props: &ExampleProps) -> Html {
type Properties = ExampleProps; html! {
<Callout
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { title={props.show_title.then(|| "Visually important content")}
Example { props } without_icon={!props.show_icon}
} intent={props.intent}
>
fn update(&mut self, _msg: Self::Message) -> ShouldRender { <p>{"The Callout element's background reflects its intent, if any."}</p>
true </Callout>
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<Callout
title=self.props.show_title.then(|| Cow::Borrowed("Visually important content"))
without_icon=!self.props.show_icon
intent=self.props.intent
>
<p>{"The Callout element's background reflects its intent, if any."}</p>
</Callout>
}
} }
} }

View file

@ -14,9 +14,9 @@ impl Component for CalloutDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
CalloutDoc { CalloutDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
show_icon: false, show_icon: false,
intent: None, intent: None,
@ -25,16 +25,12 @@ impl Component for CalloutDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -43,18 +39,18 @@ impl Component for CalloutDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Callout"}</H1> <H1 class={classes!("docs-title")}>{"Callout"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<CalloutProps <CalloutProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
} }
@ -63,26 +59,26 @@ impl Component for CalloutDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
CalloutProps for ExampleProps => CalloutProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<div> <div>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
show_icon: !props.show_icon, show_icon: !props.show_icon,
..props ..props
}) })}
checked=self.props.show_icon checked={ctx.props().example_props.show_icon}
label=html!("Show/hide icon") label={html!("Show/hide icon")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
show_title: !props.show_title, show_title: !props.show_title,
..props ..props
}) })}
checked=self.props.show_title checked={ctx.props().example_props.show_title}
label=html!("Show/hide title") label={html!("Show/hide title")}
/> />
<p>{"Select intent:"}</p> <p>{"Select intent:"}</p>
<HtmlSelect<Option<Intent>> <HtmlSelect<Option<Intent>>
@ -93,10 +89,10 @@ crate::build_example_prop_component! {
(Some(Intent::Warning), "Warning".to_string()), (Some(Intent::Warning), "Warning".to_string()),
(Some(Intent::Danger), "Danger".to_string()), (Some(Intent::Danger), "Danger".to_string()),
]} ]}
onchange=self.update_props(|props, intent| ExampleProps { onchange={self.update_props(ctx.props(), |props, intent| ExampleProps {
intent, intent,
..props ..props
}) })}
/> />
</div> </div>
</div> </div>

View file

@ -1,47 +1,22 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Card, Elevation}; use yewprint::{Card, Elevation};
pub struct Example {
props: ExampleProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub elevation: Elevation, pub elevation: Elevation,
pub interactive: bool, pub interactive: bool,
} }
impl Component for Example { #[function_component(Example)]
type Message = (); pub fn example(props: &ExampleProps) -> Html {
type Properties = ExampleProps; html! {
<Card elevation={props.elevation} interactive={props.interactive}>
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { <p>
Example { props } {
} "This is a card component. The elevation of the card can be adjusted. \
An interactive card reacts to being moused over."
fn update(&mut self, _msg: Self::Message) -> ShouldRender { }
true </p>
} </Card>
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<Card elevation=self.props.elevation interactive=self.props.interactive>
<p>
{
"This is a card component. The elevation of the card can be adjusted. \
An interactive card reacts to being moused over."
}
</p>
</Card>
}
} }
} }

View file

@ -14,9 +14,9 @@ impl Component for CardDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
CardDoc { CardDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
elevation: Elevation::Level0, elevation: Elevation::Level0,
interactive: false, interactive: false,
@ -24,16 +24,12 @@ impl Component for CardDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -42,18 +38,18 @@ impl Component for CardDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Card"}</H1> <H1 class={classes!("docs-title")}>{"Card"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<CardProps <CardProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
} }
@ -62,18 +58,20 @@ impl Component for CardDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
CardProps for ExampleProps => CardProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
let props = ctx.props();
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<div> <div>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(props, |props, _| ExampleProps {
interactive: !props.interactive, interactive: !props.interactive,
..props ..props
}) })}
checked=self.props.interactive checked={ctx.props().example_props.interactive}
label=html!("Toggle interaction") label={html!("Toggle interaction")}
/> />
<p>{"Elevation:"}</p> <p>{"Elevation:"}</p>
<HtmlSelect<Elevation> <HtmlSelect<Elevation>
@ -84,11 +82,11 @@ crate::build_example_prop_component! {
(Elevation::Level3, "Level 3".to_string()), (Elevation::Level3, "Level 3".to_string()),
(Elevation::Level4, "Level 4".to_string()), (Elevation::Level4, "Level 4".to_string()),
]} ]}
value=self.props.elevation value={ctx.props().example_props.elevation}
onchange=self.update_props(|props, elevation| ExampleProps { onchange={self.update_props(props, |props, elevation| ExampleProps {
elevation, elevation,
..props ..props
}) })}
/> />
</div> </div>
</div> </div>

View file

@ -1,10 +1,6 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Checkbox, Label}; use yewprint::{Checkbox, Label};
pub struct Example {
props: ExampleProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub disabled: bool, pub disabled: bool,
@ -12,50 +8,29 @@ pub struct ExampleProps {
pub large: bool, pub large: bool,
} }
impl Component for Example { #[function_component(Example)]
type Message = (); pub fn example(props: &ExampleProps) -> Html {
type Properties = ExampleProps; html! {
<div>
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { <Label>{"Assign responsability"}</Label>
Example { props } <Checkbox
} disabled={props.disabled}
inline={props.inline}
fn update(&mut self, _msg: Self::Message) -> ShouldRender { large={props.large}
true label={html!("Gilad Gray")}
} />
<Checkbox
fn change(&mut self, props: Self::Properties) -> ShouldRender { disabled={props.disabled}
if self.props != props { inline={props.inline}
self.props = props; large={props.large}
true label={html!("Jason Killian")}
} else { />
false <Checkbox
} disabled={props.disabled}
} inline={props.inline}
large={props.large}
fn view(&self) -> Html { label={html!("Antoine Llorca")}
html! { />
<div> </div>
<Label>{"Assign responsability"}</Label>
<Checkbox
disabled=self.props.disabled
inline=self.props.inline
large=self.props.large
label=html!("Gilad Gray")
/>
<Checkbox
disabled=self.props.disabled
inline=self.props.inline
large=self.props.large
label=html!("Jason Killian")
/>
<Checkbox
disabled=self.props.disabled
inline=self.props.inline
large=self.props.large
label=html!("Antoine Llorca")
/>
</div>
}
} }
} }

View file

@ -14,9 +14,9 @@ impl Component for CheckboxDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
CheckboxDoc { CheckboxDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
disabled: false, disabled: false,
inline: false, inline: false,
@ -25,16 +25,12 @@ impl Component for CheckboxDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -43,18 +39,18 @@ impl Component for CheckboxDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Checkbox"}</H1> <H1 class={classes!("docs-title")}>{"Checkbox"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<CheckboxProps <CheckboxProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
} }
@ -63,33 +59,33 @@ impl Component for CheckboxDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
CheckboxProps for ExampleProps => CheckboxProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
disabled: !props.disabled, disabled: !props.disabled,
..props ..props
}) })}
checked=self.props.disabled checked={ctx.props().example_props.disabled}
label=html!("Disabled") label={html!("Disabled")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
inline: !props.inline, inline: !props.inline,
..props ..props
}) })}
checked=self.props.inline checked={ctx.props().example_props.inline}
label=html!("Inline") label={html!("Inline")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
large: !props.large, large: !props.large,
..props ..props
}) })}
checked=self.props.large checked={ctx.props().example_props.large}
label=html!("Large") label={html!("Large")}
/> />
</div> </div>
} }

View file

@ -2,7 +2,6 @@ use yew::prelude::*;
use yewprint::{Button, Collapse}; use yewprint::{Button, Collapse};
pub struct Example { pub struct Example {
link: ComponentLink<Self>,
collapsed: bool, collapsed: bool,
} }
@ -14,34 +13,27 @@ impl Component for Example {
type Message = Msg; type Message = Msg;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Example { Example { collapsed: true }
collapsed: true,
link,
}
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
Msg::ToggleCollapse => self.collapsed ^= true, Msg::ToggleCollapse => self.collapsed ^= true,
} }
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let logs = include_str!("example.log"); let logs = include_str!("example.log");
html! { html! {
<div> <div>
<Button onclick=self.link.callback(|_| Msg::ToggleCollapse)> <Button onclick={ctx.link().callback(|_| Msg::ToggleCollapse)}>
{"Toggle collapse"} {"Toggle collapse"}
</Button> </Button>
<Collapse <Collapse
is_open=!self.collapsed is_open={!self.collapsed}
keep_children_mounted=true keep_children_mounted=true
> >
<pre class="bp3-code-block"> <pre class="bp3-code-block">

View file

@ -11,19 +11,15 @@ impl Component for CollapseDoc {
type Message = (); type Message = ();
type Properties = (); type Properties = ();
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
CollapseDoc CollapseDoc
} }
fn update(&mut self, _msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
"bp3-code-block" "bp3-code-block"
@ -31,9 +27,9 @@ impl Component for CollapseDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Collapse"}</H1> <H1 class={classes!("docs-title")}>{"Collapse"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer source=source> <ExampleContainer source={source}>
<Example /> <Example />
</ExampleContainer> </ExampleContainer>
</div> </div>

View file

@ -1,56 +1,31 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, ControlGroup, HtmlSelect, IconName, InputGroup}; use yewprint::{Button, ControlGroup, HtmlSelect, IconName, InputGroup};
pub struct Example {
props: ExampleProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub fill: bool, pub fill: bool,
pub vertical: bool, pub vertical: bool,
} }
impl Component for Example { #[function_component(Example)]
type Message = (); pub fn example(props: &ExampleProps) -> Html {
type Properties = ExampleProps; html! {
<ControlGroup
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { fill={props.fill}
Example { props } vertical={props.vertical}
} >
<HtmlSelect<Option<Sorting>>
fn update(&mut self, _msg: Self::Message) -> ShouldRender { options={vec![
true (None, "Filter".to_string()),
} (Some(Sorting::NameAscending), "Name - ascending".to_string()),
(Some(Sorting::NameDescending), "Name - descending".to_string()),
fn change(&mut self, props: Self::Properties) -> ShouldRender { (Some(Sorting::PriceAscending), "Price - ascending".to_string()),
if self.props != props { (Some(Sorting::PriceDescending), "Price - descending".to_string()),
self.props = props; ]}
true />
} else { <InputGroup placeholder="Find filters..." />
false <Button icon={IconName::ArrowRight} />
} </ControlGroup>
}
fn view(&self) -> Html {
html! {
<ControlGroup
fill=self.props.fill
vertical=self.props.vertical
>
<HtmlSelect<Option<Sorting>>
options={vec![
(None, "Filter".to_string()),
(Some(Sorting::NameAscending), "Name - ascending".to_string()),
(Some(Sorting::NameDescending), "Name - descending".to_string()),
(Some(Sorting::PriceAscending), "Price - ascending".to_string()),
(Some(Sorting::PriceDescending), "Price - descending".to_string()),
]}
/>
<InputGroup placeholder="Find filters..." />
<Button icon=IconName::ArrowRight />
</ControlGroup>
}
} }
} }

View file

@ -14,9 +14,9 @@ impl Component for ControlGroupDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
ControlGroupDoc { ControlGroupDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
fill: false, fill: false,
vertical: false, vertical: false,
@ -24,16 +24,12 @@ impl Component for ControlGroupDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -42,19 +38,19 @@ impl Component for ControlGroupDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"ControlGroup"}</H1> <H1 class={classes!("docs-title")}>{"ControlGroup"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<ControlGroupProps <ControlGroupProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
> >
</ControlGroupProps> </ControlGroupProps>
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
} }
@ -63,25 +59,25 @@ impl Component for ControlGroupDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
ControlGroupProps for ExampleProps => ControlGroupProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
fill: !props.fill, fill: !props.fill,
..props ..props
}) })}
checked=self.props.fill checked={ctx.props().example_props.fill}
label=html!("Fill") label={html!("Fill")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
vertical: !props.vertical, vertical: !props.vertical,
..props ..props
}) })}
checked=self.props.vertical checked={ctx.props().example_props.vertical}
label=html!("Vertical") label={html!("Vertical")}
/> />
</div> </div>
} }

View file

@ -1,48 +1,22 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, ButtonGroup, Divider}; use yewprint::{Button, ButtonGroup, Divider};
pub struct Example {
props: ExampleProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub vertical: bool, pub vertical: bool,
} }
#[function_component(Example)]
impl Component for Example { pub fn example(props: &ExampleProps) -> Html {
type Message = (); html! {
type Properties = ExampleProps; <ButtonGroup vertical={props.vertical}>
<Button>{"File"}</Button>
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { <Button>{"Edit"}</Button>
Example { props } <Divider vertical={props.vertical} />
} <Button>{"Create"}</Button>
<Button>{"Delete"}</Button>
fn update(&mut self, _msg: Self::Message) -> ShouldRender { <Divider vertical={props.vertical} />
true // <Button icon=IconName::Add />
} // <Button icon=IconName::Remove />
</ButtonGroup>
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<ButtonGroup vertical=self.props.vertical>
<Button>{"File"}</Button>
<Button>{"Edit"}</Button>
<Divider vertical=self.props.vertical />
<Button>{"Create"}</Button>
<Button>{"Delete"}</Button>
<Divider vertical=self.props.vertical />
// <Button icon=IconName::Add />
// <Button icon=IconName::Remove />
</ButtonGroup>
}
} }
} }

View file

@ -14,23 +14,19 @@ impl Component for DividerDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
DividerDoc { DividerDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { vertical: false }, state: ExampleProps { vertical: false },
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -39,18 +35,18 @@ impl Component for DividerDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Divider"}</H1> <H1 class={classes!("docs-title")}>{"Divider"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<DividerProps <DividerProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
} }
@ -59,16 +55,16 @@ impl Component for DividerDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
DividerProps for ExampleProps => DividerProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
vertical: !props.vertical vertical: !props.vertical
}) })}
checked=self.props.vertical checked={ctx.props().example_props.vertical}
label=html!("Vertical") label={html!("Vertical")}
/> />
</div> </div>
} }

View file

@ -3,8 +3,6 @@ use yewprint::{Button, Collapse, IconName, Intent};
pub struct ExampleContainer { pub struct ExampleContainer {
collapsed: bool, collapsed: bool,
props: ExampleContainerProps,
link: ComponentLink<Self>,
} }
pub enum Msg { pub enum Msg {
@ -23,41 +21,28 @@ impl Component for ExampleContainer {
type Message = Msg; type Message = Msg;
type Properties = ExampleContainerProps; type Properties = ExampleContainerProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
ExampleContainer { ExampleContainer { collapsed: true }
collapsed: true,
props,
link,
}
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
Msg::ToggleSource => self.collapsed ^= true, Msg::ToggleSource => self.collapsed ^= true,
} }
true true
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! { html! {
<div class=classes!("docs-example-wrapper")> <div class={classes!("docs-example-wrapper")}>
<div class=classes!("docs-example-frame", "docs-example-frame-row")> <div class={classes!("docs-example-frame", "docs-example-frame-row")}>
<div class=classes!("docs-example")> <div class={classes!("docs-example")}>
{self.props.children.clone()} {ctx.props().children.clone()}
</div> </div>
{ {
if let Some(props) = self.props.props.clone() { if let Some(props) = ctx.props().props.clone() {
html! { html! {
<div class=classes!("docs-example-options")> <div class={classes!("docs-example-options")}>
{props} {props}
</div> </div>
} }
@ -66,21 +51,21 @@ impl Component for ExampleContainer {
} }
} }
</div> </div>
<div class=classes!("docs-source")> <div class={classes!("docs-source")}>
<Button <Button
icon=IconName::Code icon={IconName::Code}
fill={true} fill={{true}}
intent={Intent::Primary} intent={{Intent::Primary}}
minimal={true} minimal={{true}}
onclick=self.link.callback(|_| Msg::ToggleSource) onclick={ctx.link().callback(|_| Msg::ToggleSource)}
> >
{"View source"} {"View source"}
</Button> </Button>
<Collapse <Collapse
is_open=!self.collapsed is_open={!self.collapsed}
keep_children_mounted=true keep_children_mounted=true
> >
{self.props.source.clone()} {ctx.props().source.clone()}
</Collapse> </Collapse>
</div> </div>
</div> </div>
@ -88,47 +73,40 @@ impl Component for ExampleContainer {
} }
} }
/// The macro generates the component that will be used to render the editable
/// properties of an associated example.
#[macro_export] #[macro_export]
macro_rules! build_example_prop_component { macro_rules! build_example_prop_component {
($name:ident for $prop_component:ty => $($view:tt)*) => { ($name:ident for $prop_component:ty => $($view:tt)*) => {
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct $name { pub struct $name {
callback: Callback<$prop_component>, callback: Callback<$prop_component>,
props: $prop_component, example_props: $prop_component
} }
impl Component for $name { impl Component for $name {
type Message = (); type Message = ();
type Properties = Self; type Properties = Self;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
props ctx.props().clone()
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props.props || self.callback != props.callback {
self.props = props.props;
self.callback = props.callback;
true
} else {
false
}
} }
$($view)* $($view)*
} }
impl $name { impl $name {
/// Propagate the prop changes to the parent so the example is
/// re-rendered.
fn update_props<T>( fn update_props<T>(
&self, &self,
props: &Self,
updater: impl Fn($prop_component, T) -> $prop_component + 'static, updater: impl Fn($prop_component, T) -> $prop_component + 'static,
) -> Callback<T> { ) -> Callback<T> {
let props = self.props.clone(); let example_props = props.example_props.clone();
self.callback.clone().reform(move |event| updater(props.clone(), event)) self.callback
.clone()
.reform(move |event| updater(example_props.clone(), event))
} }
} }
}; };

View file

@ -2,8 +2,6 @@ use yew::prelude::*;
use yewprint::{HtmlSelect, Text}; use yewprint::{HtmlSelect, Text};
pub struct Example { pub struct Example {
props: ExampleProps,
link: ComponentLink<Self>,
log_level: LogLevel, log_level: LogLevel,
} }
@ -19,29 +17,18 @@ impl Component for Example {
type Message = LogLevel; type Message = LogLevel;
type Properties = ExampleProps; type Properties = ExampleProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Example { Example {
props,
link,
log_level: LogLevel::Info, log_level: LogLevel::Info,
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.log_level = msg; self.log_level = msg;
true true
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! { html! {
<div style="width: 400px; text-align: center;"> <div style="width: 400px; text-align: center;">
<HtmlSelect<LogLevel> <HtmlSelect<LogLevel>
@ -53,13 +40,13 @@ impl Component for Example {
(LogLevel::Error, "ERROR".to_string()), (LogLevel::Error, "ERROR".to_string()),
(LogLevel::Off, "OFF".to_string()), (LogLevel::Off, "OFF".to_string()),
]} ]}
minimal=self.props.minimal minimal={ctx.props().minimal}
fill=self.props.fill fill={ctx.props().fill}
disabled=self.props.disabled disabled={ctx.props().disabled}
large=self.props.large large={ctx.props().large}
value=Some(self.log_level) value={Some(self.log_level)}
onchange=self.link.callback(|x| x) onchange={ctx.link().callback(|x| x)}
title=format!("Selected: {:?}", self.log_level) title={format!("Selected: {:?}", self.log_level)}
/> />
<Text>{format!("Selected: {:?}", self.log_level)}</Text> <Text>{format!("Selected: {:?}", self.log_level)}</Text>
</div> </div>

View file

@ -14,9 +14,9 @@ impl Component for HtmlSelectDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
HtmlSelectDoc { HtmlSelectDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
minimal: false, minimal: false,
fill: false, fill: false,
@ -26,16 +26,12 @@ impl Component for HtmlSelectDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -44,19 +40,19 @@ impl Component for HtmlSelectDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"HtmlSelect"}</H1> <H1 class={classes!("docs-title")}>{"HtmlSelect"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<div> <div>
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<HtmlSelectProps <HtmlSelectProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
</div> </div>
@ -66,41 +62,41 @@ impl Component for HtmlSelectDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
HtmlSelectProps for ExampleProps => HtmlSelectProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
minimal: !props.minimal, minimal: !props.minimal,
..props ..props
}) })}
checked=self.props.minimal checked={ctx.props().example_props.minimal}
label=html!("Minimal") label={html!("Minimal")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps{ onclick={self.update_props(ctx.props(), |props, _| ExampleProps{
fill: !props.fill, fill: !props.fill,
..props ..props
}) })}
checked=self.props.fill checked={ctx.props().example_props.fill}
label=html!("Fill") label={html!("Fill")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
disabled: !props.disabled, disabled: !props.disabled,
..props ..props
}) })}
checked=self.props.disabled checked={ctx.props().example_props.disabled}
label=html!("Disabled") label={html!("Disabled")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps{ onclick={self.update_props(ctx.props(), |props, _| ExampleProps{
large: !props.large, large: !props.large,
..props ..props
}) })}
checked=self.props.large checked={ctx.props().example_props.large}
label=html!("Large") label={html!("Large")}
/> />
</div> </div>
} }

View file

@ -1,26 +1,11 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Icon, IconName}; use yewprint::{Icon, IconName};
pub struct Example {} #[function_component(Example)]
pub fn example() -> Html {
impl Component for Example { html! {
type Message = (); <div>
type Properties = (); <Icon icon={IconName::Print} />
</div>
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self {
Example {}
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
true
}
fn view(&self) -> Html {
html! {
<div>
<Icon icon=IconName::Print />
</div>
}
} }
} }

View file

@ -11,19 +11,15 @@ impl Component for IconDoc {
type Message = (); type Message = ();
type Properties = (); type Properties = ();
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
IconDoc IconDoc
} }
fn update(&mut self, _msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
"bp3-code-block" "bp3-code-block"
@ -31,9 +27,9 @@ impl Component for IconDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Icon"}</H1> <H1 class={classes!("docs-title")}>{"Icon"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer source=source> <ExampleContainer source={source}>
<Example /> <Example />
</ExampleContainer> </ExampleContainer>
</div> </div>

View file

@ -1,9 +1,9 @@
use gloo::dialogs::alert;
use web_sys::HtmlInputElement;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, IconName, InputGroup, Tag}; use yewprint::{Button, IconName, InputGroup, Tag};
pub struct Example { pub struct Example {
link: ComponentLink<Self>,
props: ExampleProps,
histogram_value: String, histogram_value: String,
password_value: String, password_value: String,
password_strength: Html, password_strength: Html,
@ -29,22 +29,12 @@ pub enum Msg {
Noop, Noop,
} }
macro_rules! alert {
($($arg:tt)*) => {
yew::services::DialogService::alert(&format!(
$($arg)*
))
};
}
impl Component for Example { impl Component for Example {
type Message = Msg; type Message = Msg;
type Properties = ExampleProps; type Properties = ExampleProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Example { Example {
props,
link,
histogram_value: Default::default(), histogram_value: Default::default(),
password_value: Default::default(), password_value: Default::default(),
password_strength: Default::default(), password_strength: Default::default(),
@ -52,10 +42,10 @@ impl Component for Example {
} }
} }
fn update(&mut self, msg: Self::Message) -> bool { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
Msg::AddHistogramEntry => { Msg::AddHistogramEntry => {
alert!("You sent: {}", self.histogram_value); alert(&format!("You sent: {}", self.histogram_value));
self.histogram_value = Default::default(); self.histogram_value = Default::default();
true true
} }
@ -64,7 +54,7 @@ impl Component for Example {
true true
} }
Msg::AddPasswordEntry => { Msg::AddPasswordEntry => {
alert!("You sent: {}", self.password_value); alert(&format!("You sent: {}", self.password_value));
self.password_value = Default::default(); self.password_value = Default::default();
true true
} }
@ -81,7 +71,7 @@ impl Component for Example {
true true
} }
Msg::AddTagsEntry => { Msg::AddTagsEntry => {
alert!("You sent: {}", self.tags_value); alert(&format!("You sent: {}", self.tags_value));
self.tags_value = Default::default(); self.tags_value = Default::default();
true true
} }
@ -93,74 +83,76 @@ impl Component for Example {
} }
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! { html! {
<> <>
<InputGroup <InputGroup
fill=self.props.fill fill={ctx.props().fill}
large=self.props.large large={ctx.props().large}
small=self.props.small small={ctx.props().small}
round=self.props.round round={ctx.props().round}
disabled=self.props.disabled disabled={ctx.props().disabled}
left_icon=IconName::Filter left_icon={IconName::Filter}
placeholder={"Filter histogram..."} placeholder={"Filter histogram..."}
value=self.histogram_value.clone() value={self.histogram_value.clone()}
oninput=self.link.callback(|e: InputData| Msg::UpdateHistogram(e.value)) oninput={ctx.link().callback(|e: InputEvent| {
onkeydown=self.link.callback(|e: KeyboardEvent| { let value = e.target_unchecked_into::<HtmlInputElement>().value();
Msg::UpdateHistogram(value)
})}
onkeydown={ctx.link().callback(|e: KeyboardEvent| {
if e.key() == "Enter" { Msg::AddHistogramEntry } else { Msg::Noop } if e.key() == "Enter" { Msg::AddHistogramEntry } else { Msg::Noop }
}) })}
/> />
<InputGroup <InputGroup
fill=self.props.fill fill={ctx.props().fill}
large=self.props.large large={ctx.props().large}
small=self.props.small small={ctx.props().small}
round=self.props.round round={ctx.props().round}
disabled=self.props.disabled disabled={ctx.props().disabled}
left_element=self.password_strength.clone() left_element={self.password_strength.clone()}
placeholder={"Enter your password..."} placeholder={"Enter your password..."}
value=self.password_value.clone() value={self.password_value.clone()}
oninput=self.link.callback(|e: InputData| Msg::UpdatePassword(e.value)) oninput={ctx.link().callback(|e: InputEvent| {
onkeydown=self.link.callback(|e: KeyboardEvent| { let value = e.target_unchecked_into::<HtmlInputElement>().value();
Msg::UpdatePassword(value)
})}
onkeydown={ctx.link().callback(|e: KeyboardEvent| {
if e.key() == "Enter" { Msg::AddPasswordEntry } else { Msg::Noop } if e.key() == "Enter" { Msg::AddPasswordEntry } else { Msg::Noop }
}) })}
right_element=html! { right_element={{ html! {
<Button <Button
icon=IconName::Lock icon={IconName::Lock}
minimal=true minimal={true}
disabled=self.props.disabled disabled={ctx.props().disabled}
/> />
} }}}
/> />
<InputGroup <InputGroup
fill=self.props.fill fill={ctx.props().fill}
large=self.props.large large={ctx.props().large}
small=self.props.small small={ctx.props().small}
round=self.props.round round={ctx.props().round}
disabled=self.props.disabled disabled={ctx.props().disabled}
left_icon=IconName::Tag left_icon={IconName::Tag}
placeholder={"Find tags"} placeholder={"Find tags"}
value=self.tags_value.clone() value={self.tags_value.clone()}
oninput=self.link.callback(|e: InputData| Msg::UpdateTags(e.value)) oninput={ctx.link().callback(|e: InputEvent| {
onkeydown=self.link.callback(|e: KeyboardEvent| { let value = e.target_unchecked_into::<HtmlInputElement>().value();
Msg::UpdateTags(value)
})}
onkeydown={ctx.link().callback(|e: KeyboardEvent| {
if e.key() == "Enter" { Msg::AddTagsEntry } else { Msg::Noop } if e.key() == "Enter" { Msg::AddTagsEntry } else { Msg::Noop }
}) })}
right_element=html! { right_element={{
<Tag html!{
minimal=true <Tag
round=self.props.round minimal={true}
> round={ctx.props().round}
{{10000 / 1.max(self.tags_value.len().pow(2))}} >
</Tag> { (10000 / 1.max(self.tags_value.len().pow(2))).to_string() }
} </Tag>
}
}}
/> />
</> </>
} }

View file

@ -14,9 +14,9 @@ impl Component for InputGroupDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
InputGroupDoc { InputGroupDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
disabled: false, disabled: false,
fill: false, fill: false,
@ -27,16 +27,12 @@ impl Component for InputGroupDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -45,19 +41,19 @@ impl Component for InputGroupDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"InputGroup"}</H1> <H1 class={classes!("docs-title")}>{"InputGroup"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<InputGroupProps <InputGroupProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
> >
</InputGroupProps> </InputGroupProps>
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
} }
@ -66,49 +62,49 @@ impl Component for InputGroupDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
InputGroupProps for ExampleProps => InputGroupProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
disabled: !props.disabled, disabled: !props.disabled,
..props ..props
}) })}
checked=self.props.disabled checked={ctx.props().example_props.disabled}
label=html!("Disabled") label={html!("Disabled")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
fill: !props.fill, fill: !props.fill,
..props ..props
}) })}
checked=self.props.fill checked={ctx.props().example_props.fill}
label=html!("Fill") label={html!("Fill")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
large: !props.large, large: !props.large,
..props ..props
}) })}
checked=self.props.large checked={ctx.props().example_props.large}
label=html!("Large") label={html!("Large")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
small: !props.small, small: !props.small,
..props ..props
}) })}
checked=self.props.small checked={ctx.props().example_props.small}
label=html!("Small") label={html!("Small")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
round: !props.round, round: !props.round,
..props ..props
}) })}
checked=self.props.round checked={ctx.props().example_props.round}
label=html!("Round") label={html!("Round")}
/> />
</div> </div>
} }

View file

@ -32,17 +32,10 @@ pub use app::*;
pub use example::*; pub use example::*;
pub use logo::*; pub use logo::*;
#[macro_export]
macro_rules! log {
($s:expr $(,$args:expr)*) => {{
yew::services::ConsoleService::log(format!($s $(,$args)*).as_str());
}};
}
#[macro_export] #[macro_export]
macro_rules! include_raw_html { macro_rules! include_raw_html {
($file:expr $(, $class:expr)?) => {{ ($file:expr $(, $class:expr)?) => {{
yew::virtual_dom::VNode::VRef(yew::web_sys::Node::from({ yew::virtual_dom::VNode::VRef(web_sys::Node::from({
let div = web_sys::window() let div = web_sys::window()
.unwrap() .unwrap()
.document() .document()
@ -88,27 +81,23 @@ macro_rules! build_source_code_component {
type Message = (); type Message = ();
type Properties = (); type Properties = ();
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
let url = SourceCodeUrl::generate_url(); let url = SourceCodeUrl::generate_url();
Self { url } Self { url }
} }
fn update(&mut self, _msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
use yewprint::Text; use yewprint::Text;
html! { html! {
<a <a
class=classes!("bp3-text-muted") class={classes!("bp3-text-muted")}
href=self.url.clone() href={self.url.clone()}
target="_blank" target="_blank"
> >
<Text>{"Go to source code"}</Text> <Text>{"Go to source code"}</Text>
@ -142,7 +131,7 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
pub fn run_app() -> Result<(), wasm_bindgen::JsValue> { pub fn run_app() -> Result<(), wasm_bindgen::JsValue> {
#[cfg(feature = "console_error_panic_hook")] #[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
yew::start_app::<app::App>(); yew::start_app::<app::AppRoot>();
Ok(()) Ok(())
} }

View file

@ -1,37 +1,12 @@
use yew::prelude::*; use yew::prelude::*;
pub struct Logo {
props: LogoProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct LogoProps { pub struct LogoProps {
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
} }
impl Component for Logo { #[function_component(Logo)]
type Message = (); pub fn logo(props: &LogoProps) -> Html {
type Properties = LogoProps; crate::include_raw_html!("logo.svg", &props.class.to_string())
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
Self { props }
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
todo!()
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if props != self.props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
crate::include_raw_html!("logo.svg", &self.props.class.to_string())
}
} }

View file

@ -1,11 +1,8 @@
use crate::{DocMenu, Logo}; use crate::{DocMenu, Logo};
use std::borrow::Cow;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Icon, IconName, Menu, MenuDivider, MenuItem}; use yewprint::{Icon, IconName, Menu, MenuDivider, MenuItem};
pub struct Example { pub struct Example {}
link: ComponentLink<Self>,
}
pub enum Msg { pub enum Msg {
GoToMenu(DocMenu), GoToMenu(DocMenu),
@ -15,107 +12,106 @@ impl Component for Example {
type Message = Msg; type Message = Msg;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Self { link } Self {}
} }
fn update(&mut self, _msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
true let custom_icon = html! {
} <Logo class={classes!("custom-icon")} />
};
let share_icon = html! {
<Icon icon={IconName::Share} />
};
fn view(&self) -> Html {
html! { html! {
<> <>
<Menu> <Menu>
<MenuItem <MenuItem
text={html!("Custom SVG icon")} text={html!("Custom SVG icon")}
icon_html=html! { icon_html={custom_icon}
<Logo class=classes!("custom-icon") />
}
/> />
<MenuDivider /> <MenuDivider />
<MenuItem <MenuItem
icon=IconName::NewTextBox icon={IconName::NewTextBox}
text={html!{"New text box"}} text={html!("New text box")}
href=Cow::Borrowed("#menu") href={"#menu"}
onclick=self.link onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::Menu)) .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
/> />
<MenuItem <MenuItem
icon=IconName::NewObject icon={IconName::NewObject}
text={html!{"New object"}} text={html!("New object")}
href=Cow::Borrowed("#menu") href={"#menu"}
onclick=self.link onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::Menu)) .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
/> />
<MenuItem <MenuItem
icon=IconName::NewLink icon={IconName::NewLink}
text={html!{"New link"}} text={html!("New link")}
href=Cow::Borrowed("#menu") href={"#menu"}
onclick=self.link onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::Menu)) .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
/> />
<MenuDivider /> <MenuDivider />
<MenuItem <MenuItem
icon=IconName::Cog icon={IconName::Cog}
text={html!{"Settings"}} text={html!("Settings")}
label=html! { label={share_icon}
<Icon icon=IconName::Share /> href={"#menu"}
} onclick={ctx.link()
href=Cow::Borrowed("#menu") .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
onclick=self.link
.callback(|_| Msg::GoToMenu(DocMenu::Menu))
/> />
</Menu> </Menu>
<Menu> <Menu>
<MenuDivider title={html!("Edit")} /> <MenuDivider title={html!("Edit")} />
<MenuItem <MenuItem
icon=IconName::Cut icon={IconName::Cut}
text={html!("Cut")} text={html!("Cut")}
label={html!("Ctrl+X")} label={html!("Ctrl+X")}
href=Cow::Borrowed("#menu") href={"#menu"}
onclick=self.link onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::Menu)) .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
/> />
<MenuItem <MenuItem
icon=IconName::Duplicate icon={IconName::Duplicate}
text={html!("Copy")} text={html!("Copy")}
label={html!("Ctrl+C")} label={html!("Ctrl+C")}
href=Cow::Borrowed("#menu") href={"#menu"}
onclick=self.link onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::Menu)) .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
/> />
<MenuItem <MenuItem
icon=IconName::Clipboard icon={IconName::Clipboard}
text={html!("Paste")} text={html!("Paste")}
label={html!("Ctrl+V")} label={html!("Ctrl+V")}
disabled=true disabled=true
/> />
<MenuDivider title={html!("Text")} /> <MenuDivider title={html!("Text")} />
<MenuItem <MenuItem
icon=IconName::AlignLeft icon={IconName::AlignLeft}
text={html!("Alignment")} text={html!("Alignment")}
href=Cow::Borrowed("#menu") href={"#menu"}
onclick=self.link onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::Menu)) .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
/> />
<MenuItem <MenuItem
icon=IconName::Style icon={IconName::Style}
text={html!("Style")} text={html!("Style")}
href=Cow::Borrowed("#menu") href={"#menu"}
onclick=self.link onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::Menu)) .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
/> />
<MenuItem <MenuItem
icon=IconName::Asterisk icon={IconName::Asterisk}
text={html!("Miscellaneous")} text={html!("Miscellaneous")}
href=Cow::Borrowed("#menu") href={"#menu"}
onclick=self.link onclick={ctx.link().callback(|_| Msg::GoToMenu(DocMenu::Menu))}
.callback(|_| Msg::GoToMenu(DocMenu::Menu))
/> />
</Menu> </Menu>
</> </>

View file

@ -11,19 +11,15 @@ impl Component for MenuDoc {
type Message = (); type Message = ();
type Properties = (); type Properties = ();
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
MenuDoc MenuDoc
} }
fn update(&mut self, _msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
"bp3-code-block" "bp3-code-block"
@ -31,9 +27,9 @@ impl Component for MenuDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Menu"}</H1> <H1 class={classes!("docs-title")}>{"Menu"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer source=source> <ExampleContainer source={source}>
<Example /> <Example />
</ExampleContainer> </ExampleContainer>
</div> </div>

View file

@ -1,10 +1,7 @@
use std::borrow::Cow;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, Callout, IconName, Intent, NumericInput}; use yewprint::{Button, Callout, IconName, Intent, NumericInput};
pub struct Example { pub struct Example {
props: ExampleProps,
link: ComponentLink<Self>,
value: i32, value: i32,
value_two: i32, value_two: i32,
} }
@ -29,16 +26,14 @@ impl Component for Example {
type Message = Msg; type Message = Msg;
type Properties = ExampleProps; type Properties = ExampleProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Example { Example {
props,
link,
value: 0, value: 0,
value_two: 0, value_two: 0,
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
Msg::Reset => { Msg::Reset => {
self.value = 4; self.value = 4;
@ -54,52 +49,43 @@ impl Component for Example {
true true
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! { html! {
<> <>
<NumericInput<i32> <NumericInput<i32>
disabled=self.props.disabled disabled={ctx.props().disabled}
fill=self.props.large fill={ctx.props().large}
value=self.value value={self.value}
bounds={-105..} bounds={-105..}
increment=10 increment=10
placeholder=String::from("Greater or equal to -105...") placeholder={String::from("Greater or equal to -105...")}
onchange=self.link.callback(|x| Msg::UpdateValue(x)) onchange={ctx.link().callback(|x| Msg::UpdateValue(x))}
disable_buttons=self.props.disable_buttons disable_buttons={ctx.props().disable_buttons}
buttons_on_the_left=self.props.buttons_on_the_left buttons_on_the_left={ctx.props().buttons_on_the_left}
left_icon=self.props.left_icon.then(|| IconName::Dollar) left_icon={ctx.props().left_icon.then(|| IconName::Dollar)}
/> />
<NumericInput<i32> <NumericInput<i32>
disabled=self.props.disabled disabled={ctx.props().disabled}
fill=self.props.fill fill={ctx.props().fill}
large=self.props.large large={ctx.props().large}
value=self.value_two value={self.value_two}
bounds={-10..=10} bounds={-10..=10}
increment=1 increment=1
placeholder=String::from("Integer between -10 and 10") placeholder={String::from("Integer between -10 and 10")}
onchange=self.link.callback(|x| Msg::UpdateValueTwo(x)) onchange={ctx.link().callback(|x| Msg::UpdateValueTwo(x))}
disable_buttons=self.props.disable_buttons disable_buttons={ctx.props().disable_buttons}
buttons_on_the_left=self.props.buttons_on_the_left buttons_on_the_left={ctx.props().buttons_on_the_left}
left_icon=self.props.left_icon.then(|| IconName::Dollar) left_icon={ctx.props().left_icon.then(|| IconName::Dollar)}
/> />
<Button <Button
icon=IconName::Refresh icon={IconName::Refresh}
onclick=self.link.callback(|_| Msg::Reset) onclick={ctx.link().callback(|_| Msg::Reset)}
> >
{"Reset at 4"} {"Reset at 4"}
</Button> </Button>
<Callout <Callout
title=Cow::Borrowed("Selected values") title={"Selected values"}
intent=Intent::Primary intent={Intent::Primary}
> >
<ul> <ul>
<li>{self.value}</li> <li>{self.value}</li>

View file

@ -14,9 +14,9 @@ impl Component for NumericInputDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
NumericInputDoc { NumericInputDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
fill: false, fill: false,
disabled: false, disabled: false,
@ -28,16 +28,12 @@ impl Component for NumericInputDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -46,19 +42,19 @@ impl Component for NumericInputDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"NumericInput"}</H1> <H1 class={classes!("docs-title")}>{"NumericInput"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<div> <div>
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<NumericInputProps <NumericInputProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
</div> </div>
@ -68,57 +64,57 @@ impl Component for NumericInputDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
NumericInputProps for ExampleProps => NumericInputProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
fill: !props.fill, fill: !props.fill,
..props ..props
}) })}
checked=self.props.fill checked={ctx.props().example_props.fill}
label=html!("Fill") label={html!("Fill")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
disabled: !props.disabled, disabled: !props.disabled,
..props ..props
}) })}
checked=self.props.disabled checked={ctx.props().example_props.disabled}
label=html!("Disabled") label={html!("Disabled")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
large: !props.large, large: !props.large,
..props ..props
}) })}
checked=self.props.large checked={ctx.props().example_props.large}
label=html!("Large") label={html!("Large")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
disable_buttons: !props.disable_buttons, disable_buttons: !props.disable_buttons,
..props ..props
}) })}
checked=self.props.disable_buttons checked={ctx.props().example_props.disable_buttons}
label=html!("Disable buttons") label={html!("Disable buttons")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
buttons_on_the_left: !props.buttons_on_the_left, buttons_on_the_left: !props.buttons_on_the_left,
..props ..props
}) })}
checked=self.props.buttons_on_the_left checked={ctx.props().example_props.buttons_on_the_left}
label=html!("Buttons on the left") label={html!("Buttons on the left")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
left_icon: !props.left_icon, left_icon: !props.left_icon,
..props ..props
}) })}
checked=self.props.left_icon checked={ctx.props().example_props.left_icon}
label=html!("Left icon") label={html!("Left icon")}
/> />
</div> </div>
} }

View file

@ -2,8 +2,6 @@ use yew::prelude::*;
use yewprint::{Button, Intent, PanelStack, PanelStackState, Text}; use yewprint::{Button, Intent, PanelStack, PanelStackState, Text};
pub struct Example { pub struct Example {
link: ComponentLink<Self>,
props: ExampleProps,
state: PanelStackState, state: PanelStackState,
} }
@ -23,37 +21,38 @@ impl Component for Example {
type Message = ExampleMessage; type Message = ExampleMessage;
type Properties = ExampleProps; type Properties = ExampleProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
let state = PanelStackState::new(html! { let state = PanelStackState::new(html! {
<div class=classes!("docs-panel-stack-contents-example")> <div class={classes!("docs-panel-stack-contents-example")}>
<div>{"Hello World!"}</div> <div>{"Hello World!"}</div>
<Button <Button
intent=Intent::Primary intent={Intent::Primary}
onclick=link.callback(|_| ExampleMessage::OpenPanel2) onclick={ctx.link().callback(|_| ExampleMessage::OpenPanel2)}
> >
{"Open panel 2"} {"Open panel 2"}
</Button> </Button>
</div> </div>
}) })
.with_title(html! { .with_title(html! {
<Text class=classes!("bp3-heading") ellipsize=true> <Text class={classes!("bp3-heading")} ellipsize=true>
{"Root Panel"} {"Root Panel"}
</Text> </Text>
}) })
.finish(); .finish();
Example { link, props, state } Example { state }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
// Open
ExampleMessage::OpenPanel2 => self ExampleMessage::OpenPanel2 => self
.state .state
.open_panel(html! { .open_panel(html! {
<div class=classes!("docs-panel-stack-contents-example")> <div class={classes!("docs-panel-stack-contents-example")}>
<Button <Button
intent=Intent::Success intent={Intent::Success}
onclick=self.link.callback(|_| ExampleMessage::OpenPanel2) onclick={ctx.link().callback(|_| ExampleMessage::OpenPanel2)}
> >
{"Open another panel 2"} {"Open another panel 2"}
</Button> </Button>
@ -61,7 +60,7 @@ impl Component for Example {
</div> </div>
}) })
.with_title(html! { .with_title(html! {
<Text class=classes!("bp3-heading") ellipsize=true> <Text class={classes!("bp3-heading")} ellipsize=true>
{"Panel 2"} {"Panel 2"}
</Text> </Text>
}) })
@ -71,70 +70,34 @@ impl Component for Example {
} }
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! { html! {
<div> <div>
<PanelStack <PanelStack
state=self.state.clone() state={self.state.clone()}
onclose=self.link.callback(|_| ExampleMessage::ClosePanel) onclose={ctx.link().callback(|_| ExampleMessage::ClosePanel)}
class=classes!("docs-panel-stack-example") class={classes!("docs-panel-stack-example")}
/> />
</div> </div>
} }
} }
} }
// Second panel: a simple counter /// Second panel: a simple counter
#[function_component(Panel2)]
pub fn panel2() -> Html {
let counter = use_state(|| 0);
let onclick = {
let counter = counter.clone();
Callback::from(move |_| counter.set(*counter + 1))
};
pub struct Panel2 { html! {
link: ComponentLink<Self>, <div>
counter: i64, <p>{"Counter: "}{ *counter}</p>
}
pub enum Panel2Message {
AddOne,
}
impl Component for Panel2 {
type Message = Panel2Message;
type Properties = ();
fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
Panel2 { counter: 0, link }
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Panel2Message::AddOne => {
self.counter += 1;
true
}
}
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
html! {
<div> <div>
<p>{"Counter: "}{self.counter}</p> <Button {onclick}>{ "Add 1" }</Button>
<div>
<Button onclick=self.link.callback(|_| Panel2Message::AddOne)>
{"Add 1"}
</Button>
</div>
</div> </div>
} </div>
} }
} }

View file

@ -14,9 +14,9 @@ impl Component for PanelStackDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
PanelStackDoc { PanelStackDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
animate: true, animate: true,
vertical: false, vertical: false,
@ -24,16 +24,12 @@ impl Component for PanelStackDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -42,14 +38,14 @@ impl Component for PanelStackDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"PanelStack"}</H1> <H1 class={classes!("docs-title")}>{"PanelStack"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<div> <div>
<ExampleContainer <ExampleContainer
source=source source={source}
props=None props={None}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
</div> </div>

View file

@ -1,10 +1,6 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Intent, ProgressBar}; use yewprint::{Intent, ProgressBar};
pub struct Example {
props: ExampleProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub intent: Option<Intent>, pub intent: Option<Intent>,
@ -12,32 +8,14 @@ pub struct ExampleProps {
pub stripes: bool, pub stripes: bool,
} }
impl Component for Example { #[function_component(Example)]
type Message = (); pub fn example(props: &ExampleProps) -> Html {
type Properties = ExampleProps; html! {
<ProgressBar
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { animate={props.animate}
Example { props } stripes={props.stripes}
} intent={props.intent}
fn update(&mut self, _msg: Self::Message) -> ShouldRender { value=0.3
true />
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<ProgressBar
animate=self.props.animate
stripes=self.props.stripes
intent=self.props.intent
value=0.3
/>
}
} }
} }

View file

@ -14,9 +14,9 @@ impl Component for ProgressBarDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
ProgressBarDoc { ProgressBarDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
intent: None, intent: None,
animate: false, animate: false,
@ -25,16 +25,12 @@ impl Component for ProgressBarDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -43,18 +39,18 @@ impl Component for ProgressBarDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"ProgressBar"}</H1> <H1 class={classes!("docs-title")}>{"ProgressBar"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<ProgressBarProps <ProgressBarProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
} }
@ -63,26 +59,26 @@ impl Component for ProgressBarDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
ProgressBarProps for ExampleProps => ProgressBarProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<div> <div>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
stripes: !props.stripes, stripes: !props.stripes,
..props ..props
}) })}
checked=self.props.stripes checked={ctx.props().example_props.stripes}
label=html!("Stripes") label={html!("Stripes")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
animate: !props.animate, animate: !props.animate,
..props ..props
}) })}
checked=self.props.animate checked={ctx.props().example_props.animate}
label=html!("Animate") label={html!("Animate")}
/> />
<p>{"Select intent:"}</p> <p>{"Select intent:"}</p>
<HtmlSelect<Option<Intent>> <HtmlSelect<Option<Intent>>
@ -93,10 +89,10 @@ crate::build_example_prop_component! {
(Some(Intent::Warning), "Warning".to_string()), (Some(Intent::Warning), "Warning".to_string()),
(Some(Intent::Danger), "Danger".to_string()), (Some(Intent::Danger), "Danger".to_string()),
]} ]}
onchange=self.update_props(|props, intent| ExampleProps { onchange={self.update_props(ctx.props(), |props, intent| ExampleProps {
intent, intent,
..props ..props
}) })}
/> />
</div> </div>
</div> </div>

View file

@ -2,8 +2,6 @@ use yew::prelude::*;
use yewprint::{Label, Radio, RadioGroup}; use yewprint::{Label, Radio, RadioGroup};
pub struct Example { pub struct Example {
props: ExampleProps,
link: ComponentLink<Self>,
selected_value: Lunch, selected_value: Lunch,
} }
@ -22,15 +20,13 @@ impl Component for Example {
type Message = Msg; type Message = Msg;
type Properties = ExampleProps; type Properties = ExampleProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Example { Example {
props,
selected_value: Lunch::Salad, selected_value: Lunch::Salad,
link,
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
Msg::ValueUpdate(value) => { Msg::ValueUpdate(value) => {
self.selected_value = value; self.selected_value = value;
@ -39,51 +35,42 @@ impl Component for Example {
} }
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! { html! {
<> <>
<div> <div>
<Radio <Radio
label=html!("Blue pill") label={html!("Blue pill")}
inline=self.props.inline inline={ctx.props().inline}
disabled=self.props.disabled disabled={ctx.props().disabled}
large=self.props.large large={ctx.props().large}
name="group".to_string() name={"group".to_string()}
/> />
<Radio <Radio
label=html!("Red pill") label={html!("Red pill")}
inline=self.props.inline inline={ctx.props().inline}
disabled=self.props.disabled disabled={ctx.props().disabled}
large=self.props.large large={ctx.props().large}
name="group".to_string() name={"group".to_string()}
/> />
</div> </div>
<div> <div>
<RadioGroup<Lunch> <RadioGroup<Lunch>
label=Some(html!( label={Some(html!(
<Label> <Label>
{"Determine Lunch"} {"Determine Lunch"}
</Label> </Label>
)) ))}
options=vec![ options={vec![
(Lunch::Soup, "Soup".to_string()), (Lunch::Soup, "Soup".to_string()),
(Lunch::Salad, "Salad".to_string()), (Lunch::Salad, "Salad".to_string()),
(Lunch::Sandwich, "Sandwich".to_string()), (Lunch::Sandwich, "Sandwich".to_string()),
] ]}
value=self.selected_value value={self.selected_value}
onchange=self.link.callback(|v| Msg::ValueUpdate(v)) onchange={ctx.link().callback(|v| Msg::ValueUpdate(v))}
inline=self.props.inline inline={ctx.props().inline}
disabled=self.props.disabled disabled={ctx.props().disabled}
large=self.props.large large={ctx.props().large}
/> />
</div> </div>
</> </>

View file

@ -14,9 +14,9 @@ impl Component for RadioDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
RadioDoc { RadioDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
disabled: false, disabled: false,
inline: false, inline: false,
@ -25,16 +25,12 @@ impl Component for RadioDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -43,18 +39,18 @@ impl Component for RadioDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Radio"}</H1> <H1 class={classes!("docs-title")}>{"Radio"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<RadioProps <RadioProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
} }
@ -63,33 +59,33 @@ impl Component for RadioDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
RadioProps for ExampleProps => RadioProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
disabled: !props.disabled, disabled: !props.disabled,
..props ..props
}) })}
checked=self.props.disabled checked={ctx.props().example_props.disabled}
label=html!("Disabled") label={html!("Disabled")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
inline: !props.inline, inline: !props.inline,
..props ..props
}) })}
checked=self.props.inline checked={ctx.props().example_props.inline}
label=html!("Inline") label={html!("Inline")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
large: !props.large, large: !props.large,
..props ..props
}) })}
checked=self.props.large checked={ctx.props().example_props.large}
label=html!("Large") label={html!("Large")}
/> />
</div> </div>
} }

View file

@ -3,11 +3,9 @@ use yew::prelude::*;
use yewprint::{Intent, Slider, Tag}; use yewprint::{Intent, Slider, Tag};
pub struct Example { pub struct Example {
props: ExampleProps,
float: f64, float: f64,
integer: i32, integer: i32,
log_level: Option<LogLevel>, log_level: Option<LogLevel>,
link: ComponentLink<Self>,
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
@ -27,17 +25,15 @@ impl Component for Example {
type Message = Msg; type Message = Msg;
type Properties = ExampleProps; type Properties = ExampleProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Example { Example {
props,
float: 1.2, float: 1.2,
integer: 30, integer: 30,
log_level: None, log_level: None,
link,
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
Msg::FloatUpdate(value) => { Msg::FloatUpdate(value) => {
self.float = value; self.float = value;
@ -53,16 +49,7 @@ impl Component for Example {
true true
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
let percentage_labels = (0..=100) let percentage_labels = (0..=100)
.step_by(1) .step_by(1)
.map(|x| (x, (x % 10 == 0).then(|| format!("{}%", x).into()))) .map(|x| (x, (x % 10 == 0).then(|| format!("{}%", x).into())))
@ -74,8 +61,8 @@ impl Component for Example {
style="display: flex; align-items: flex-start; width: 100%" style="display: flex; align-items: flex-start; width: 100%"
> >
<Slider<f64> <Slider<f64>
selected=self.float selected={self.float}
values=vec![ values={vec![
(0.0, Some("0".into())), (0.0, Some("0".into())),
(0.1, None), (0.1, None),
(0.2, None), (0.2, None),
@ -87,43 +74,43 @@ impl Component for Example {
(0.8, None), (0.8, None),
(0.9, None), (0.9, None),
(1.0, Some("1".into())), (1.0, Some("1".into())),
] ]}
intent=self.props.intent intent={ctx.props().intent}
onchange=self.link.callback(|x| Msg::FloatUpdate(x)) onchange={ctx.link().callback(|x| Msg::FloatUpdate(x))}
/> />
<Tag <Tag
style=Cow::Borrowed("width: 32px; margin-left: 16px") style={"width: 32px; margin-left: 16px"}
minimal=true minimal=true
intent=self.props.intent intent={ctx.props().intent}
> >
{format!("{:.1}", self.float)} {format!("{:.1}", self.float)}
</Tag> </Tag>
</div> </div>
<Slider<i32> <Slider<i32>
values=percentage_labels values={percentage_labels}
selected=self.integer selected={self.integer}
intent=self.props.intent intent={ctx.props().intent}
value_label=Cow::Owned(format!("{}%", self.integer)) value_label={Cow::Owned(format!("{}%", self.integer))}
onchange=self.link.callback(|x| Msg::IntegerUpdate(x)) onchange={ctx.link().callback(|x| Msg::IntegerUpdate(x))}
/> />
<Slider<LogLevel> <Slider<LogLevel>
values=vec![ values={vec![
(LogLevel::Off, Some("OFF".into())), (LogLevel::Off, Some("OFF".into())),
(LogLevel::Error, Some("ERROR".into())), (LogLevel::Error, Some("ERROR".into())),
(LogLevel::Warn, Some("WARN".into())), (LogLevel::Warn, Some("WARN".into())),
(LogLevel::Info, Some("INFO".into())), (LogLevel::Info, Some("INFO".into())),
(LogLevel::Debug, Some("DEBUG".into())), (LogLevel::Debug, Some("DEBUG".into())),
(LogLevel::Trace, Some("TRACE".into())), (LogLevel::Trace, Some("TRACE".into())),
] ]}
intent=self.props.intent intent={ctx.props().intent}
selected=self.log_level.clone() selected={self.log_level.clone()}
onchange=self.link.callback(|x| Msg::LogLevelUpdate(x)) onchange={ctx.link().callback(|x| Msg::LogLevelUpdate(x))}
/> />
<Slider<()> <Slider<()>
values=vec![((), Some("Neo".into()))] values={vec![((), Some("Neo".into()))]}
intent=self.props.intent intent={ctx.props().intent}
selected=() selected={()}
onchange=self.link.callback(|_| Msg::Noop) onchange={ctx.link().callback(|_| Msg::Noop)}
/> />
</> </>
} }

View file

@ -14,9 +14,9 @@ impl Component for SliderDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
SliderDoc { SliderDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
vertical: false, vertical: false,
intent: None, intent: None,
@ -24,16 +24,12 @@ impl Component for SliderDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -42,21 +38,21 @@ impl Component for SliderDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Slider"}</H1> <H1 class={classes!("docs-title")}>{"Slider"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<SliderProps <SliderProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
<H2>{"Edge Cases"}</H2> <H2>{"Edge Cases"}</H2>
<div class=classes!("bp3-running-text")> <div class={classes!("bp3-running-text")}>
<ul> <ul>
<li> <li>
<p> <p>
@ -108,17 +104,17 @@ impl Component for SliderDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
SliderProps for ExampleProps => SliderProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
vertical: !props.vertical, vertical: !props.vertical,
..props ..props
}) })}
checked=self.props.vertical checked={ctx.props().example_props.vertical}
label=html!("Vertical") label={html!("Vertical")}
disabled=true disabled=true
/> />
<p>{"Select intent:"}</p> <p>{"Select intent:"}</p>
@ -130,10 +126,10 @@ crate::build_example_prop_component! {
(Some(Intent::Warning), "Warning".to_string()), (Some(Intent::Warning), "Warning".to_string()),
(Some(Intent::Danger), "Danger".to_string()), (Some(Intent::Danger), "Danger".to_string()),
]} ]}
onchange=self.update_props(|props, intent| ExampleProps { onchange={self.update_props(ctx.props(), |props, intent| ExampleProps {
intent, intent,
..props ..props
}) })}
/> />
</div> </div>
} }

View file

@ -1,45 +1,20 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Intent, Spinner}; use yewprint::{Intent, Spinner};
pub struct Example {
props: ExampleProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub intent: Option<Intent>, pub intent: Option<Intent>,
pub size: u32, pub size: u32,
} }
impl Component for Example { #[function_component(Example)]
type Message = (); pub fn example(props: &ExampleProps) -> Html {
type Properties = ExampleProps; html! {
<div>
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { <Spinner
Example { props } size={props.size as f32}
} intent={props.intent}
/>
fn update(&mut self, _msg: Self::Message) -> ShouldRender { </div>
true
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<div>
<Spinner
size=self.props.size as f32
intent=self.props.intent
/>
</div>
}
} }
} }

View file

@ -14,9 +14,9 @@ impl Component for SpinnerDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
SpinnerDoc { SpinnerDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
intent: None, intent: None,
size: 50, size: 50,
@ -24,16 +24,12 @@ impl Component for SpinnerDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -42,19 +38,19 @@ impl Component for SpinnerDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Spinner"}</H1> <H1 class={classes!("docs-title")}>{"Spinner"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<div> <div>
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<SpinnerProps <SpinnerProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
</div> </div>
@ -64,7 +60,7 @@ impl Component for SpinnerDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
SpinnerProps for ExampleProps => SpinnerProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
@ -78,10 +74,10 @@ crate::build_example_prop_component! {
(Some(Intent::Warning), "Warning".to_string()), (Some(Intent::Warning), "Warning".to_string()),
(Some(Intent::Danger), "Danger".to_string()), (Some(Intent::Danger), "Danger".to_string()),
]} ]}
onchange=self.update_props(|props, intent| ExampleProps { onchange={self.update_props(ctx.props(), |props, intent| ExampleProps {
intent, intent,
..props ..props
}) })}
/> />
<p <p
style="margin-top: 5px;" style="margin-top: 5px;"
@ -89,7 +85,7 @@ crate::build_example_prop_component! {
{"Select Size:"} {"Select Size:"}
</p> </p>
<Slider<u32> <Slider<u32>
selected=self.props.size selected={ctx.props().example_props.size}
values={vec![ values={vec![
(10, Some("10".into())), (10, Some("10".into())),
(20, None), (20, None),
@ -102,10 +98,10 @@ crate::build_example_prop_component! {
(90, None), (90, None),
(100, Some("100".into())), (100, Some("100".into())),
]} ]}
onchange=self.update_props(|props, size| ExampleProps { onchange={self.update_props(ctx.props(), |props, size| ExampleProps {
size, size,
..props ..props
}) })}
/> />
</div> </div>
</div> </div>

View file

@ -1,10 +1,6 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Label, Switch}; use yewprint::{Label, Switch};
pub struct Example {
props: ExampleProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub disabled: bool, pub disabled: bool,
@ -13,63 +9,41 @@ pub struct ExampleProps {
pub align_right: bool, pub align_right: bool,
} }
impl Component for Example { #[function_component(Example)]
type Message = (); pub fn example(props: &ExampleProps) -> Html {
type Properties = ExampleProps; html! {
<div>
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { <Label>{"Privacy settings"}</Label>
Example { props } <Switch
} disabled={props.disabled}
inline={props.inline}
fn update(&mut self, _msg: Self::Message) -> ShouldRender { large={props.large}
true align_right={props.align_right}
} label={html!("Enabled")}
/>
fn change(&mut self, props: Self::Properties) -> ShouldRender { <Switch
if self.props != props { disabled={props.disabled}
self.props = props; inline={props.inline}
true large={props.large}
} else { align_right={props.align_right}
false label={html!(<em>{"Public"}</em>)}
} />
} <Switch
disabled={props.disabled}
fn view(&self) -> Html { inline={props.inline}
html! { large={props.large}
<div> align_right={props.align_right}
<Label>{"Privacy settings"}</Label> label={html!(<strong>{"Cooperative"}</strong>)}
<Switch />
disabled=self.props.disabled <Switch
inline=self.props.inline disabled={props.disabled}
large=self.props.large inline={props.inline}
label=html!{<strong>{"Enabled"}</strong>} large={props.large}
align_right=self.props.align_right align_right={props.align_right}
/> label={html!(<u>{"Containing Text"}</u>)}
<Switch inner_label_checked={"on".to_string()}
disabled=self.props.disabled inner_label={"off".to_string()}
inline=self.props.inline />
large=self.props.large </div>
label=html!{<em>{"Public"}</em>}
align_right=self.props.align_right
/>
<Switch
disabled=self.props.disabled
inline=self.props.inline
large=self.props.large
checked=true
label=html!{<u>{"Cooperative"}</u>}
align_right=self.props.align_right
/>
<Switch
disabled=self.props.disabled
inline=self.props.inline
large=self.props.large
label=html!{"Containing Text"}
inner_label_checked={"on".to_string()}
inner_label={"off".to_string()}
align_right=self.props.align_right
/>
</div>
}
} }
} }

View file

@ -14,9 +14,9 @@ impl Component for SwitchDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
SwitchDoc { SwitchDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
disabled: false, disabled: false,
inline: false, inline: false,
@ -26,16 +26,12 @@ impl Component for SwitchDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -44,19 +40,19 @@ impl Component for SwitchDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Switch"}</H1> <H1 class={classes!("docs-title")}>{"Switch"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<SwitchProps <SwitchProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
> >
</SwitchProps> </SwitchProps>
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
} }
@ -65,41 +61,41 @@ impl Component for SwitchDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
SwitchProps for ExampleProps => SwitchProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
disabled: !props.disabled, disabled: !props.disabled,
..props ..props
}) })}
checked=self.props.disabled checked={ctx.props().example_props.disabled}
label=html!("Disabled") label={html!("Disabled")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
inline: !props.inline, inline: !props.inline,
..props ..props
}) })}
checked=self.props.inline checked={ctx.props().example_props.inline}
label=html!("Inline") label={html!("Inline")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
large: !props.large, large: !props.large,
..props ..props
}) })}
checked=self.props.large checked={ctx.props().example_props.large}
label=html!("Large") label={html!("Large")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
align_right: !props.align_right, align_right: !props.align_right,
..props ..props
}) })}
checked=self.props.align_right checked={ctx.props().example_props.align_right}
label=html!("Align right") label={html!("Align right")}
/> />
</div> </div>
} }

View file

@ -2,8 +2,6 @@ use yew::prelude::*;
use yewprint::{Tab, Tabs}; use yewprint::{Tab, Tabs};
pub struct Example { pub struct Example {
link: ComponentLink<Self>,
props: ExampleProps,
selected: Civilization, selected: Civilization,
} }
@ -17,15 +15,13 @@ impl Component for Example {
type Message = Civilization; type Message = Civilization;
type Properties = ExampleProps; type Properties = ExampleProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Example { Example {
link,
props,
selected: Civilization::Minoan, selected: Civilization::Minoan,
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
if self.selected != msg { if self.selected != msg {
self.selected = msg; self.selected = msg;
true true
@ -34,25 +30,16 @@ impl Component for Example {
} }
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! { html! {
<div> <div>
<Tabs<Civilization> <Tabs<Civilization>
id="civilizations" id="civilizations"
animate=self.props.animate animate={ctx.props().animate}
vertical=self.props.vertical vertical={ctx.props().vertical}
selected_tab_id=self.selected selected_tab_id={self.selected}
onchange=self.link.callback(|x| x) onchange={ctx.link().callback(|x| x)}
tabs=vec![ tabs={vec![
Tab { Tab {
disabled: false, disabled: false,
id: Civilization::Sumer, id: Civilization::Sumer,
@ -133,7 +120,7 @@ impl Component for Example {
panel_class: Classes::default(), panel_class: Classes::default(),
title_class: Classes::default(), title_class: Classes::default(),
}, },
] ]}
/> />
</div> </div>
} }

View file

@ -14,9 +14,9 @@ impl Component for TabsDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
TabsDoc { TabsDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
animate: true, animate: true,
vertical: false, vertical: false,
@ -24,16 +24,12 @@ impl Component for TabsDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -42,19 +38,19 @@ impl Component for TabsDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Tabs"}</H1> <H1 class={classes!("docs-title")}>{"Tabs"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<div> <div>
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<TabsProps <TabsProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
</div> </div>
@ -64,25 +60,25 @@ impl Component for TabsDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
TabsProps for ExampleProps => TabsProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
animate: !props.animate, animate: !props.animate,
..props ..props
}) })}
checked=self.props.animate checked={ctx.props().example_props.animate}
label=html!("Animate indicator") label={html!("Animate indicator")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
vertical: !props.vertical, vertical: !props.vertical,
..props ..props
}) })}
checked=self.props.vertical checked={ctx.props().example_props.vertical}
label=html!("Use vertical tabs") label={html!("Use vertical tabs")}
/> />
</div> </div>
} }

View file

@ -2,8 +2,6 @@ use yew::prelude::*;
use yewprint::{IconName, Intent, Tag}; use yewprint::{IconName, Intent, Tag};
pub struct Example { pub struct Example {
props: ExampleProps,
link: ComponentLink<Self>,
tags: Vec<String>, tags: Vec<String>,
} }
@ -33,12 +31,12 @@ impl Component for Example {
type Message = ExampleMsg; type Message = ExampleMsg;
type Properties = ExampleProps; type Properties = ExampleProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
let tags = props.initial_tags.clone(); let tags = ctx.props().initial_tags.clone();
Example { props, link, tags } Example { tags }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
ExampleMsg::Remove(label) => { ExampleMsg::Remove(label) => {
self.tags = self self.tags = self
@ -53,43 +51,38 @@ impl Component for Example {
true true
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn changed(&mut self, ctx: &Context<Self>) -> bool {
if self.props != props { if ctx.props().reset_tags != ctx.props().reset_tags {
if self.props.reset_tags != props.reset_tags { self.tags = ctx.props().initial_tags.clone();
self.tags = props.initial_tags.clone();
}
self.props = props;
true
} else {
false
} }
true
} }
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
self.tags self.tags
.iter() .iter()
.map(|label| { .map(|label| {
let remove = { let remove = {
let label = label.clone(); let label = label.clone();
self.props.removable.then(|| { ctx.props().removable.then(|| {
self.link ctx.link()
.callback(move |_| ExampleMsg::Remove(label.clone())) .callback(move |_| ExampleMsg::Remove(label.clone()))
}) })
}; };
html! { html! {
<Tag <Tag
active=self.props.active active={ctx.props().active}
fill=self.props.fill fill={ctx.props().fill}
icon=self.props.icon.then(|| IconName::Print) icon={ctx.props().icon.then(|| IconName::Print)}
intent=self.props.intent intent={ctx.props().intent}
interactive=self.props.interactive interactive={ctx.props().interactive}
large=self.props.large large={ctx.props().large}
minimal=self.props.minimal minimal={ctx.props().minimal}
multiline=self.props.multiline multiline={ctx.props().multiline}
right_icon=self.props.right_icon.then(|| IconName::Star) right_icon={ctx.props().right_icon.then(|| IconName::Star)}
round=self.props.round round={ctx.props().round}
onremove=remove onremove={remove}
onclick=self.link.callback(|_| ExampleMsg::Click) onclick={ctx.link().callback(|_| ExampleMsg::Click)}
> >
{label.clone()} {label.clone()}
</Tag> </Tag>

View file

@ -28,9 +28,9 @@ impl Component for TagDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
TagDoc { TagDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
initial_tags: initial_tags(), initial_tags: initial_tags(),
active: false, active: false,
@ -49,16 +49,12 @@ impl Component for TagDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -67,18 +63,18 @@ impl Component for TagDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Tag"}</H1> <H1 class={classes!("docs-title")}>{"Tag"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html!{ props={Some(html!{
<TagProps <TagProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props/> <Example ..example_props/>
</ExampleContainer> </ExampleContainer>
</div> </div>
} }
@ -87,90 +83,92 @@ impl Component for TagDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
TagProps for ExampleProps => TagProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
let props = ctx.props();
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<div> <div>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(props, |props, _| ExampleProps {
active: !props.active, active: !props.active,
..props ..props
}) })}
checked=self.props.active checked={props.example_props.active}
label=html!("Active") label={html!("Active")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(props, |props, _| ExampleProps {
fill: !props.fill, fill: !props.fill,
..props ..props
}) })}
checked=self.props.fill checked={props.example_props.fill}
label=html!("Fill") label={html!("Fill")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(props, |props, _| ExampleProps {
interactive: !props.interactive, interactive: !props.interactive,
..props ..props
}) })}
checked=self.props.interactive checked={props.example_props.interactive}
label=html!("Interactive") label={html!("Interactive")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(props, |props, _| ExampleProps {
large: !props.large, large: !props.large,
..props ..props
}) })}
checked=self.props.large checked={props.example_props.large}
label=html!("Large") label={html!("Large")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(props, |props, _| ExampleProps {
minimal: !props.minimal, minimal: !props.minimal,
..props ..props
}) })}
checked=self.props.minimal checked={props.example_props.minimal}
label=html!("Minimal") label={html!("Minimal")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(props, |props, _| ExampleProps {
multiline: !props.multiline, multiline: !props.multiline,
..props ..props
}) })}
checked=self.props.multiline checked={props.example_props.multiline}
label=html!("Multiline") label={html!("Multiline")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(props, |props, _| ExampleProps {
round: !props.round, round: !props.round,
..props ..props
}) })}
checked=self.props.round checked={props.example_props.round}
label=html!("Round") label={html!("Round")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(props, |props, _| ExampleProps {
removable: !props.removable, removable: !props.removable,
..props ..props
}) })}
checked=self.props.removable checked={props.example_props.removable}
label=html!("Removable") label={html!("Removable")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(props, |props, _| ExampleProps {
icon: !props.icon, icon: !props.icon,
..props ..props
}) })}
checked=self.props.icon checked={props.example_props.icon}
label=html!("Icon") label={html!("Icon")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(props, |props, _| ExampleProps {
right_icon: !props.right_icon, right_icon: !props.right_icon,
..props ..props
}) })}
checked=self.props.right_icon checked={props.example_props.right_icon}
label=html!("Right icon") label={html!("Right icon")}
/> />
<p>{"Select intent:"}</p> <p>{"Select intent:"}</p>
<ButtonGroup <ButtonGroup
@ -184,17 +182,17 @@ crate::build_example_prop_component! {
(Some(Intent::Warning), "Warning".to_string()), (Some(Intent::Warning), "Warning".to_string()),
(Some(Intent::Danger), "Danger".to_string()), (Some(Intent::Danger), "Danger".to_string()),
]} ]}
onchange=self.update_props(|props, intent| ExampleProps { onchange={self.update_props(props, |props, intent| ExampleProps {
intent, intent,
..props ..props
}) })}
/> />
<Button <Button
icon=IconName::Refresh icon={IconName::Refresh}
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(props, |props, _| ExampleProps {
reset_tags: props.reset_tags + 1, reset_tags: props.reset_tags + 1,
..props ..props.clone()
}) })}
> >
{"Reset tags"} {"Reset tags"}
</Button> </Button>

View file

@ -1,44 +1,19 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::Text; use yewprint::Text;
pub struct Example {
props: ExampleProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub ellipsize: bool, pub ellipsize: bool,
pub text: String, pub text: String,
} }
impl Component for Example { #[function_component(Example)]
type Message = (); pub fn example(props: &ExampleProps) -> Html {
type Properties = ExampleProps; html! {
<div style="width: 150px; height: 20px">
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { <Text ellipsize={props.ellipsize}>
Example { props } {&props.text}
} </Text>
</div>
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! {
<div style="width: 150px; height: 20px">
<Text ellipsize=self.props.ellipsize>
{&self.props.text}
</Text>
</div>
}
} }
} }

View file

@ -2,6 +2,7 @@ mod example;
use crate::ExampleContainer; use crate::ExampleContainer;
use example::*; use example::*;
use web_sys::HtmlInputElement;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Switch, H1, H5}; use yewprint::{Switch, H1, H5};
@ -14,9 +15,9 @@ impl Component for TextDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
TextDoc { TextDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
ellipsize: false, ellipsize: false,
text: String::from("Hello, world!"), text: String::from("Hello, world!"),
@ -24,16 +25,12 @@ impl Component for TextDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -42,19 +39,19 @@ impl Component for TextDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Text"}</H1> <H1 class={classes!("docs-title")}>{"Text"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<div> <div>
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<TextProps <TextProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
</div> </div>
@ -64,37 +61,36 @@ impl Component for TextDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
TextProps for ExampleProps => TextProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
ellipsize: !props.ellipsize, ellipsize: !props.ellipsize,
..props ..props
}) })}
checked=self.props.ellipsize checked={ctx.props().example_props.ellipsize}
label=html!("Ellipsize") label={html!("Ellipsize")}
/> />
<input <input
class="bp3-input" class="bp3-input"
onchange=self.update_props(|props, e| onchange={self.update_props(ctx.props(), |props, e: Event| {
match e { if let Some(input) = e.target_dyn_into::<HtmlInputElement>() {
ChangeData::Value(text) => {
ExampleProps { ExampleProps {
text, text: input.value(),
..props ..props
} }
}, } else {
_ => {
ExampleProps { ExampleProps {
text: "Hello, world!".to_string(), text: "Hello, world!".to_string(),
..props ..props
} }
} }
}) }
)}
type="text" type="text"
value={self.props.text.clone()} value={ctx.props().example_props.text.clone()}
/> />
</div> </div>
} }

View file

@ -1,10 +1,6 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Intent, TextArea}; use yewprint::{Intent, TextArea};
pub struct Example {
props: ExampleProps,
}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub intent: Option<Intent>, pub intent: Option<Intent>,
@ -13,36 +9,15 @@ pub struct ExampleProps {
pub fill: bool, pub fill: bool,
} }
impl Component for Example { #[function_component(Example)]
type Message = (); pub fn example(props: &ExampleProps) -> Html {
type Properties = ExampleProps; html! {
<div style="width: 200px; height: 50px">
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { <TextArea intent={props.intent}
Example { props } large={props.large}
} fill={props.fill}
small={props.small}
fn update(&mut self, _msg: Self::Message) -> ShouldRender { />
true </div>
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<div style="width: 200px; height: 50px">
<TextArea intent=self.props.intent
large=self.props.large
fill=self.props.fill
small=self.props.small
/>
</div>
}
} }
} }

View file

@ -14,9 +14,9 @@ impl Component for TextAreaDoc {
type Message = ExampleProps; type Message = ExampleProps;
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
TextAreaDoc { TextAreaDoc {
callback: link.callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
intent: None, intent: None,
large: false, large: false,
@ -26,16 +26,12 @@ impl Component for TextAreaDoc {
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.state = msg; self.state = msg;
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone(); let example_props = self.state.clone();
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
@ -44,19 +40,19 @@ impl Component for TextAreaDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Text"}</H1> <H1 class={classes!("docs-title")}>{"Text"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<div> <div>
<ExampleContainer <ExampleContainer
source=source source={source}
props=Some(html! { props={Some(html! {
<TextAreaProps <TextAreaProps
callback={self.callback.clone()} callback={self.callback.clone()}
props=example_props.clone() example_props={example_props.clone()}
/> />
}) })}
> >
<Example with example_props /> <Example ..example_props />
</ExampleContainer> </ExampleContainer>
</div> </div>
</div> </div>
@ -66,33 +62,33 @@ impl Component for TextAreaDoc {
crate::build_example_prop_component! { crate::build_example_prop_component! {
TextAreaProps for ExampleProps => TextAreaProps for ExampleProps =>
fn view(&self) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
<div> <div>
<H5>{"Props"}</H5> <H5>{"Props"}</H5>
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
fill: !props.fill, fill: !props.fill,
..props ..props
}) })}
checked=self.props.fill checked={ctx.props().example_props.fill}
label=html!("Fill") label={html!("Fill")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
large: !props.large, large: !props.large,
..props ..props
}) })}
checked=self.props.large checked={ctx.props().example_props.large}
label=html!("Large") label={html!("Large")}
/> />
<Switch <Switch
onclick=self.update_props(|props, _| ExampleProps { onclick={self.update_props(ctx.props(), |props, _| ExampleProps {
small: !props.small, small: !props.small,
..props ..props
}) })}
checked=self.props.small checked={ctx.props().example_props.small}
label=html!("Small") label={html!("Small")}
/> />
<HtmlSelect<Option<Intent>> <HtmlSelect<Option<Intent>>
options={vec![ options={vec![
@ -102,10 +98,10 @@ crate::build_example_prop_component! {
(Some(Intent::Warning), "Warning".to_string()), (Some(Intent::Warning), "Warning".to_string()),
(Some(Intent::Danger), "Danger".to_string()), (Some(Intent::Danger), "Danger".to_string()),
]} ]}
onchange=self.update_props(|props, intent| ExampleProps { onchange={self.update_props(ctx.props(), |props, intent| ExampleProps {
intent, intent,
..props ..props
}) })}
/> />
</div> </div>
} }

View file

@ -17,7 +17,7 @@ impl Component for Example {
type Message = Msg; type Message = Msg;
type Properties = (); type Properties = ();
fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
let mut tree = TreeBuilder::new().build(); let mut tree = TreeBuilder::new().build();
let root_id = tree let root_id = tree
.insert( .insert(
@ -71,7 +71,7 @@ impl Component for Example {
icon: Some(IconName::Tag), icon: Some(IconName::Tag),
icon_intent: Some(Intent::Primary), icon_intent: Some(Intent::Primary),
label: "Outer file".into(), label: "Outer file".into(),
secondary_label: Some(html!(<Icon icon=IconName::EyeOpen />)), secondary_label: Some(html!(<Icon icon={IconName::EyeOpen} />)),
data: 3, data: 3,
..Default::default() ..Default::default()
}), }),
@ -81,12 +81,12 @@ impl Component for Example {
Self { Self {
tree: tree.into(), tree: tree.into(),
callback_expand_node: link.callback(|(node_id, _)| Msg::ExpandNode(node_id)), callback_expand_node: ctx.link().callback(|(node_id, _)| Msg::ExpandNode(node_id)),
callback_select_node: link.callback(|(node_id, _)| Msg::SelectNode(node_id)), callback_select_node: ctx.link().callback(|(node_id, _)| Msg::SelectNode(node_id)),
} }
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
Msg::ExpandNode(node_id) => { Msg::ExpandNode(node_id) => {
let mut tree = self.tree.borrow_mut(); let mut tree = self.tree.borrow_mut();
@ -109,17 +109,13 @@ impl Component for Example {
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
html! { html! {
<Tree<i32> <Tree<i32>
tree=self.tree.clone() tree={self.tree.clone()}
on_collapse=Some(self.callback_expand_node.clone()) on_collapse={Some(self.callback_expand_node.clone())}
on_expand=Some(self.callback_expand_node.clone()) on_expand={Some(self.callback_expand_node.clone())}
onclick=Some(self.callback_select_node.clone()) onclick={Some(self.callback_select_node.clone())}
/> />
} }
} }

View file

@ -11,19 +11,15 @@ impl Component for TreeDoc {
type Message = (); type Message = ();
type Properties = (); type Properties = ();
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
TreeDoc TreeDoc
} }
fn update(&mut self, _msg: Self::Message) -> ShouldRender { fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
true true
} }
fn change(&mut self, _props: Self::Properties) -> ShouldRender { fn view(&self, _ctx: &Context<Self>) -> Html {
true
}
fn view(&self) -> Html {
let source = crate::include_raw_html!( let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"), concat!(env!("OUT_DIR"), "/", file!(), ".html"),
"bp3-code-block" "bp3-code-block"
@ -31,9 +27,9 @@ impl Component for TreeDoc {
html! { html! {
<div> <div>
<H1 class=classes!("docs-title")>{"Tree"}</H1> <H1 class={classes!("docs-title")}>{"Tree"}</H1>
<SourceCodeUrl /> <SourceCodeUrl />
<ExampleContainer source=source> <ExampleContainer source={source}>
<Example /> <Example />
</ExampleContainer> </ExampleContainer>
</div> </div>