Update dependencies (#154)

Co-authored-by: Cecile Tonglet <cecile.tonglet@cecton.com>
This commit is contained in:
Yohan Boogaert 2022-12-14 10:26:26 +01:00 committed by GitHub
parent 70aa7a0736
commit 3e58abab37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 1781 additions and 1038 deletions

1301
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -18,14 +18,15 @@ default = ["tree"]
tree = ["id_tree"] tree = ["id_tree"]
[dependencies] [dependencies]
gloo = "0.6" gloo = "0.8"
id_tree = { version = "1.7", optional = true } id_tree = { version = "1.8", optional = true }
implicit-clone = "0.3.5"
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["DomRect", "Element", "Event", "HtmlSelectElement"] } web-sys = { version = "0.3", features = ["DomRect", "Element", "Event", "HtmlSelectElement"] }
yew = "0.19" yew = "0.20"
[build-dependencies] [build-dependencies]
heck = "0.3" heck = "0.4"
regex = { version = "1", default-features = false, features = ["std", "unicode-perl"] } regex = { version = "1", default-features = false, features = ["std", "unicode-perl"] }
[workspace] [workspace]

View file

@ -7,7 +7,7 @@
//! After that you can extract the file following this path: //! After that you can extract the file following this path:
//! package/lib/esnext/generated/iconSvgPaths.js //! package/lib/esnext/generated/iconSvgPaths.js
use heck::CamelCase; use heck::ToUpperCamelCase;
use regex::Regex; use regex::Regex;
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
@ -29,7 +29,7 @@ fn main() {
src.push_str(&map[1]); src.push_str(&map[1]);
src.push_str("(icon: IconName) -> &'static [&'static str] { match icon {\n"); src.push_str("(icon: IconName) -> &'static [&'static str] { match icon {\n");
for item in re_item.captures_iter(&map[2]) { for item in re_item.captures_iter(&map[2]) {
let key = item[1].to_camel_case(); let key = item[1].to_upper_camel_case();
src.push_str("IconName::"); src.push_str("IconName::");
src.push_str(&key); src.push_str(&key);
src.push_str(" => &"); src.push_str(" => &");

View file

@ -14,26 +14,36 @@ pub struct ButtonGroupProps {
#[prop_or_default] #[prop_or_default]
pub style: Option<AttrValue>, pub style: Option<AttrValue>,
#[prop_or_default] #[prop_or_default]
pub children: html::Children, pub children: Children,
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
} }
#[function_component(ButtonGroup)] #[function_component(ButtonGroup)]
pub fn button_group(props: &ButtonGroupProps) -> Html { pub fn button_group(props: &ButtonGroupProps) -> Html {
let ButtonGroupProps {
minimal,
vertical,
fill,
large,
style,
children,
class,
} = props;
html! { html! {
<div <div
class={classes!( class={classes!(
"bp3-button-group", "bp3-button-group",
props.minimal.then_some("bp3-minimal"), minimal.then_some("bp3-minimal"),
props.fill.then_some("bp3-fill"), fill.then_some("bp3-fill"),
props.large.then_some("bp3-large"), large.then_some("bp3-large"),
props.vertical.then_some("bp3-vertical"), vertical.then_some("bp3-vertical"),
props.class.clone(), class.clone(),
)} )}
style={props.style.clone()} {style}
> >
{props.children.clone()} {children.clone()}
</div> </div>
} }
} }

View file

@ -33,59 +33,65 @@ pub struct ButtonProps {
#[prop_or_default] #[prop_or_default]
pub style: Option<AttrValue>, pub style: Option<AttrValue>,
#[prop_or_default] #[prop_or_default]
pub children: html::Children, pub children: Children,
} }
#[function_component(Button)] #[function_component(Button)]
pub fn button(props: &ButtonProps) -> Html { pub fn button(props: &ButtonProps) -> Html {
let ButtonProps {
fill,
minimal,
small,
outlined,
loading,
large,
active,
disabled,
icon,
intent,
title,
onclick,
class,
style,
children,
} = props;
html! { html! {
<button <button
class={classes!( class={classes!(
"bp3-button", "bp3-button",
props.fill.then_some("bp3-fill"), fill.then_some("bp3-fill"),
props.minimal.then_some("bp3-minimal"), minimal.then_some("bp3-minimal"),
props.small.then_some("bp3-small"), small.then_some("bp3-small"),
props.outlined.then_some("bp3-outlined"), outlined.then_some("bp3-outlined"),
props.loading.then_some("bp3-loading"), loading.then_some("bp3-loading"),
props.large.then_some("bp3-large"), large.then_some("bp3-large"),
(props.active && !props.disabled).then_some("bp3-active"), (*active && !disabled).then_some("bp3-active"),
props.disabled.then_some("bp3-disabled"), disabled.then_some("bp3-disabled"),
props.intent, intent,
props.class.clone(), class.clone(),
)} )}
style={props.style.clone()} {style}
onclick={(!props.disabled).then_some(props.onclick.clone())} {title}
onclick={(!disabled).then_some(onclick.clone())}
> >
{ {
props loading
.loading
.then(|| html! { .then(|| html! {
<Spinner <Spinner
class={classes!("bp3-button-spinner")} class={classes!("bp3-button-spinner")}
size={ICON_SIZE_LARGE as f32} size={ICON_SIZE_LARGE as f32}
/> />
}) })
.unwrap_or_default()
} }
{icon.map(|icon| html!(<Icon {icon} />))}
{ {
if let Some(icon) = props.icon { (!children.is_empty())
html! { .then(|| html! {
<Icon icon={icon} />
}
} else {
html!()
}
}
{
if props.children.is_empty() {
html! ()
} else {
html! {
<span class="bp3-button-text"> <span class="bp3-button-text">
{for props.children.iter()} {for children.iter()}
</span> </span>
} })
}
} }
</button> </button>
} }

View file

@ -15,16 +15,25 @@ pub struct CalloutProps {
pub intent: Option<Intent>, pub intent: Option<Intent>,
#[prop_or_default] #[prop_or_default]
pub title: Option<AttrValue>, pub title: Option<AttrValue>,
pub children: html::Children, pub children: Children,
} }
#[function_component(Callout)] #[function_component(Callout)]
pub fn callout(props: &CalloutProps) -> Html { pub fn callout(
let icon = if props.without_icon { CalloutProps {
class,
without_icon,
icon,
intent,
title,
children,
}: &CalloutProps,
) -> Html {
let icon = if *without_icon {
None None
} else { } else {
props.icon.or_else(|| { icon.or_else(|| {
props.intent.map(|intent| match intent { intent.map(|intent| match intent {
Intent::Primary => IconName::InfoSign, Intent::Primary => IconName::InfoSign,
Intent::Success => IconName::Tick, Intent::Success => IconName::Tick,
Intent::Warning => IconName::WarningSign, Intent::Warning => IconName::WarningSign,
@ -32,25 +41,27 @@ pub fn callout(props: &CalloutProps) -> Html {
}) })
}) })
}; };
let classes = classes!(
props.class.clone(),
"bp3-callout",
icon.map(|_| "bp3-callout-icon"),
props.intent,
);
html! { html! {
<div class={classes}> <div
class={classes!(
"bp3-callout",
icon.map(|_| "bp3-callout-icon"),
intent,
class.clone(),
)}
>
{ {
icon.iter() icon.iter()
.map(|name| html!{<Icon icon={*name} icon_size={ICON_SIZE_LARGE}/>}) .map(|icon| html!{<Icon {icon} icon_size={ICON_SIZE_LARGE}/>})
.collect::<Html>() .collect::<Html>()
} }
{ {
props.title.iter() title.iter()
.map(|title| html!{<h4 class={"bp3-heading"}>{title}</h4>}) .map(|title| html!{<h4 class={"bp3-heading"}>{title}</h4>})
.collect::<Html>() .collect::<Html>()
} }
{ props.children.clone() } {children.clone()}
</div> </div>
} }
} }

View file

@ -11,20 +11,34 @@ pub struct CardProps {
pub onclick: Callback<MouseEvent>, pub onclick: Callback<MouseEvent>,
#[prop_or(false)] #[prop_or(false)]
pub interactive: bool, pub interactive: bool,
pub children: html::Children, #[prop_or_default]
pub style: AttrValue,
pub children: Children,
} }
#[function_component(Card)] #[function_component(Card)]
pub fn card(props: &CardProps) -> Html { pub fn card(
CardProps {
class,
elevation,
onclick,
interactive,
style,
children,
}: &CardProps,
) -> Html {
html! { html! {
<div class={classes!( <div
"bp3-card", class={classes!(
props.class.clone(), "bp3-card",
props.elevation, elevation,
props.interactive.then_some("bp3-interactive"), interactive.then_some("bp3-interactive"),
)} class.clone(),
onclick={props.onclick.clone()}> )}
{props.children.clone()} {onclick}
{style}
>
{children.clone()}
</div> </div>
} }
} }

View file

@ -13,33 +13,41 @@ pub struct CheckboxProps {
#[prop_or_default] #[prop_or_default]
pub onchange: Callback<Event>, pub onchange: Callback<Event>,
#[prop_or_default] #[prop_or_default]
pub label: yew::virtual_dom::VNode, pub label: Html,
#[prop_or_default] #[prop_or_default]
pub indeterminate_state: bool, pub class: Classes,
#[prop_or_default]
pub children: Children,
} }
#[function_component(Checkbox)] #[function_component(Checkbox)]
pub fn checkbox(props: &CheckboxProps) -> Html { pub fn checkbox(
CheckboxProps {
disabled,
inline,
large,
checked,
onchange,
label,
class,
children,
}: &CheckboxProps,
) -> Html {
html! { html! {
<label <label
class={classes!( class={classes!(
"bp3-control", "bp3-checkbox", "bp3-control",
props.disabled.then_some("bp3-disabled"), "bp3-checkbox",
props.inline.then_some("bp3-inline"), disabled.then_some("bp3-disabled"),
props.large.then_some("bp3-large") inline.then_some("bp3-inline"),
large.then_some("bp3-large"),
class.clone(),
)} )}
> >
<input <input type="checkbox" checked={*checked} {onchange} disabled={*disabled} />
type="checkbox" <span class="bp3-control-indicator" />
checked={props.checked} {label.clone()}
onchange={props.onchange.clone()} {children.clone()}
disabled={props.disabled}
/>
<span
class="bp3-control-indicator"
>
</span>
{props.label.clone()}
</label> </label>
} }
} }

View file

@ -19,7 +19,7 @@ pub struct CollapseProps {
#[prop_or_default] #[prop_or_default]
pub is_open: bool, pub is_open: bool,
#[prop_or_default] #[prop_or_default]
pub children: html::Children, pub children: Children,
#[prop_or_default] #[prop_or_default]
pub keep_children_mounted: bool, pub keep_children_mounted: bool,
#[prop_or(Duration::from_millis(200))] #[prop_or(Duration::from_millis(200))]
@ -70,7 +70,7 @@ impl Component for Collapse {
} }
} }
fn changed(&mut self, ctx: &Context<Self>) -> bool { fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
if ctx.props().is_open { if ctx.props().is_open {
match self.animation_state { match self.animation_state {
AnimationState::Open | AnimationState::Opening => {} AnimationState::Open | AnimationState::Opening => {}

View file

@ -9,23 +9,32 @@ pub struct ControlGroupProps {
#[prop_or_default] #[prop_or_default]
pub large: bool, pub large: bool,
#[prop_or_default] #[prop_or_default]
pub children: html::Children, pub children: Children,
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
} }
#[function_component(ControlGroup)] #[function_component(ControlGroup)]
pub fn control_group(props: &ControlGroupProps) -> Html { pub fn control_group(
ControlGroupProps {
fill,
vertical,
large,
class,
children,
}: &ControlGroupProps,
) -> Html {
html! { html! {
<div <div
class={classes!( class={classes!(
"bp3-control-group", "bp3-control-group",
props.fill.then_some("bp3-fill"), fill.then_some("bp3-fill"),
props.vertical.then_some("bp3-vertical"), vertical.then_some("bp3-vertical"),
props.class.clone(), large.then_some("bp3-large"),
class.clone(),
)} )}
> >
{props.children.clone()} {children.clone()}
</div> </div>
} }
} }

View file

@ -5,19 +5,17 @@ pub struct DividerProps {
#[prop_or_default] #[prop_or_default]
pub vertical: bool, pub vertical: bool,
#[prop_or_default] #[prop_or_default]
pub children: html::Children,
#[prop_or_default]
pub class: Classes, pub class: Classes,
} }
#[function_component(Divider)] #[function_component(Divider)]
pub fn view(props: &DividerProps) -> Html { pub fn view(DividerProps { vertical, class }: &DividerProps) -> Html {
html! { html! {
<span <span
class={classes!( class={classes!(
"bp3-divider", "bp3-divider",
props.vertical.then_some("bp3-vertical"), vertical.then_some("bp3-vertical"),
props.class.clone(), class.clone(),
)} )}
/> />
} }

View file

@ -5,16 +5,16 @@ pub struct ChildrenOnlyProps {
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
#[prop_or_default] #[prop_or_default]
pub children: html::Children, pub children: Children,
} }
macro_rules! build_component { macro_rules! build_component {
($name:ident, $tag:tt, $class:literal) => { ($name:ident, $tag:tt, $class:literal) => {
#[function_component($name)] #[function_component($name)]
pub fn $tag(props: &ChildrenOnlyProps) -> Html { pub fn $tag(ChildrenOnlyProps { class, children }: &ChildrenOnlyProps) -> Html {
html! { html! {
<$tag class={classes!($class, props.class.clone())}> <$tag class={classes!($class, class.clone())}>
{ props.children.clone() } {children.clone()}
</$tag> </$tag>
} }
} }

View file

@ -1,3 +1,4 @@
use implicit_clone::{unsync::IArray, ImplicitClone};
use std::marker::PhantomData; use std::marker::PhantomData;
use crate::{Icon, IconName}; use crate::{Icon, IconName};
@ -11,7 +12,7 @@ pub struct HtmlSelect<T: Clone + PartialEq + 'static> {
} }
#[derive(Debug, Clone, PartialEq, Properties)] #[derive(Debug, Clone, PartialEq, Properties)]
pub struct HtmlSelectProps<T: Clone + PartialEq + 'static> { pub struct HtmlSelectProps<T: ImplicitClone + PartialEq + 'static> {
#[prop_or_default] #[prop_or_default]
pub fill: bool, pub fill: bool,
#[prop_or_default] #[prop_or_default]
@ -21,19 +22,17 @@ pub struct HtmlSelectProps<T: Clone + PartialEq + 'static> {
#[prop_or_default] #[prop_or_default]
pub disabled: bool, pub disabled: bool,
#[prop_or_default] #[prop_or_default]
pub icon: Option<IconName>, pub title: Option<AttrValue>,
#[prop_or_default]
pub title: Option<String>,
#[prop_or_default] #[prop_or_default]
pub onchange: Callback<T>, pub onchange: Callback<T>,
#[prop_or_default] #[prop_or_default]
pub value: Option<T>, pub value: Option<T>,
pub options: Vec<(T, String)>, pub options: IArray<(T, AttrValue)>,
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
} }
impl<T: Clone + PartialEq + 'static> Component for HtmlSelect<T> { impl<T: ImplicitClone + PartialEq + 'static> Component for HtmlSelect<T> {
type Message = Event; type Message = Event;
type Properties = HtmlSelectProps<T>; type Properties = HtmlSelectProps<T>;
@ -58,10 +57,10 @@ impl<T: Clone + PartialEq + 'static> Component for HtmlSelect<T> {
false false
} }
fn changed(&mut self, ctx: &Context<Self>) -> bool { fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
if let Some(value) = ctx.props().value.as_ref() { if let Some(value) = ctx.props().value.as_ref() {
if let Some(select) = self.select_element.cast::<HtmlSelectElement>() { if let Some(select) = self.select_element.cast::<HtmlSelectElement>() {
if let Some(i) = ctx.props().options.iter().position(|(x, _)| x == value) { if let Some(i) = ctx.props().options.iter().position(|(x, _)| &x == value) {
if let Ok(i) = i.try_into() { if let Ok(i) = i.try_into() {
if select.selected_index() != i { if select.selected_index() != i {
select.set_selected_index(i); select.set_selected_index(i);
@ -74,20 +73,25 @@ impl<T: Clone + PartialEq + 'static> Component for HtmlSelect<T> {
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
let option_children = ctx let Self::Properties {
.props() fill,
.options minimal,
large,
disabled,
title,
onchange: _,
value,
options,
class,
} = &ctx.props();
let option_children = options
.iter() .iter()
.map(|(value, label)| { .map(|(this_value, label)| {
let selected = ctx let selected = value.as_ref().map(|x| &this_value == x).unwrap_or_default();
.props()
.value
.as_ref()
.map(|x| value == x)
.unwrap_or_default();
html! { html! {
<option selected={selected}> <option {selected}>
{label} {label}
</option> </option>
} }
@ -98,18 +102,18 @@ impl<T: Clone + PartialEq + 'static> Component for HtmlSelect<T> {
<div <div
class={classes!( class={classes!(
"bp3-html-select", "bp3-html-select",
ctx.props().minimal.then_some("bp3-minimal"), minimal.then_some("bp3-minimal"),
ctx.props().large.then_some("bp3-large"), large.then_some("bp3-large"),
ctx.props().fill.then_some("bp3-fill"), fill.then_some("bp3-fill"),
ctx.props().disabled.then_some("bp3-disabled"), disabled.then_some("bp3-disabled"),
ctx.props().class.clone(), class.clone(),
)} )}
> >
<select <select
disabled={ctx.props().disabled} value={String::new()}
disabled={*disabled}
onchange={ctx.link().callback(|x| x)} onchange={ctx.link().callback(|x| x)}
title={ctx.props().title.clone()} {title}
value={"".to_string()}
ref={self.select_element.clone()} ref={self.select_element.clone()}
> >
{option_children} {option_children}

View file

@ -1,4 +1,5 @@
use crate::Intent; use crate::Intent;
use implicit_clone::ImplicitClone;
use yew::prelude::*; use yew::prelude::*;
include!(concat!(env!("OUT_DIR"), "/icon_svg_paths.rs")); include!(concat!(env!("OUT_DIR"), "/icon_svg_paths.rs"));
@ -12,15 +13,17 @@ impl Default for IconName {
} }
} }
impl ImplicitClone for IconName {}
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct IconProps { pub struct IconProps {
pub icon: IconName, pub icon: IconName,
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
#[prop_or_default] #[prop_or_default]
pub title: Option<String>, pub title: Option<AttrValue>,
#[prop_or_default] #[prop_or_default]
pub color: Option<String>, pub color: Option<AttrValue>,
#[prop_or_default] #[prop_or_default]
pub intent: Option<Intent>, pub intent: Option<Intent>,
#[prop_or(16)] #[prop_or(16)]
@ -30,34 +33,47 @@ pub struct IconProps {
} }
#[function_component(Icon)] #[function_component(Icon)]
pub fn icon(props: &IconProps) -> Html { pub fn icon(
let paths = if props.icon_size == ICON_SIZE_STANDARD { IconProps {
icon_svg_paths_16(props.icon) icon,
class,
title,
color: fill,
intent,
icon_size,
onclick,
}: &IconProps,
) -> Html {
let paths = if *icon_size == ICON_SIZE_STANDARD {
icon_svg_paths_16(*icon)
} else { } else {
icon_svg_paths_20(props.icon) icon_svg_paths_20(*icon)
}; };
let pixel_grid_size = if props.icon_size >= ICON_SIZE_LARGE { let pixel_grid_size = if *icon_size >= ICON_SIZE_LARGE {
ICON_SIZE_LARGE ICON_SIZE_LARGE
} else { } else {
ICON_SIZE_STANDARD ICON_SIZE_STANDARD
}; };
let icon_string = format!("{:?}", props.icon); let icon_string = AttrValue::from(format!("{:?}", icon));
let width = AttrValue::from(format!("{icon_size}"));
let height = width.clone();
html! { html! {
<span <span
class={classes!("bp3-icon", props.class.clone(), props.intent)} class={classes!("bp3-icon", class.clone(), intent)}
onclick={props.onclick.clone()} {onclick}
> >
<svg <svg
fill={props.color.clone()} {fill}
data-icon={icon_string.clone()} data-icon={&icon_string}
width={props.icon_size.to_string()} {width}
height={props.icon_size.to_string()} {height}
viewBox={format!("0 0 {x} {x}", x=pixel_grid_size)} viewBox={format!("0 0 {pixel_grid_size} {pixel_grid_size}")}
> >
<desc>{props.title.clone().unwrap_or(icon_string)}</desc> <desc>{title.clone().unwrap_or(icon_string)}</desc>
{ {
paths.iter() paths
.iter()
.map(|x| html! { .map(|x| html! {
<path d={*x} fillRule="evenodd" /> <path d={*x} fillRule="evenodd" />
}) })

View file

@ -63,13 +63,13 @@ pub struct InputGroupProps {
#[prop_or_default] #[prop_or_default]
pub round: bool, pub round: bool,
#[prop_or_default] #[prop_or_default]
pub placeholder: String, pub placeholder: AttrValue,
#[prop_or_default] #[prop_or_default]
pub left_icon: Option<IconName>, pub left_icon: Option<IconName>,
#[prop_or_default] #[prop_or_default]
pub left_element: Option<yew::virtual_dom::VNode>, pub left_element: Option<Html>,
#[prop_or_default] #[prop_or_default]
pub right_element: Option<yew::virtual_dom::VNode>, pub right_element: Option<Html>,
#[prop_or_default] #[prop_or_default]
pub input_type: TextInputType, pub input_type: TextInputType,
#[prop_or_default] #[prop_or_default]
@ -79,7 +79,7 @@ pub struct InputGroupProps {
#[prop_or_default] #[prop_or_default]
pub onkeydown: Callback<KeyboardEvent>, pub onkeydown: Callback<KeyboardEvent>,
#[prop_or_default] #[prop_or_default]
pub value: String, pub value: AttrValue,
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
#[prop_or_default] #[prop_or_default]
@ -104,7 +104,26 @@ impl Component for InputGroup {
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
let input_style = match (self.left_element_width, self.right_element_width) { let Self::Properties {
disabled,
fill,
large,
small,
round,
placeholder,
left_icon,
left_element,
right_element,
input_type,
oninput,
onkeyup,
onkeydown,
value,
class,
input_ref,
} = &ctx.props();
let 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)),
(Some(left), Some(right)) => format!( (Some(left), Some(right)) => format!(
@ -119,16 +138,16 @@ impl Component for InputGroup {
<div <div
class={classes!( class={classes!(
"bp3-input-group", "bp3-input-group",
ctx.props().disabled.then_some("bp3-disabled"), disabled.then_some("bp3-disabled"),
ctx.props().fill.then_some("bp3-fill"), fill.then_some("bp3-fill"),
ctx.props().large.then_some("bp3-large"), large.then_some("bp3-large"),
ctx.props().small.then_some("bp3-small"), small.then_some("bp3-small"),
ctx.props().round.then_some("bp3-round"), round.then_some("bp3-round"),
ctx.props().class.clone(), class.clone(),
)} )}
> >
{ {
if let Some(left_element) = ctx.props().left_element.clone() { if let Some(left_element) = left_element.clone() {
html! { html! {
<span <span
class="bp3-input-left-container" class="bp3-input-left-container"
@ -137,7 +156,7 @@ impl Component for InputGroup {
{left_element} {left_element}
</span> </span>
} }
} else if let Some(icon) = ctx.props().left_icon { } else if let Some(icon) = left_icon {
html! { html! {
<Icon icon={icon} /> <Icon icon={icon} />
} }
@ -146,19 +165,19 @@ impl Component for InputGroup {
} }
} }
<input <input
ref={ctx.props().input_ref.clone()} ref={input_ref.clone()}
class="bp3-input" class="bp3-input"
type={ctx.props().input_type.as_str()} type={input_type.as_str()}
placeholder={ctx.props().placeholder.clone()} placeholder={placeholder.clone()}
disabled={ctx.props().disabled} disabled={*disabled}
oninput={ctx.props().oninput.clone()} {oninput}
onkeyup={ctx.props().onkeyup.clone()} {onkeyup}
onkeydown={ctx.props().onkeydown.clone()} {onkeydown}
value={ctx.props().value.clone()} {value}
style={input_style} {style}
/> />
{ {
if let Some(right_element) = ctx.props().right_element.clone() { if let Some(right_element) = right_element.clone() {
html! { html! {
<span <span
class="bp3-input-action" class="bp3-input-action"

View file

@ -64,6 +64,7 @@ pub use text_area::*;
#[cfg(feature = "tree")] #[cfg(feature = "tree")]
pub use tree::*; pub use tree::*;
use implicit_clone::ImplicitClone;
use yew::Classes; use yew::Classes;
// See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons // See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
@ -87,6 +88,8 @@ pub enum Intent {
Danger, Danger,
} }
impl ImplicitClone for Intent {}
impl From<Intent> for Classes { impl From<Intent> for Classes {
fn from(intent: Intent) -> Self { fn from(intent: Intent) -> Self {
use Intent::*; use Intent::*;
@ -146,14 +149,16 @@ impl Elevation {
impl Default for Elevation { impl Default for Elevation {
fn default() -> Self { fn default() -> Self {
Elevation::Level0 Self::Level0
} }
} }
impl ImplicitClone for Elevation {}
impl From<Elevation> for Classes { impl From<Elevation> for Classes {
fn from(elevation: Elevation) -> Self { fn from(elevation: Elevation) -> Self {
use Elevation::*; use Elevation::*;
Classes::from(match elevation { Self::from(match elevation {
Level0 => "bp3-elevation-0", Level0 => "bp3-elevation-0",
Level1 => "bp3-elevation-1", Level1 => "bp3-elevation-1",
Level2 => "bp3-elevation-2", Level2 => "bp3-elevation-2",
@ -162,3 +167,9 @@ impl From<Elevation> for Classes {
}) })
} }
} }
impl From<&Elevation> for Classes {
fn from(elevation: &Elevation) -> Self {
Self::from(*elevation)
}
}

View file

@ -10,7 +10,7 @@ pub struct MenuProps {
pub class: Classes, pub class: Classes,
#[prop_or_default] #[prop_or_default]
pub r#ref: NodeRef, pub r#ref: NodeRef,
pub children: html::Children, pub children: Children,
} }
#[function_component(Menu)] #[function_component(Menu)]
@ -32,7 +32,7 @@ pub fn menu(props: &MenuProps) -> Html {
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct MenuItemProps { pub struct MenuItemProps {
#[prop_or_default] #[prop_or_default]
pub text: yew::virtual_dom::VNode, pub text: Html,
#[prop_or_default] #[prop_or_default]
pub text_class: Classes, pub text_class: Classes,
#[prop_or_default] #[prop_or_default]
@ -44,7 +44,7 @@ pub struct MenuItemProps {
#[prop_or_default] #[prop_or_default]
pub href: Option<AttrValue>, pub href: Option<AttrValue>,
#[prop_or_default] #[prop_or_default]
pub label: Option<yew::virtual_dom::VNode>, pub label: Option<Html>,
#[prop_or_default] #[prop_or_default]
pub label_class: Classes, pub label_class: Classes,
// TODO: pub multiline: bool, (requires <Text>) // TODO: pub multiline: bool, (requires <Text>)
@ -57,32 +57,47 @@ pub struct MenuItemProps {
pub icon_html: Option<Html>, pub icon_html: Option<Html>,
#[prop_or_default] #[prop_or_default]
pub onclick: Callback<MouseEvent>, pub onclick: Callback<MouseEvent>,
// TODO: pub children: html::Children, // TODO: pub children: Children,
} }
#[function_component(MenuItem)] #[function_component(MenuItem)]
pub fn menu_item(props: &MenuItemProps) -> Html { pub fn menu_item(
MenuItemProps {
text,
text_class,
active,
class,
disabled,
href,
label,
label_class,
intent,
icon,
icon_html,
onclick,
}: &MenuItemProps,
) -> Html {
html! { html! {
<li> <li>
<a <a
class={classes!( class={classes!(
"bp3-menu-item", "bp3-menu-item",
props.active.then_some("bp3-active"), active.then_some("bp3-active"),
props.disabled.then_some("bp3-disabled"), disabled.then_some("bp3-disabled"),
props.intent intent
.or_else(|| props.active.then_some(Intent::Primary)), .or_else(|| active.then_some(Intent::Primary)),
props.class.clone(), class.clone(),
)} )}
href={(!props.disabled).then(|| props.href.clone()).flatten()} href={(!disabled).then(|| href.clone()).flatten()}
tabIndex={(!props.disabled).then_some("0")} tabIndex={(!disabled).then_some("0")}
onclick={(!props.disabled).then(|| props.onclick.clone())} onclick={(!disabled).then(|| onclick.clone())}
> >
{ {
if let Some(icon_name) = props.icon { if let Some(icon_name) = icon {
html! { html! {
<Icon icon={icon_name} /> <Icon icon={icon_name} />
} }
} else if let Some(html) = props.icon_html.clone() { } else if let Some(html) = icon_html.clone() {
html html
} else { } else {
html! { html! {
@ -90,16 +105,16 @@ pub fn menu_item(props: &MenuItemProps) -> Html {
} }
} }
} }
<div class={classes!("bp3-text", "bp3-fill", props.text_class.clone())}> <div class={classes!("bp3-text", "bp3-fill", text_class.clone())}>
{props.text.clone()} {text.clone()}
</div> </div>
{ {
if let Some(label) = props.label.clone() { if let Some(label) = label.clone() {
html! { html! {
<span <span
class={classes!( class={classes!(
"bp3-menu-item-label", "bp3-menu-item-label",
props.label_class.clone())} label_class.clone())}
> >
{label} {label}
</span> </span>
@ -117,7 +132,7 @@ pub fn menu_item(props: &MenuItemProps) -> Html {
#[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<Html>,
} }
#[function_component(MenuDivider)] #[function_component(MenuDivider)]

View file

@ -42,9 +42,9 @@ where
#[prop_or_default] #[prop_or_default]
pub large: bool, pub large: bool,
#[prop_or_default] #[prop_or_default]
pub class: String, pub class: Classes,
#[prop_or_default] #[prop_or_default]
pub placeholder: String, pub placeholder: AttrValue,
#[prop_or_default] #[prop_or_default]
pub left_icon: Option<IconName>, pub left_icon: Option<IconName>,
#[prop_or_default] #[prop_or_default]
@ -120,7 +120,7 @@ where
} }
} }
fn changed(&mut self, ctx: &Context<Self>) -> bool { fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
self.input = ctx.props().value.to_string(); self.input = ctx.props().value.to_string();
true true
} }
@ -132,14 +132,24 @@ where
disabled, disabled,
disable_buttons, disable_buttons,
buttons_on_the_left, buttons_on_the_left,
.. large,
} = *ctx.props(); placeholder,
left_icon,
left_element,
right_element,
fill,
bounds,
class,
intent,
onchange: _,
} = &ctx.props();
let bounds = &ctx.props().bounds; let button_up_disabled =
let button_up_disabled = disabled || bounds.clamp(value + increment, increment) == value; *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;
let buttons = if disable_buttons { let buttons = if *disable_buttons {
html!() html!()
} else { } else {
html! { html! {
@ -148,11 +158,13 @@ where
icon={IconName::ChevronUp} icon={IconName::ChevronUp}
disabled={button_up_disabled} disabled={button_up_disabled}
onclick={ctx.link().callback(|_| Msg::Up)} onclick={ctx.link().callback(|_| Msg::Up)}
{intent}
/> />
<Button <Button
icon={IconName::ChevronDown} icon={IconName::ChevronDown}
disabled={button_down_disabled} disabled={button_down_disabled}
onclick={ctx.link().callback(|_| Msg::Down)} onclick={ctx.link().callback(|_| Msg::Down)}
{intent}
/> />
</ButtonGroup> </ButtonGroup>
} }
@ -160,12 +172,12 @@ where
let input_group = html! { let input_group = html! {
<InputGroup <InputGroup
placeholder={ctx.props().placeholder.clone()} {placeholder}
large={ctx.props().large} {large}
disabled={ctx.props().disabled} {disabled}
left_icon={ctx.props().left_icon} {left_icon}
left_element={ctx.props().left_element.clone()} left_element={left_element.clone()}
right_element={ctx.props().right_element.clone()} right_element={right_element.clone()}
value={self.input.clone()} value={self.input.clone()}
oninput={ctx.link().callback(|e: InputEvent| { oninput={ctx.link().callback(|e: InputEvent| {
let value = e.target_unchecked_into::<HtmlInputElement>().value(); let value = e.target_unchecked_into::<HtmlInputElement>().value();
@ -183,12 +195,12 @@ where
/> />
}; };
if buttons_on_the_left { if *buttons_on_the_left {
html! { html! {
<ControlGroup <ControlGroup
class={classes!("bp3-numeric-input")} class={classes!("bp3-numeric-input", class.clone())}
fill={ctx.props().fill} {fill}
large={ctx.props().large} {large}
> >
{buttons} {buttons}
{input_group} {input_group}
@ -197,9 +209,9 @@ where
} else { } else {
html! { html! {
<ControlGroup <ControlGroup
class={classes!("bp3-numeric-input")} class={classes!("bp3-numeric-input", class.clone())}
fill={ctx.props().fill} {fill}
large={ctx.props().large} {large}
> >
{input_group} {input_group}
{buttons} {buttons}

View file

@ -1,5 +1,6 @@
use crate::{Button, IconName}; use crate::{Button, IconName};
use gloo::timers::callback::Timeout; use gloo::timers::callback::Timeout;
use implicit_clone::ImplicitClone;
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt; use std::fmt;
use std::rc::Rc; use std::rc::Rc;
@ -42,6 +43,8 @@ pub struct PanelStackState {
action: Option<StateAction>, action: Option<StateAction>,
} }
impl ImplicitClone for PanelStackState {}
impl PanelStackState { impl PanelStackState {
pub fn new(content: Html) -> PanelBuilder<fn(Option<Html>, Html) -> Self, Html, Self> { pub fn new(content: Html) -> PanelBuilder<fn(Option<Html>, Html) -> Self, Html, Self> {
PanelBuilder::new(content, |title, content| { PanelBuilder::new(content, |title, content| {
@ -204,7 +207,7 @@ impl Component for PanelStack {
} }
} }
fn changed(&mut self, ctx: &Context<Self>) -> bool { fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
self.action_to_perform = ctx.props().state.action; self.action_to_perform = ctx.props().state.action;
true true
} }
@ -293,8 +296,8 @@ 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}</span>
{ctx.props().title.clone().unwrap_or_default()} {ctx.props().title.clone()}
<span/> <span/>
</div> </div>
{for ctx.props().children.iter()} {for ctx.props().children.iter()}
@ -302,7 +305,7 @@ impl Component for Panel {
} }
} }
fn changed(&mut self, ctx: &Context<Self>) -> bool { fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
self.animation = ctx.props().animation; self.animation = ctx.props().animation;
true true
} }

View file

@ -16,26 +16,35 @@ pub struct ProgressBarProps {
} }
#[function_component(ProgressBar)] #[function_component(ProgressBar)]
pub fn progress_bar(props: &ProgressBarProps) -> Html { pub fn progress_bar(
let width = if let Some(value) = props.value { ProgressBarProps {
animate,
stripes,
value,
intent,
class,
}: &ProgressBarProps,
) -> Html {
let style = if let Some(value) = value {
// NOTE: nightly, issue #44095 for f32::clamp // NOTE: nightly, issue #44095 for f32::clamp
// let percent = ((1000. * value).ceil() / 10.).clamp(0.,100.); // let percent = ((1000. * value).ceil() / 10.).clamp(0.,100.);
let percent = ((1000. * value).ceil() / 10.).max(0.).min(100.); let percent = ((1000. * value).ceil() / 10.).max(0.).min(100.);
format!("width: {}%;", percent) AttrValue::from(format!("width: {}%;", percent))
} else { } else {
"".into() "".into()
}; };
html! { html! {
<div <div
class={classes!( class={classes!(
"bp3-progress-bar", "bp3-progress-bar",
props.intent, intent,
(!props.animate).then_some("bp3-no-animation"), (!animate).then_some("bp3-no-animation"),
(!props.stripes).then_some("bp3-no-stripes"), (!stripes).then_some("bp3-no-stripes"),
props.class.clone(), class.clone(),
)} )}
> >
<div class={classes!("bp3-progress-meter")} style={{width}}/> <div class={classes!("bp3-progress-meter")} {style}/>
</div> </div>
} }
} }

View file

@ -8,43 +8,58 @@ pub struct RadioProps {
pub inline: bool, pub inline: bool,
#[prop_or_default] #[prop_or_default]
pub large: bool, pub large: bool,
#[prop_or(false)]
pub checked: bool,
#[prop_or_default] #[prop_or_default]
pub checked: Option<bool>, pub name: Option<AttrValue>,
#[prop_or_default] #[prop_or_default]
pub name: Option<String>, pub onchange: Callback<Event>,
#[prop_or_default] #[prop_or_default]
pub onchange: Option<Callback<Event>>, pub label: Html,
#[prop_or_default] #[prop_or_default]
pub label: yew::virtual_dom::VNode, pub value: Option<AttrValue>,
#[prop_or_default] #[prop_or_default]
pub value: Option<String>, pub class: Classes,
} }
#[function_component(Radio)] #[function_component(Radio)]
pub fn radio(props: &RadioProps) -> Html { pub fn radio(
RadioProps {
disabled,
inline,
large,
checked,
name,
onchange,
label,
value,
class,
}: &RadioProps,
) -> Html {
html! { html! {
<label <label
class={classes!( class={classes!(
"bp3-control", "bp3-control",
"bp3-radio", "bp3-radio",
props.disabled.then_some("bp3-disabled"), disabled.then_some("bp3-disabled"),
props.inline.then_some("bp3-inline"), inline.then_some("bp3-inline"),
props.large.then_some("bp3-large"), large.then_some("bp3-large"),
class.clone(),
)} )}
> >
<input <input
type="radio" type="radio"
onchange={props.onchange.clone().unwrap_or_default()} {onchange}
disabled={props.disabled} disabled={*disabled}
value={props.value.clone().unwrap_or_default()} {value}
checked={props.checked.unwrap_or(false)} checked={*checked}
name={props.name.clone().unwrap_or_default()} {name}
/> />
<span <span
class={classes!("bp3-control-indicator")} class={classes!("bp3-control-indicator")}
> >
</span> </span>
{props.label.clone()} {label.clone()}
</label> </label>
} }
} }

View file

@ -1,17 +1,18 @@
use crate::Radio; use crate::Radio;
use implicit_clone::{unsync::IArray, ImplicitClone};
use yew::prelude::*; use yew::prelude::*;
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct RadioGroupProps<T: Clone + PartialEq + 'static> { pub struct RadioGroupProps<T: ImplicitClone + PartialEq + 'static> {
#[prop_or_default] #[prop_or_default]
pub label: Option<yew::virtual_dom::VNode>, pub label: Option<Html>,
#[prop_or_default] #[prop_or_default]
pub disabled: bool, pub disabled: bool,
#[prop_or_default] #[prop_or_default]
pub inline: bool, pub inline: bool,
#[prop_or_default] #[prop_or_default]
pub large: bool, pub large: bool,
pub options: Vec<(T, String)>, pub options: IArray<(T, AttrValue)>,
#[prop_or_default] #[prop_or_default]
pub value: Option<T>, pub value: Option<T>,
#[prop_or_default] #[prop_or_default]
@ -20,26 +21,33 @@ pub struct RadioGroupProps<T: Clone + PartialEq + 'static> {
pub class: Classes, pub class: Classes,
} }
// impl<T: Clone + PartialEq + 'static> Component for RadioGroup<T> {
#[function_component(RadioGroup)] #[function_component(RadioGroup)]
pub fn radio_group<T: Clone + PartialEq + 'static>(props: &RadioGroupProps<T>) -> Html { pub fn radio_group<T: ImplicitClone + PartialEq + 'static>(
let option_children = props RadioGroupProps {
.options label,
disabled,
inline,
large,
options,
value,
onchange,
class,
}: &RadioGroupProps<T>,
) -> Html {
let option_children = options
.iter() .iter()
.map(|(value, label)| { .map(|(this_value, label)| {
let checked = props.value.as_ref().map(|x| value == x).unwrap_or_default(); let checked = value.as_ref().map(|x| &this_value == x).unwrap_or_default();
let value = value.clone();
html! { html! {
<Radio <Radio
value={"".to_string()} value={String::new()}
label={html!(label)} label={html!(label)}
checked={checked} {checked}
onchange={props.onchange.reform(move |_| value.clone())} onchange={onchange.reform(move |_| this_value.clone())}
inline={props.inline} {inline}
disabled={props.disabled} {disabled}
large={props.large} {large}
/> />
} }
}) })
@ -49,16 +57,10 @@ pub fn radio_group<T: Clone + PartialEq + 'static>(props: &RadioGroupProps<T>) -
<div <div
class={classes!( class={classes!(
"bp3-radio-group", "bp3-radio-group",
props.class.clone(), class.clone(),
)} )}
> >
{ {label.clone()}
if let Some(label) = props.label.clone() {
label
} else {
html!()
}
}
{option_children} {option_children}
</div> </div>
} }

View file

@ -1,12 +1,12 @@
use crate::Intent; use crate::Intent;
use std::borrow::Cow; use implicit_clone::{unsync::IArray, ImplicitClone};
use std::marker::PhantomData; 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: ImplicitClone + PartialEq + 'static> {
mouse_move: Closure<dyn FnMut(MouseEvent)>, mouse_move: Closure<dyn FnMut(MouseEvent)>,
mouse_up: Closure<dyn FnMut(MouseEvent)>, mouse_up: Closure<dyn FnMut(MouseEvent)>,
handle_ref: NodeRef, handle_ref: NodeRef,
@ -17,7 +17,7 @@ pub struct Slider<T: Clone + PartialEq + 'static> {
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct SliderProps<T: Clone + PartialEq + 'static> { pub struct SliderProps<T: ImplicitClone + PartialEq + 'static> {
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
#[prop_or_default] #[prop_or_default]
@ -25,9 +25,9 @@ pub struct SliderProps<T: Clone + PartialEq + 'static> {
#[prop_or_default] #[prop_or_default]
pub intent: Option<Intent>, pub intent: Option<Intent>,
#[prop_or_default] #[prop_or_default]
pub value_label: Option<Cow<'static, str>>, pub value_label: Option<AttrValue>,
pub onchange: Callback<T>, pub onchange: Callback<T>,
pub values: Vec<(T, Option<Cow<'static, str>>)>, pub values: IArray<(T, Option<AttrValue>)>,
pub selected: Option<T>, pub selected: Option<T>,
} }
@ -38,7 +38,7 @@ pub enum Msg {
Keyboard(KeyboardEvent), Keyboard(KeyboardEvent),
} }
impl<T: Clone + PartialEq + 'static> Component for Slider<T> { impl<T: ImplicitClone + PartialEq + 'static> Component for Slider<T> {
type Message = Msg; type Message = Msg;
type Properties = SliderProps<T>; type Properties = SliderProps<T>;
@ -98,13 +98,16 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
let position = (pixel_delta / tick_size).round() as usize; let position = (pixel_delta / tick_size).round() as usize;
let (value, _) = let (value, _) = ctx.props().values.get(position).unwrap_or_else(|| {
ctx.props().values.get(position).unwrap_or_else(|| { ctx.props()
ctx.props().values.last().expect("No value in the vec") .values
}); .last()
.cloned()
.expect("No value in the array")
});
if Some(value) != ctx.props().selected.as_ref() { if Some(&value) != ctx.props().selected.as_ref() {
ctx.props().onchange.emit(value.clone()); ctx.props().onchange.emit(value);
} }
true true
@ -139,7 +142,7 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
.props() .props()
.values .values
.iter() .iter()
.position(|(value, _)| Some(value) == ctx.props().selected.as_ref()) .position(|(value, _)| Some(value) == ctx.props().selected)
.map(|i| i.saturating_sub(1)) .map(|i| i.saturating_sub(1))
.unwrap_or(0); .unwrap_or(0);
let (value, _) = ctx.props().values[index].clone(); let (value, _) = ctx.props().values[index].clone();
@ -153,20 +156,16 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
.props() .props()
.values .values
.iter() .iter()
.position(|(value, _)| Some(value) == ctx.props().selected.as_ref()) .position(|(value, _)| Some(value) == ctx.props().selected)
.map(|i| i.saturating_add(1)) .map(|i| i.saturating_add(1))
.unwrap_or(0); .unwrap_or(0);
let (value, _) = ctx let (value, _) = ctx.props().values.get(index).unwrap_or_else(|| {
.props() let (value, label) = ctx.props().values.last().expect(
.values "Already check, \
.get(index)
.unwrap_or_else(|| {
ctx.props().values.last().expect(
"Already check, \
there are at least 2 values in ctx.props().options; qed", there are at least 2 values in ctx.props().options; qed",
) );
}) (value.clone(), label.clone())
.clone(); });
ctx.props().onchange.emit(value); ctx.props().onchange.emit(value);
true true
} }
@ -181,14 +180,14 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
.props() .props()
.values .values
.iter() .iter()
.position(|(value, _)| Some(value) == ctx.props().selected.as_ref()); .position(|(value, _)| Some(value) == ctx.props().selected);
let labels = if ctx.props().values.len() > 1 { let labels = if ctx.props().values.len() > 1 {
ctx.props() ctx.props()
.values .values
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(i, (_, label))| { .filter_map(|(i, (_, label))| {
label.clone().map(|x| { label.map(|x| {
html! { html! {
<div <div
class={classes!("bp3-slider-label")} class={classes!("bp3-slider-label")}
@ -320,7 +319,7 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
onkeydown={ctx.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()}
</span> </span>
} }
} }
@ -334,7 +333,7 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
ref={self.handle_ref.clone()} ref={self.handle_ref.clone()}
style="left: calc(50% - 8px);" style="left: calc(50% - 8px);"
> >
{value_label.clone().unwrap_or_default()} {value_label.clone()}
</span> </span>
} }
} }

View file

@ -35,12 +35,13 @@ pub fn spinner(props: &SpinnerProps) -> Html {
view_box_x, view_box_x, view_box_width, view_box_width, view_box_x, view_box_x, view_box_width, view_box_width,
) )
}; };
let spinner_track = format!( let spinner_track = AttrValue::from(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}", "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, R2 = R * 2.0,
); ));
let stroke_offset = PATH_LENGTH - PATH_LENGTH * props.value.clamp(0.0, 1.0); let stroke_offset = PATH_LENGTH - PATH_LENGTH * props.value.clamp(0.0, 1.0);
let width = AttrValue::from(format!("{size}"));
let height = width.clone();
html! { html! {
<div <div
@ -54,18 +55,18 @@ pub fn spinner(props: &SpinnerProps) -> Html {
class={classes!("bp3-spinner-animation")} class={classes!("bp3-spinner-animation")}
> >
<svg <svg
width={size.to_string()} {width}
height={size.to_string()} {height}
stroke-width={stroke_width.to_string()} stroke-width={stroke_width.to_string()}
viewBox={view_box} viewBox={view_box}
> >
<path <path
class={classes!("bp3-spinner-track")} class={classes!("bp3-spinner-track")}
d={spinner_track.clone()} d={&spinner_track}
/> />
<path <path
class={classes!("bp3-spinner-head")} class={classes!("bp3-spinner-head")}
d={spinner_track} d={&spinner_track}
pathLength={PATH_LENGTH.to_string()} pathLength={PATH_LENGTH.to_string()}
stroke-dasharray={format!("{} {}", PATH_LENGTH, PATH_LENGTH)} stroke-dasharray={format!("{} {}", PATH_LENGTH, PATH_LENGTH)}
stroke-dashoffset={stroke_offset.to_string()} stroke-dashoffset={stroke_offset.to_string()}

View file

@ -13,75 +13,85 @@ pub struct SwitchProps {
#[prop_or_default] #[prop_or_default]
pub onclick: Callback<MouseEvent>, pub onclick: Callback<MouseEvent>,
#[prop_or_default] #[prop_or_default]
pub label: yew::virtual_dom::VNode, pub label: Html,
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
#[prop_or_default] #[prop_or_default]
pub inner_label_checked: Option<String>, pub inner_label_checked: Option<AttrValue>,
#[prop_or_default] #[prop_or_default]
pub inner_label: Option<String>, pub inner_label: Option<AttrValue>,
#[prop_or_default] #[prop_or_default]
pub align_right: bool, pub align_right: bool,
} }
#[function_component(Switch)] #[function_component(Switch)]
pub fn switch(props: &SwitchProps) -> Html { pub fn switch(
let display_label = { SwitchProps {
if props.inner_label.is_some() || props.inner_label_checked.is_some() { checked,
let inner_label = props.inner_label.as_deref().unwrap_or_default(); disabled,
let inner_label_checked = props.inner_label_checked.as_ref(); inline,
html! { large,
<> onclick,
<div class={classes!("bp3-control-indicator-child")}> label,
<div class={classes!("bp3-switch-inner-text")}> class,
{ inner_label_checked,
if let Some(label_checked) = inner_label_checked { inner_label,
label_checked.clone() align_right,
} else { }: &SwitchProps,
inner_label.to_string() ) -> Html {
} let display_label = (inner_label.is_some() || inner_label_checked.is_some()).then(|| {
let inner_label = inner_label.clone().unwrap_or_default();
html! {
<>
<div class={classes!("bp3-control-indicator-child")}>
<div class={classes!("bp3-switch-inner-text")}>
{
if let Some(label_checked) = inner_label_checked.clone() {
label_checked
} else {
inner_label.clone()
} }
</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}
</div> </div>
</> </div>
} </>
} else {
Html::default()
} }
}; });
html! { html! {
<label <label
class={classes!( class={classes!(
"bp3-control", "bp3-control",
"bp3-switch", "bp3-switch",
props.disabled.then_some("bp3-disabled"), disabled.then_some("bp3-disabled"),
props.inline.then_some("bp3-inline"), inline.then_some("bp3-inline"),
props.large.then_some("bp3-large"), large.then_some("bp3-large"),
props.class.clone(), if *align_right {
if props.align_right {
"bp3-align-right" "bp3-align-right"
} else { } else {
"bp3-align-left" "bp3-align-left"
}, },
class.clone(),
)} )}
> >
<input <input
type="checkbox" type="checkbox"
checked={props.checked} checked={*checked}
onclick={props.onclick.clone()} {onclick}
disabled={props.disabled} disabled={*disabled}
/> />
<span <span
class={classes!("bp3-control-indicator")} class={classes!("bp3-control-indicator")}
> >
{display_label} {display_label}
</span> </span>
{props.label.clone()} {label.clone()}
</label> </label>
} }
} }

View file

@ -1,22 +1,23 @@
use implicit_clone::{unsync::IArray, ImplicitClone};
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 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: ImplicitClone + PartialEq + Hash + 'static> {
tab_refs: HashMap<u64, NodeRef>, tab_refs: HashMap<u64, NodeRef>,
indicator_ref: NodeRef, indicator_ref: NodeRef,
phantom: PhantomData<T>, phantom: PhantomData<T>,
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct TabsProps<T: Clone + PartialEq> { pub struct TabsProps<T: ImplicitClone + PartialEq + 'static> {
#[prop_or_default] #[prop_or_default]
pub animate: bool, pub animate: bool,
#[prop_or_default] #[prop_or_default]
pub default_selected_tab_id: Option<T>, pub default_selected_tab_id: Option<T>,
pub id: String, pub id: AttrValue,
#[prop_or_default] #[prop_or_default]
pub large: bool, pub large: bool,
#[prop_or_default] #[prop_or_default]
@ -28,10 +29,10 @@ pub struct TabsProps<T: Clone + PartialEq> {
pub onchange: Callback<T>, pub onchange: Callback<T>,
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
pub tabs: Vec<Tab<T>>, pub tabs: IArray<Tab<T>>,
} }
impl<T: Clone + PartialEq + Hash + 'static> Component for Tabs<T> { impl<T: ImplicitClone + PartialEq + Hash + 'static> Component for Tabs<T> {
type Message = (); type Message = ();
type Properties = TabsProps<T>; type Properties = TabsProps<T>;
@ -194,3 +195,5 @@ pub struct Tab<T> {
pub title_class: Classes, pub title_class: Classes,
pub panel_class: Classes, pub panel_class: Classes,
} }
impl<T: ImplicitClone> ImplicitClone for Tab<T> {}

View file

@ -5,7 +5,7 @@ use yew::virtual_dom::AttrValue;
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct TagProps { pub struct TagProps {
#[prop_or_default] #[prop_or_default]
pub children: html::Children, pub children: Children,
#[prop_or_default] #[prop_or_default]
// FIXME Not clear that this field has any effect without `interactive` on. // FIXME Not clear that this field has any effect without `interactive` on.
pub active: bool, pub active: bool,
@ -40,25 +40,44 @@ pub struct TagProps {
} }
#[function_component(Tag)] #[function_component(Tag)]
pub fn tag(props: &TagProps) -> Html { pub fn tag(
let icon = props.icon.map(|icon| { TagProps {
children,
active,
fill,
icon,
intent,
interactive,
large,
minimal,
multiline,
onclick,
onremove,
right_icon,
round,
title,
class,
style,
}: &TagProps,
) -> Html {
let icon = icon.map(|icon| {
html! { html! {
<Icon icon={icon} /> <Icon {icon} />
} }
}); });
let right_icon = props.right_icon.map(|icon| { let right_icon = right_icon.map(|icon| {
html! { html! {
<Icon icon={icon} /> <Icon {icon} />
} }
}); });
let remove_button = props.onremove.clone().map(|callback| { let remove_button = onremove.clone().map(|onclick| {
html! { html! {
<button <button
class={classes!("bp3-tag-remove")} class={classes!("bp3-tag-remove")}
onclick={callback} {onclick}
tabindex={props.interactive.then_some("0")} tabindex={interactive.then_some("0")}
> >
<Icon icon={IconName::SmallCross} /> <Icon icon={IconName::SmallCross} />
</button> </button>
@ -69,29 +88,29 @@ pub fn tag(props: &TagProps) -> Html {
<span <span
class={classes!( class={classes!(
"bp3-tag", "bp3-tag",
props.intent, intent,
props.active.then_some("bp3-active"), active.then_some("bp3-active"),
props.fill.then_some("bp3-fill"), fill.then_some("bp3-fill"),
props.interactive.then_some("bp3-interactive"), interactive.then_some("bp3-interactive"),
props.large.then_some("bp3-large"), large.then_some("bp3-large"),
props.minimal.then_some("bp3-minimal"), minimal.then_some("bp3-minimal"),
props.round.then_some("bp3-round"), round.then_some("bp3-round"),
props.class.clone(), class.clone(),
)} )}
style={props.style.clone()} {style}
onclick={props.onclick.clone()} {onclick}
> >
{icon.unwrap_or_default()} {icon}
<Text <Text
class={classes!("bp3-fill")} class={classes!("bp3-fill")}
ellipsize={!props.multiline} ellipsize={!multiline}
title={props.title.clone()} {title}
inline=true inline=true
> >
{props.children.clone()} {children.clone()}
</Text> </Text>
{right_icon.unwrap_or_default()} {right_icon}
{remove_button.unwrap_or_default()} {remove_button}
</span> </span>
} }
} }

View file

@ -19,17 +19,26 @@ pub struct TextProps {
} }
#[function_component(Text)] #[function_component(Text)]
pub fn text(props: &TextProps) -> Html { pub fn text(
TextProps {
ellipsize,
children,
class,
inline,
title,
style,
}: &TextProps,
) -> Html {
html! { html! {
<@{if props.inline { "span" } else { "div"}} <@{if *inline { "span" } else { "div"}}
class={classes!( class={classes!(
props.class.clone(), ellipsize.then_some("bp3-text-overflow-ellipsis"),
props.ellipsize.then_some("bp3-text-overflow-ellipsis"), class.clone(),
)} )}
style={props.style.clone()} {style}
title={props.title.clone()} {title}
> >
{props.children.clone()} {children.clone()}
</@> </@>
} }
} }

View file

@ -7,10 +7,9 @@ pub struct TextAreaProps {
pub class: Classes, pub class: Classes,
#[prop_or_default] #[prop_or_default]
pub fill: bool, pub fill: bool,
//TODO pub grow_vertically: bool,
#[prop_or_default] #[prop_or_default]
pub grow_vertically: bool, pub r#ref: NodeRef,
#[prop_or_default]
pub input_ref: NodeRef,
#[prop_or_default] #[prop_or_default]
pub intent: Option<Intent>, pub intent: Option<Intent>,
#[prop_or_default] #[prop_or_default]
@ -18,23 +17,33 @@ 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<Event>>, pub onchange: Callback<Event>,
} }
#[function_component(TextArea)] #[function_component(TextArea)]
pub fn text_area(props: &TextAreaProps) -> Html { pub fn text_area(
let classes = classes!( TextAreaProps {
"bp3-input", class,
props.intent, fill,
props.class.clone(), r#ref,
props.fill.then_some("bp3-fill"), intent,
props.small.then_some("bp3-small"), large,
props.large.then_some("bp3-large"), small,
); onchange,
}: &TextAreaProps,
) -> Html {
html! { html! {
<textarea <textarea
class={classes} class={classes!(
onchange={props.onchange.clone()} "bp3-input",
intent,
fill.then_some("bp3-fill"),
small.then_some("bp3-small"),
large.then_some("bp3-large"),
class.clone(),
)}
ref={r#ref}
{onchange}
/> />
} }
} }

View file

@ -1,6 +1,7 @@
use crate::collapse::Collapse; use crate::collapse::Collapse;
use crate::icon::{Icon, IconName}; use crate::icon::{Icon, IconName};
use crate::Intent; use crate::Intent;
use gloo::timers::callback::Timeout;
use id_tree::*; use id_tree::*;
use std::cell::{Ref, RefCell, RefMut}; use std::cell::{Ref, RefCell, RefMut};
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
@ -18,7 +19,7 @@ pub struct TreeData<T> {
impl<T> PartialEq for TreeData<T> { impl<T> PartialEq for TreeData<T> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.version == other.version Rc::ptr_eq(&self.tree, &other.tree) && self.version == other.version
} }
} }
@ -66,12 +67,12 @@ pub struct NodeData<T> {
pub disabled: bool, pub disabled: bool,
pub has_caret: bool, pub has_caret: bool,
pub icon: Option<IconName>, pub icon: Option<IconName>,
pub icon_color: Option<String>, pub icon_color: Option<AttrValue>,
pub icon_intent: Option<Intent>, pub icon_intent: Option<Intent>,
pub is_expanded: bool, pub is_expanded: bool,
pub is_selected: bool, pub is_selected: bool,
pub label: yew::virtual_dom::VNode, pub label: Html,
pub secondary_label: Option<yew::virtual_dom::VNode>, pub secondary_label: Option<Html>,
pub data: T, pub data: T,
} }
@ -148,12 +149,7 @@ impl<T: Clone + PartialEq + 'static> Component for Tree<T> {
// FIXME: The 'static bound here is probably wrong. Fix this at the end of PR. // FIXME: The 'static bound here is probably wrong. Fix this at the end of PR.
impl<T: 'static + Clone + PartialEq> Tree<T> { impl<T: 'static + Clone + PartialEq> Tree<T> {
fn render_children( fn render_children(&self, ctx: &Context<Self>, node_id: &NodeId, depth: u32) -> Html {
&self,
ctx: &Context<Self>,
node_id: &NodeId,
depth: u32,
) -> yew::virtual_dom::VNode {
let tree = ctx.props().tree.borrow(); 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();
@ -207,6 +203,8 @@ impl<T: 'static + Clone + PartialEq> Tree<T> {
struct TreeNode { struct TreeNode {
handler_caret_click: Callback<MouseEvent>, handler_caret_click: Callback<MouseEvent>,
handler_click: Callback<MouseEvent>, handler_click: Callback<MouseEvent>,
// NOTE: to prevent event bubbling, see https://github.com/yewstack/yew/issues/3041
callback_timeout: Option<Timeout>,
} }
#[derive(Clone, Properties)] #[derive(Clone, Properties)]
@ -215,16 +213,16 @@ struct TreeNodeProps {
disabled: bool, disabled: bool,
has_caret: bool, has_caret: bool,
icon: Option<IconName>, icon: Option<IconName>,
icon_color: Option<String>, icon_color: Option<AttrValue>,
icon_intent: Option<Intent>, icon_intent: Option<Intent>,
is_expanded: bool, is_expanded: bool,
is_selected: bool, is_selected: bool,
label: yew::virtual_dom::VNode, label: Html,
secondary_label: Option<yew::virtual_dom::VNode>, secondary_label: Option<Html>,
on_collapse: Option<Callback<(NodeId, MouseEvent)>>, on_collapse: Option<Callback<(NodeId, MouseEvent)>>,
on_expand: Option<Callback<(NodeId, MouseEvent)>>, on_expand: Option<Callback<(NodeId, MouseEvent)>>,
onclick: Option<Callback<(NodeId, MouseEvent)>>, onclick: Option<Callback<(NodeId, MouseEvent)>>,
children: html::Children, children: yew::Children,
depth: u32, depth: u32,
} }
@ -261,6 +259,7 @@ impl Component for TreeNode {
TreeNode { TreeNode {
handler_caret_click: ctx.link().callback(TreeNodeMessage::CaretClick), handler_caret_click: ctx.link().callback(TreeNodeMessage::CaretClick),
handler_click: ctx.link().callback(TreeNodeMessage::Click), handler_click: ctx.link().callback(TreeNodeMessage::Click),
callback_timeout: None,
} }
} }
@ -269,21 +268,20 @@ impl Component for TreeNode {
return false; return false;
} }
let node_id = ctx.props().node_id.clone();
match msg { match msg {
TreeNodeMessage::CaretClick(event) => { TreeNodeMessage::CaretClick(event) => {
if ctx.props().is_expanded { if ctx.props().is_expanded {
if let Some(on_collapse) = ctx.props().on_collapse.as_ref() { if let Some(on_collapse) = ctx.props().on_collapse.clone() {
event.stop_propagation(); self.register_callback(on_collapse, (node_id, event));
on_collapse.emit((ctx.props().node_id.clone(), event));
} }
} else if let Some(on_expand) = ctx.props().on_expand.as_ref() { } else if let Some(on_expand) = ctx.props().on_expand.clone() {
event.stop_propagation(); self.register_callback(on_expand, (node_id, event));
on_expand.emit((ctx.props().node_id.clone(), event));
} }
} }
TreeNodeMessage::Click(event) => { TreeNodeMessage::Click(event) => {
if let Some(onclick) = ctx.props().onclick.as_ref() { if let Some(onclick) = ctx.props().onclick.clone() {
onclick.emit((ctx.props().node_id.clone(), event)); self.register_callback(onclick, (node_id, event));
} }
} }
} }
@ -306,16 +304,16 @@ impl Component for TreeNode {
> >
{ {
if ctx.props().has_caret { if ctx.props().has_caret {
let mut class = Classes::from("bp3-tree-node-caret");
class.push(if ctx.props().is_expanded {
"bp3-tree-node-caret-open"
} else {
"bp3-tree-node-caret-closed"
});
html! { html! {
<Icon <Icon
class={classes!(class.to_string())} class={classes!(
"bp3-tree-node-caret",
if ctx.props().is_expanded {
"bp3-tree-node-caret-open"
} else {
"bp3-tree-node-caret-closed"
},
)}
icon={IconName::ChevronRight} icon={IconName::ChevronRight}
onclick={self.handler_caret_click.clone()} onclick={self.handler_caret_click.clone()}
/> />
@ -353,4 +351,17 @@ impl Component for TreeNode {
</li> </li>
} }
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
self.callback_timeout.take();
}
}
impl TreeNode {
fn register_callback<IN: 'static>(&mut self, callback: Callback<IN>, value: IN) {
if self.callback_timeout.is_none() {
self.callback_timeout
.replace(Timeout::new(0, move || callback.emit(value)));
}
}
} }

View file

@ -6,7 +6,7 @@ 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
[dependencies] [dependencies]
env_logger = "0.9" env_logger = "0.10"
log = "0.4" log = "0.4"
xtask-wasm = "0.1.3" xtask-wasm = "0.1.9"
yewprint-css = { path = "../yewprint-css" } yewprint-css = { path = "../yewprint-css" }

View file

@ -19,4 +19,4 @@ flate2 = "1"
log = "0.4" log = "0.4"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
tar = "0.4" tar = "0.4"
ureq = { version = "2.4", features = ["json"] } ureq = { version = "2.5", features = ["json"] }

View file

@ -14,8 +14,9 @@ crate-type = ["cdylib"]
# logging them with `console.error`. This is great for development, but requires # logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying. # code size when deploying.
console_error_panic_hook = { version = "0.1.6", optional = true } console_error_panic_hook = { version = "0.1.7", optional = true }
gloo = "0.6" gloo = "0.8"
implicit-clone = "0.3.3"
once_cell = "1.16" once_cell = "1.16"
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["Window", "MediaQueryList", "Event", "HtmlInputElement"] } web-sys = { version = "0.3", features = ["Window", "MediaQueryList", "Event", "HtmlInputElement"] }
@ -25,13 +26,13 @@ web-sys = { version = "0.3", features = ["Window", "MediaQueryList", "Event", "H
# #
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. # Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
wee_alloc = { version = "0.4.5", optional = true } wee_alloc = { version = "0.4.5", optional = true }
yew = "0.19" yew = { version = "0.20", features = ["csr"] }
yew-router = "0.16" yew-router = "0.17"
yewprint = { path = ".." } yewprint = { path = ".." }
[build-dependencies] [build-dependencies]
build-data = "0.1.3" build-data = "0.1.3"
syntect = "4.4.0" syntect = "0.5.0"
[dev-dependencies] [dev-dependencies]
ureq = "2.4" ureq = "2.5"

View file

@ -34,7 +34,7 @@ fn main() {
.join(&path) .join(&path)
.with_file_name("mod.rs.html"); .with_file_name("mod.rs.html");
let src = let src =
syntect::html::highlighted_html_for_file(&path, syntax_set, theme).unwrap(); syntect::html::highlighted_snippet_for_file(&path, syntax_set, theme).unwrap();
let _ = std::fs::create_dir_all(dest_path.parent().unwrap()); let _ = std::fs::create_dir_all(dest_path.parent().unwrap());
fs::write(&dest_path, src.trim_end()).unwrap(); fs::write(&dest_path, src.trim_end()).unwrap();

View file

@ -62,8 +62,8 @@ impl Component for App {
Msg::ToggleLight => self.dark_theme ^= true, Msg::ToggleLight => self.dark_theme ^= true,
Msg::GoToMenu(event, doc_menu) => { Msg::GoToMenu(event, doc_menu) => {
event.prevent_default(); event.prevent_default();
if let Some(history) = ctx.link().history() { if let Some(navigator) = ctx.link().navigator() {
history.push(doc_menu); navigator.push(&doc_menu);
} else { } else {
gloo::console::warn!("Could not get history from Context"); gloo::console::warn!("Could not get history from Context");
} }
@ -284,7 +284,7 @@ impl Component for App {
{{ navigation }} {{ navigation }}
<main class={classes!("docs-content-wrapper")} role="main"> <main class={classes!("docs-content-wrapper")} role="main">
<div class={classes!("docs-page")}> <div class={classes!("docs-page")}>
<Switch<DocMenu> render={Switch::render(switch)} /> <Switch<DocMenu> render={switch} />
</div> </div>
</main> </main>
</div> </div>
@ -293,7 +293,7 @@ impl Component for App {
} }
} }
fn switch(route: &DocMenu) -> Html { fn switch(route: DocMenu) -> Html {
match route { match route {
DocMenu::Button | DocMenu::Home => html! (<ButtonDoc />), DocMenu::Button | DocMenu::Home => html! (<ButtonDoc />),
DocMenu::ButtonGroup => html! (<ButtonGroupDoc />), DocMenu::ButtonGroup => html! (<ButtonGroupDoc />),

View file

@ -1,5 +1,5 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::Button; use yewprint::{Button, Intent};
pub struct Example { pub struct Example {
counter: i64, counter: i64,
@ -15,6 +15,7 @@ pub struct ExampleProps {
pub large: bool, pub large: bool,
pub active: bool, pub active: bool,
pub disabled: bool, pub disabled: bool,
pub intent: Option<Intent>,
} }
pub enum Msg { pub enum Msg {
@ -37,20 +38,33 @@ impl Component for Example {
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
let Self::Properties {
minimal,
fill,
small,
outlined,
loading,
large,
active,
disabled,
intent,
} = &ctx.props();
html! { html! {
<div> <div>
<p>{"Counter: "}{self.counter}</p> <p>{"Counter: "}{self.counter}</p>
<div> <div>
<Button <Button
onclick={ctx.link().callback(|_| Msg::AddOne)} onclick={ctx.link().callback(|_| Msg::AddOne)}
minimal={ctx.props().minimal} {minimal}
fill={ctx.props().fill} {fill}
small={ctx.props().small} {small}
outlined={ctx.props().outlined} {outlined}
loading={ctx.props().loading} {loading}
large={ctx.props().large} {large}
active={ctx.props().active} {active}
disabled={ctx.props().disabled} {disabled}
{intent}
> >
{"Add 1"} {"Add 1"}
</Button> </Button>

View file

@ -2,8 +2,9 @@ mod example;
use crate::ExampleContainer; use crate::ExampleContainer;
use example::*; use example::*;
use implicit_clone::unsync::IArray;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Switch, H1, H5}; use yewprint::{HtmlSelect, Intent, Switch, H1, H5};
pub struct ButtonDoc { pub struct ButtonDoc {
callback: Callback<ExampleProps>, callback: Callback<ExampleProps>,
@ -26,6 +27,7 @@ impl Component for ButtonDoc {
large: false, large: false,
active: false, active: false,
disabled: false, disabled: false,
intent: None,
}, },
} }
} }
@ -136,6 +138,20 @@ crate::build_example_prop_component! {
checked={ctx.props().example_props.disabled} checked={ctx.props().example_props.disabled}
label={html!("Disabled")} label={html!("Disabled")}
/> />
<HtmlSelect<Option<Intent>>
options={[
(None, "None".into()),
(Some(Intent::Primary), "Primary".into()),
(Some(Intent::Success), "Success".into()),
(Some(Intent::Warning), "Warning".into()),
(Some(Intent::Danger), "Danger".into()),
].into_iter().collect::<IArray<_>>()}
onchange={self.update_props(ctx, |props, intent| ExampleProps {
intent,
..props
})}
value={ctx.props().example_props.intent}
/>
</div> </div>
} }
} }

View file

@ -9,12 +9,18 @@ pub struct ExampleProps {
} }
#[function_component(Example)] #[function_component(Example)]
pub fn example(props: &ExampleProps) -> Html { pub fn example(
ExampleProps {
intent,
show_icon,
show_title,
}: &ExampleProps,
) -> Html {
html! { html! {
<Callout <Callout
title={props.show_title.then_some("Visually important content")} title={show_title.then_some("Visually important content")}
without_icon={!props.show_icon} without_icon={!show_icon}
intent={props.intent} {intent}
> >
<p>{"The Callout element's background reflects its intent, if any."}</p> <p>{"The Callout element's background reflects its intent, if any."}</p>
</Callout> </Callout>

View file

@ -2,6 +2,7 @@ mod example;
use crate::ExampleContainer; use crate::ExampleContainer;
use example::*; use example::*;
use implicit_clone::unsync::IArray;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{HtmlSelect, Intent, Switch, H1, H5}; use yewprint::{HtmlSelect, Intent, Switch, H1, H5};
@ -82,17 +83,18 @@ crate::build_example_prop_component! {
/> />
<p>{"Select intent:"}</p> <p>{"Select intent:"}</p>
<HtmlSelect<Option<Intent>> <HtmlSelect<Option<Intent>>
options={vec![ options={[
(None, "None".to_string()), (None, "None".into()),
(Some(Intent::Primary), "Primary".to_string()), (Some(Intent::Primary), "Primary".into()),
(Some(Intent::Success), "Success".to_string()), (Some(Intent::Success), "Success".into()),
(Some(Intent::Warning), "Warning".to_string()), (Some(Intent::Warning), "Warning".into()),
(Some(Intent::Danger), "Danger".to_string()), (Some(Intent::Danger), "Danger".into()),
]} ].into_iter().collect::<IArray<_>>()}
onchange={self.update_props(ctx, |props, intent| ExampleProps { onchange={self.update_props(ctx, |props, intent| ExampleProps {
intent, intent,
..props ..props
})} })}
value={ctx.props().example_props.intent}
/> />
</div> </div>
</div> </div>

View file

@ -2,6 +2,7 @@ mod example;
use crate::ExampleContainer; use crate::ExampleContainer;
use example::*; use example::*;
use implicit_clone::unsync::IArray;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Elevation, HtmlSelect, Switch, H1, H5}; use yewprint::{Elevation, HtmlSelect, Switch, H1, H5};
@ -73,13 +74,13 @@ crate::build_example_prop_component! {
/> />
<p>{"Elevation:"}</p> <p>{"Elevation:"}</p>
<HtmlSelect<Elevation> <HtmlSelect<Elevation>
options={vec![ options={[
(Elevation::Level0, "Level 0".to_string()), (Elevation::Level0, "Level 0".into()),
(Elevation::Level1, "Level 1".to_string()), (Elevation::Level1, "Level 1".into()),
(Elevation::Level2, "Level 2".to_string()), (Elevation::Level2, "Level 2".into()),
(Elevation::Level3, "Level 3".to_string()), (Elevation::Level3, "Level 3".into()),
(Elevation::Level4, "Level 4".to_string()), (Elevation::Level4, "Level 4".into()),
]} ].into_iter().collect::<IArray<_>>()}
value={ctx.props().example_props.elevation} value={ctx.props().example_props.elevation}
onchange={self.update_props(ctx, |props, elevation| ExampleProps { onchange={self.update_props(ctx, |props, elevation| ExampleProps {
elevation, elevation,

View file

@ -1,3 +1,4 @@
use implicit_clone::{unsync::IArray, ImplicitClone};
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, ControlGroup, HtmlSelect, IconName, InputGroup}; use yewprint::{Button, ControlGroup, HtmlSelect, IconName, InputGroup};
@ -8,20 +9,20 @@ pub struct ExampleProps {
} }
#[function_component(Example)] #[function_component(Example)]
pub fn example(props: &ExampleProps) -> Html { pub fn example(ExampleProps { fill, vertical }: &ExampleProps) -> Html {
html! { html! {
<ControlGroup <ControlGroup
fill={props.fill} {fill}
vertical={props.vertical} {vertical}
> >
<HtmlSelect<Option<Sorting>> <HtmlSelect<Option<Sorting>>
options={vec![ options={[
(None, "Filter".to_string()), (None, "Filter".into()),
(Some(Sorting::NameAscending), "Name - ascending".to_string()), (Some(Sorting::NameAscending), "Name - ascending".into()),
(Some(Sorting::NameDescending), "Name - descending".to_string()), (Some(Sorting::NameDescending), "Name - descending".into()),
(Some(Sorting::PriceAscending), "Price - ascending".to_string()), (Some(Sorting::PriceAscending), "Price - ascending".into()),
(Some(Sorting::PriceDescending), "Price - descending".to_string()), (Some(Sorting::PriceDescending), "Price - descending".into()),
]} ].into_iter().collect::<IArray<_>>()}
/> />
<InputGroup placeholder="Find filters..." /> <InputGroup placeholder="Find filters..." />
<Button icon={IconName::ArrowRight} /> <Button icon={IconName::ArrowRight} />
@ -36,3 +37,5 @@ pub enum Sorting {
PriceAscending, PriceAscending,
PriceDescending, PriceDescending,
} }
impl ImplicitClone for Sorting {}

View file

@ -11,10 +11,10 @@ pub enum Msg {
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleContainerProps { pub struct ExampleContainerProps {
pub source: yew::virtual_dom::VNode, pub source: Html,
pub children: html::Children, pub children: Children,
#[prop_or_default] #[prop_or_default]
pub props: Option<yew::virtual_dom::VNode>, pub props: Option<Html>,
} }
impl Component for ExampleContainer { impl Component for ExampleContainer {

View file

@ -1,3 +1,4 @@
use implicit_clone::{unsync::IArray, ImplicitClone};
use yew::prelude::*; use yew::prelude::*;
use yewprint::{HtmlSelect, Text}; use yewprint::{HtmlSelect, Text};
@ -31,7 +32,7 @@ impl Component for Example {
true true
} }
fn changed(&mut self, ctx: &Context<Self>) -> bool { fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
if self.reset != ctx.props().reset { if self.reset != ctx.props().reset {
self.reset = ctx.props().reset; self.reset = ctx.props().reset;
self.log_level = LogLevel::Info; self.log_level = LogLevel::Info;
@ -43,14 +44,14 @@ impl Component for Example {
html! { html! {
<div style="width: 400px; text-align: center;"> <div style="width: 400px; text-align: center;">
<HtmlSelect<LogLevel> <HtmlSelect<LogLevel>
options={vec![ options={[
(LogLevel::Trace, "TRACE".to_string()), (LogLevel::Trace, "TRACE".into()),
(LogLevel::Debug, "DEBUG".to_string()), (LogLevel::Debug, "DEBUG".into()),
(LogLevel::Info, "INFO".to_string()), (LogLevel::Info, "INFO".into()),
(LogLevel::Warn, "WARN".to_string()), (LogLevel::Warn, "WARN".into()),
(LogLevel::Error, "ERROR".to_string()), (LogLevel::Error, "ERROR".into()),
(LogLevel::Off, "OFF".to_string()), (LogLevel::Off, "OFF".into()),
]} ].into_iter().collect::<IArray<_>>()}
minimal={ctx.props().minimal} minimal={ctx.props().minimal}
fill={ctx.props().fill} fill={ctx.props().fill}
disabled={ctx.props().disabled} disabled={ctx.props().disabled}
@ -74,3 +75,5 @@ pub enum LogLevel {
Error, Error,
Off, Off,
} }
impl ImplicitClone for LogLevel {}

View file

@ -2,8 +2,8 @@ mod example;
use crate::ExampleContainer; use crate::ExampleContainer;
use example::*; use example::*;
use implicit_clone::unsync::IArray;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::borrow::Cow;
use web_sys::HtmlInputElement; use web_sys::HtmlInputElement;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{HtmlSelect, Icon, IconName, InputGroup, Intent, Slider, Text, H1, H5}; use yewprint::{HtmlSelect, Icon, IconName, InputGroup, Intent, Slider, Text, H1, H5};
@ -58,21 +58,24 @@ impl Component for IconDoc {
"bp3-code-block" "bp3-code-block"
); );
let search_string = &self.search_string.to_lowercase(); let search_string = self.search_string.to_lowercase();
let icon_list = ICON_LIST let icon_list = ICON_LIST
.iter() .iter()
.filter_map(|(icon_name, icon)| { .filter_map(|(icon_name, icon)| {
icon_name.contains(search_string).then_some(*icon).map(|x| { icon_name
html! { .contains(&search_string)
<div class={classes!("docs-icon-list-item")}> .then_some(*icon)
<Icon .map(|icon| {
icon={x} html! {
icon_size=20 <div class={classes!("docs-icon-list-item")}>
/> <Icon
<Text>{format!("{:?}", x)}</Text> {icon}
</div> icon_size=20
} />
}) <Text>{format!("{:?}", icon)}</Text>
</div>
}
})
}) })
.collect::<Html>(); .collect::<Html>();
@ -101,7 +104,7 @@ impl Component for IconDoc {
value={self.search_string.clone()} value={self.search_string.clone()}
oninput={ctx.link().callback(|e: InputEvent| { oninput={ctx.link().callback(|e: InputEvent| {
let value = e.target_unchecked_into::<HtmlInputElement>().value(); let value = e.target_unchecked_into::<HtmlInputElement>().value();
IconDocMsg::SearchIcon(value) IconDocMsg::SearchIcon(value.into())
})} })}
/> />
</div> </div>
@ -118,7 +121,7 @@ crate::build_example_prop_component! {
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
let option_labels = (0..=100) let option_labels = (0..=100)
.map(|x| (x, (x % 20 == 0).then(|| format!("{}", x).into()))) .map(|x| (x, (x % 20 == 0).then(|| format!("{}", x).into())))
.collect::<Vec<_>>(); .collect::<IArray<_>>();
html! { html! {
<div> <div>
@ -153,13 +156,13 @@ crate::build_example_prop_component! {
{"Select intent:"} {"Select intent:"}
</p> </p>
<HtmlSelect<Option<Intent>> <HtmlSelect<Option<Intent>>
options={vec![ options={[
(None, "None".to_string()), (None, "None".into()),
(Some(Intent::Primary), "Primary".to_string()), (Some(Intent::Primary), "Primary".into()),
(Some(Intent::Success), "Success".to_string()), (Some(Intent::Success), "Success".into()),
(Some(Intent::Warning), "Warning".to_string()), (Some(Intent::Warning), "Warning".into()),
(Some(Intent::Danger), "Danger".to_string()), (Some(Intent::Danger), "Danger".into()),
]} ].into_iter().collect::<IArray<_>>()}
value={self.example_props.intent} value={self.example_props.intent}
onchange={self.update_props(ctx, |props, intent| ExampleProps { onchange={self.update_props(ctx, |props, intent| ExampleProps {
intent, intent,
@ -179,7 +182,7 @@ crate::build_example_prop_component! {
..props ..props
})} })}
value_label={ value_label={
Cow::Owned(format!("{}", ctx.props().example_props.icon_size)) format!("{}", ctx.props().example_props.icon_size)
} }
/> />
</div> </div>

View file

@ -97,7 +97,7 @@ impl Component for Example {
value={self.histogram_value.clone()} value={self.histogram_value.clone()}
oninput={ctx.link().callback(|e: InputEvent| { oninput={ctx.link().callback(|e: InputEvent| {
let value = e.target_unchecked_into::<HtmlInputElement>().value(); let value = e.target_unchecked_into::<HtmlInputElement>().value();
Msg::UpdateHistogram(value) Msg::UpdateHistogram(value.into())
})} })}
onkeydown={ctx.link().callback(|e: KeyboardEvent| { onkeydown={ctx.link().callback(|e: KeyboardEvent| {
if e.key() == "Enter" { Msg::AddHistogramEntry } else { Msg::Noop } if e.key() == "Enter" { Msg::AddHistogramEntry } else { Msg::Noop }
@ -114,7 +114,7 @@ impl Component for Example {
value={self.password_value.clone()} value={self.password_value.clone()}
oninput={ctx.link().callback(|e: InputEvent| { oninput={ctx.link().callback(|e: InputEvent| {
let value = e.target_unchecked_into::<HtmlInputElement>().value(); let value = e.target_unchecked_into::<HtmlInputElement>().value();
Msg::UpdatePassword(value) Msg::UpdatePassword(value.into())
})} })}
onkeydown={ctx.link().callback(|e: KeyboardEvent| { onkeydown={ctx.link().callback(|e: KeyboardEvent| {
if e.key() == "Enter" { Msg::AddPasswordEntry } else { Msg::Noop } if e.key() == "Enter" { Msg::AddPasswordEntry } else { Msg::Noop }
@ -138,7 +138,7 @@ impl Component for Example {
value={self.tags_value.clone()} value={self.tags_value.clone()}
oninput={ctx.link().callback(|e: InputEvent| { oninput={ctx.link().callback(|e: InputEvent| {
let value = e.target_unchecked_into::<HtmlInputElement>().value(); let value = e.target_unchecked_into::<HtmlInputElement>().value();
Msg::UpdateTags(value) Msg::UpdateTags(value.into())
})} })}
onkeydown={ctx.link().callback(|e: KeyboardEvent| { onkeydown={ctx.link().callback(|e: KeyboardEvent| {
if e.key() == "Enter" { Msg::AddTagsEntry } else { Msg::Noop } if e.key() == "Enter" { Msg::AddTagsEntry } else { Msg::Noop }

View file

@ -42,7 +42,7 @@ pub use logo::*;
#[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(web_sys::Node::from({ Html::VRef(web_sys::Node::from({
let div = web_sys::window() let div = web_sys::window()
.unwrap() .unwrap()
.document() .document()
@ -136,7 +136,8 @@ 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::AppRoot>();
yew::Renderer::<app::AppRoot>::new().render();
Ok(()) Ok(())
} }

View file

@ -14,6 +14,7 @@ pub struct ExampleProps {
pub disable_buttons: bool, pub disable_buttons: bool,
pub buttons_on_the_left: bool, pub buttons_on_the_left: bool,
pub left_icon: bool, pub left_icon: bool,
pub intent: Option<Intent>,
} }
pub enum Msg { pub enum Msg {
@ -50,32 +51,45 @@ impl Component for Example {
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
let Self::Properties {
fill,
disabled,
large,
disable_buttons,
buttons_on_the_left,
left_icon,
intent,
} = &ctx.props();
html! { html! {
<> <>
<NumericInput<i32> <NumericInput<i32>
disabled={ctx.props().disabled} {disabled}
fill={ctx.props().large} {fill}
{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={ctx.link().callback(|x| Msg::UpdateValue(x))} onchange={ctx.link().callback(|x| Msg::UpdateValue(x))}
disable_buttons={ctx.props().disable_buttons} {disable_buttons}
buttons_on_the_left={ctx.props().buttons_on_the_left} {buttons_on_the_left}
left_icon={ctx.props().left_icon.then_some(IconName::Dollar)} left_icon={left_icon.then_some(IconName::Dollar)}
{intent}
/> />
<NumericInput<i32> <NumericInput<i32>
disabled={ctx.props().disabled} {disabled}
fill={ctx.props().fill} {fill}
large={ctx.props().large} {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={ctx.link().callback(|x| Msg::UpdateValueTwo(x))} onchange={ctx.link().callback(|x| Msg::UpdateValueTwo(x))}
disable_buttons={ctx.props().disable_buttons} {disable_buttons}
buttons_on_the_left={ctx.props().buttons_on_the_left} {buttons_on_the_left}
left_icon={ctx.props().left_icon.then_some(IconName::Dollar)} left_icon={left_icon.then_some(IconName::Dollar)}
{intent}
/> />
<Button <Button
icon={IconName::Refresh} icon={IconName::Refresh}
@ -84,8 +98,8 @@ impl Component for Example {
{"Reset at 4"} {"Reset at 4"}
</Button> </Button>
<Callout <Callout
title={"Selected values"} title="Selected values"
intent={Intent::Primary} {intent}
> >
<ul> <ul>
<li>{self.value}</li> <li>{self.value}</li>

View file

@ -2,8 +2,9 @@ mod example;
use crate::ExampleContainer; use crate::ExampleContainer;
use example::*; use example::*;
use implicit_clone::unsync::IArray;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Switch, H1, H5}; use yewprint::{HtmlSelect, Intent, Switch, H1, H5};
pub struct NumericInputDoc { pub struct NumericInputDoc {
callback: Callback<ExampleProps>, callback: Callback<ExampleProps>,
@ -24,6 +25,7 @@ impl Component for NumericInputDoc {
disable_buttons: false, disable_buttons: false,
buttons_on_the_left: false, buttons_on_the_left: false,
left_icon: false, left_icon: false,
intent: None,
}, },
} }
} }
@ -116,6 +118,20 @@ crate::build_example_prop_component! {
checked={ctx.props().example_props.left_icon} checked={ctx.props().example_props.left_icon}
label={html!("Left icon")} label={html!("Left icon")}
/> />
<HtmlSelect<Option<Intent>>
options={[
(None, "None".into()),
(Some(Intent::Primary), "Primary".into()),
(Some(Intent::Success), "Success".into()),
(Some(Intent::Warning), "Warning".into()),
(Some(Intent::Danger), "Danger".into()),
].into_iter().collect::<IArray<_>>()}
onchange={self.update_props(ctx, |props, intent| ExampleProps {
intent,
..props
})}
value={ctx.props().example_props.intent}
/>
</div> </div>
} }
} }

View file

@ -2,6 +2,7 @@ mod example;
use crate::ExampleContainer; use crate::ExampleContainer;
use example::*; use example::*;
use implicit_clone::unsync::IArray;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{HtmlSelect, Intent, Switch, H1, H5}; use yewprint::{HtmlSelect, Intent, Switch, H1, H5};
@ -82,13 +83,13 @@ crate::build_example_prop_component! {
/> />
<p>{"Select intent:"}</p> <p>{"Select intent:"}</p>
<HtmlSelect<Option<Intent>> <HtmlSelect<Option<Intent>>
options={vec![ options={[
(None, "None".to_string()), (None, "None".into()),
(Some(Intent::Primary), "Primary".to_string()), (Some(Intent::Primary), "Primary".into()),
(Some(Intent::Success), "Success".to_string()), (Some(Intent::Success), "Success".into()),
(Some(Intent::Warning), "Warning".to_string()), (Some(Intent::Warning), "Warning".into()),
(Some(Intent::Danger), "Danger".to_string()), (Some(Intent::Danger), "Danger".into()),
]} ].into_iter().collect::<IArray<_>>()}
value={self.example_props.intent} value={self.example_props.intent}
onchange={self.update_props(ctx, |props, intent| ExampleProps { onchange={self.update_props(ctx, |props, intent| ExampleProps {
intent, intent,

View file

@ -1,3 +1,4 @@
use implicit_clone::{unsync::IArray, ImplicitClone};
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Label, Radio, RadioGroup}; use yewprint::{Label, Radio, RadioGroup};
@ -61,11 +62,11 @@ impl Component for Example {
{"Determine Lunch"} {"Determine Lunch"}
</Label> </Label>
))} ))}
options={vec![ options={[
(Lunch::Soup, "Soup".to_string()), (Lunch::Soup, "Soup".into()),
(Lunch::Salad, "Salad".to_string()), (Lunch::Salad, "Salad".into()),
(Lunch::Sandwich, "Sandwich".to_string()), (Lunch::Sandwich, "Sandwich".into()),
]} ].into_iter().collect::<IArray<_>>()}
value={self.selected_value} value={self.selected_value}
onchange={ctx.link().callback(|v| Msg::ValueUpdate(v))} onchange={ctx.link().callback(|v| Msg::ValueUpdate(v))}
inline={ctx.props().inline} inline={ctx.props().inline}
@ -84,3 +85,5 @@ pub enum Lunch {
Salad, Salad,
Sandwich, Sandwich,
} }
impl ImplicitClone for Lunch {}

View file

@ -1,4 +1,4 @@
use std::borrow::Cow; use implicit_clone::{unsync::IArray, ImplicitClone};
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Intent, Slider, Tag}; use yewprint::{Intent, Slider, Tag};
@ -53,7 +53,7 @@ impl Component for Example {
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())))
.collect::<Vec<_>>(); .collect::<IArray<_>>();
html! { html! {
<> <>
@ -62,7 +62,7 @@ impl Component for Example {
> >
<Slider<f64> <Slider<f64>
selected={self.float} selected={self.float}
values={vec![ values={[
(0.0, Some("0".into())), (0.0, Some("0".into())),
(0.1, None), (0.1, None),
(0.2, None), (0.2, None),
@ -74,7 +74,7 @@ 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())),
]} ].into_iter().collect::<IArray<_>>()}
intent={ctx.props().intent} intent={ctx.props().intent}
onchange={ctx.link().callback(|x| Msg::FloatUpdate(x))} onchange={ctx.link().callback(|x| Msg::FloatUpdate(x))}
/> />
@ -90,24 +90,26 @@ impl Component for Example {
values={percentage_labels} values={percentage_labels}
selected={self.integer} selected={self.integer}
intent={ctx.props().intent} intent={ctx.props().intent}
value_label={Cow::Owned(format!("{}%", self.integer))} value_label={format!("{}%", self.integer)}
onchange={ctx.link().callback(|x| Msg::IntegerUpdate(x))} onchange={ctx.link().callback(|x| Msg::IntegerUpdate(x))}
/> />
<Slider<LogLevel> <Slider<LogLevel>
values={vec![ values={[
(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())),
]} ].into_iter().collect::<IArray<_>>()}
intent={ctx.props().intent} intent={ctx.props().intent}
selected={self.log_level} selected={self.log_level}
onchange={ctx.link().callback(|x| Msg::LogLevelUpdate(x))} onchange={ctx.link().callback(|x| Msg::LogLevelUpdate(x))}
/> />
<Slider<()> <Slider<()>
values={vec![((), Some("Neo".into()))]} values={[
((), Some("Neo".into()))
].into_iter().collect::<IArray<_>>()}
intent={ctx.props().intent} intent={ctx.props().intent}
selected={()} selected={()}
onchange={ctx.link().callback(|_| Msg::Noop)} onchange={ctx.link().callback(|_| Msg::Noop)}
@ -126,3 +128,5 @@ pub enum LogLevel {
Error, Error,
Off, Off,
} }
impl ImplicitClone for LogLevel {}

View file

@ -2,6 +2,7 @@ mod example;
use crate::ExampleContainer; use crate::ExampleContainer;
use example::*; use example::*;
use implicit_clone::unsync::IArray;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{HtmlSelect, Intent, Switch, H1, H2, H5}; use yewprint::{HtmlSelect, Intent, Switch, H1, H2, H5};
@ -119,17 +120,18 @@ crate::build_example_prop_component! {
/> />
<p>{"Select intent:"}</p> <p>{"Select intent:"}</p>
<HtmlSelect<Option<Intent>> <HtmlSelect<Option<Intent>>
options={vec![ options={[
(None, "None".to_string()), (None, "None".into()),
(Some(Intent::Primary), "Primary".to_string()), (Some(Intent::Primary), "Primary".into()),
(Some(Intent::Success), "Success".to_string()), (Some(Intent::Success), "Success".into()),
(Some(Intent::Warning), "Warning".to_string()), (Some(Intent::Warning), "Warning".into()),
(Some(Intent::Danger), "Danger".to_string()), (Some(Intent::Danger), "Danger".into()),
]} ].into_iter().collect::<IArray<_>>()}
onchange={self.update_props(ctx, |props, intent| ExampleProps { onchange={self.update_props(ctx, |props, intent| ExampleProps {
intent, intent,
..props ..props
})} })}
value={ctx.props().example_props.intent}
/> />
</div> </div>
} }

View file

@ -2,6 +2,7 @@ mod example;
use crate::ExampleContainer; use crate::ExampleContainer;
use example::*; use example::*;
use implicit_clone::unsync::IArray;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{HtmlSelect, Intent, Slider, H1, H5}; use yewprint::{HtmlSelect, Intent, Slider, H1, H5};
@ -67,17 +68,18 @@ crate::build_example_prop_component! {
<div> <div>
<p>{"Select intent:"}</p> <p>{"Select intent:"}</p>
<HtmlSelect<Option<Intent>> <HtmlSelect<Option<Intent>>
options={vec![ options={[
(None, "None".to_string()), (None, "None".into()),
(Some(Intent::Primary), "Primary".to_string()), (Some(Intent::Primary), "Primary".into()),
(Some(Intent::Success), "Success".to_string()), (Some(Intent::Success), "Success".into()),
(Some(Intent::Warning), "Warning".to_string()), (Some(Intent::Warning), "Warning".into()),
(Some(Intent::Danger), "Danger".to_string()), (Some(Intent::Danger), "Danger".into()),
]} ].into_iter().collect::<IArray<_>>()}
onchange={self.update_props(ctx, |props, intent| ExampleProps { onchange={self.update_props(ctx, |props, intent| ExampleProps {
intent, intent,
..props ..props
})} })}
value={ctx.props().example_props.intent}
/> />
<p <p
style="margin-top: 5px;" style="margin-top: 5px;"
@ -86,7 +88,7 @@ crate::build_example_prop_component! {
</p> </p>
<Slider<u32> <Slider<u32>
selected={ctx.props().example_props.size} selected={ctx.props().example_props.size}
values={vec![ values={[
(10, Some("10".into())), (10, Some("10".into())),
(20, None), (20, None),
(30, None), (30, None),
@ -97,7 +99,7 @@ crate::build_example_prop_component! {
(80, None), (80, None),
(90, None), (90, None),
(100, Some("100".into())), (100, Some("100".into())),
]} ].into_iter().collect::<IArray<_>>()}
onchange={self.update_props(ctx, |props, size| ExampleProps { onchange={self.update_props(ctx, |props, size| ExampleProps {
size, size,
..props ..props

View file

@ -1,3 +1,4 @@
use implicit_clone::{unsync::IArray, ImplicitClone};
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Tab, Tabs}; use yewprint::{Tab, Tabs};
@ -39,7 +40,7 @@ impl Component for Example {
vertical={ctx.props().vertical} vertical={ctx.props().vertical}
selected_tab_id={self.selected} selected_tab_id={self.selected}
onchange={ctx.link().callback(|x| x)} onchange={ctx.link().callback(|x| x)}
tabs={vec![ tabs={[
Tab { Tab {
disabled: false, disabled: false,
id: Civilization::Sumer, id: Civilization::Sumer,
@ -120,7 +121,7 @@ impl Component for Example {
panel_class: Classes::default(), panel_class: Classes::default(),
title_class: Classes::default(), title_class: Classes::default(),
}, },
]} ].into_iter().collect::<IArray<_>>()}
/> />
</div> </div>
} }
@ -134,3 +135,5 @@ pub enum Civilization {
AncientEgypt, AncientEgypt,
IndusValley, IndusValley,
} }
impl ImplicitClone for Civilization {}

View file

@ -1,3 +1,4 @@
use implicit_clone::unsync::IArray;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{IconName, Intent, Tag}; use yewprint::{IconName, Intent, Tag};
@ -7,7 +8,7 @@ pub struct Example {
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub initial_tags: Vec<String>, pub initial_tags: IArray<AttrValue>,
pub active: bool, pub active: bool,
pub fill: bool, pub fill: bool,
pub icon: bool, pub icon: bool,
@ -32,7 +33,12 @@ impl Component for Example {
type Properties = ExampleProps; type Properties = ExampleProps;
fn create(ctx: &Context<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
let tags = ctx.props().initial_tags.clone(); let tags = ctx
.props()
.initial_tags
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>();
Example { tags } Example { tags }
} }
@ -51,9 +57,14 @@ impl Component for Example {
true true
} }
fn changed(&mut self, ctx: &Context<Self>) -> bool { fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
if ctx.props().reset_tags != ctx.props().reset_tags { if ctx.props().reset_tags != ctx.props().reset_tags {
self.tags = ctx.props().initial_tags.clone(); self.tags = ctx
.props()
.initial_tags
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>();
} }
true true
} }

View file

@ -2,6 +2,7 @@ mod example;
use crate::ExampleContainer; use crate::ExampleContainer;
use example::*; use example::*;
use implicit_clone::unsync::{IArray, IString};
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, ButtonGroup, HtmlSelect, IconName, Intent, Switch, H1, H5}; use yewprint::{Button, ButtonGroup, HtmlSelect, IconName, Intent, Switch, H1, H5};
@ -10,8 +11,8 @@ pub struct TagDoc {
state: ExampleProps, state: ExampleProps,
} }
fn initial_tags() -> Vec<String> { fn initial_tags() -> IArray<AttrValue> {
vec![ [
"Landscape".into(), "Landscape".into(),
"Bird".into(), "Bird".into(),
"City".into(), "City".into(),
@ -22,6 +23,8 @@ fn initial_tags() -> Vec<String> {
too. Coming back to where you started is not the same as never leaving." too. Coming back to where you started is not the same as never leaving."
.into(), .into(),
] ]
.into_iter()
.collect::<IArray<_>>()
} }
impl Component for TagDoc { impl Component for TagDoc {
@ -173,17 +176,18 @@ crate::build_example_prop_component! {
vertical=true vertical=true
> >
<HtmlSelect<Option<Intent>> <HtmlSelect<Option<Intent>>
options={vec![ options={IArray::Static(&[
(None, "None".to_string()), (None, IString::Static("None")),
(Some(Intent::Primary), "Primary".to_string()), (Some(Intent::Primary), IString::Static("Primary")),
(Some(Intent::Success), "Success".to_string()), (Some(Intent::Success), IString::Static("Success")),
(Some(Intent::Warning), "Warning".to_string()), (Some(Intent::Warning), IString::Static("Warning")),
(Some(Intent::Danger), "Danger".to_string()), (Some(Intent::Danger), IString::Static("Danger")),
]} ])}
onchange={self.update_props(ctx, |props, intent| ExampleProps { onchange={self.update_props(ctx, |props, intent| ExampleProps {
intent, intent,
..props ..props
})} })}
value={ctx.props().example_props.intent}
/> />
<Button <Button
icon={IconName::Refresh} icon={IconName::Refresh}

View file

@ -4,7 +4,7 @@ use yewprint::Text;
#[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: AttrValue,
} }
#[function_component(Example)] #[function_component(Example)]

View file

@ -20,7 +20,7 @@ impl Component for TextDoc {
callback: ctx.link().callback(|x| x), callback: ctx.link().callback(|x| x),
state: ExampleProps { state: ExampleProps {
ellipsize: false, ellipsize: false,
text: String::from("Hello, world!"), text: "Hello, world!".into(),
}, },
} }
} }
@ -78,12 +78,12 @@ crate::build_example_prop_component! {
onchange={self.update_props(ctx, |props, e: Event| { onchange={self.update_props(ctx, |props, e: Event| {
if let Some(input) = e.target_dyn_into::<HtmlInputElement>() { if let Some(input) = e.target_dyn_into::<HtmlInputElement>() {
ExampleProps { ExampleProps {
text: input.value(), text: input.value().into(),
..props ..props
} }
} else { } else {
ExampleProps { ExampleProps {
text: "Hello, world!".to_string(), text: "Hello, world!".into(),
..props ..props
} }
} }

View file

@ -2,6 +2,7 @@ mod example;
use crate::ExampleContainer; use crate::ExampleContainer;
use example::*; use example::*;
use implicit_clone::unsync::IArray;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{HtmlSelect, Intent, Switch, H1, H5}; use yewprint::{HtmlSelect, Intent, Switch, H1, H5};
@ -91,17 +92,18 @@ crate::build_example_prop_component! {
label={html!("Small")} label={html!("Small")}
/> />
<HtmlSelect<Option<Intent>> <HtmlSelect<Option<Intent>>
options={vec![ options={[
(None, "None".to_string()), (None, "None".into()),
(Some(Intent::Primary), "Primary".to_string()), (Some(Intent::Primary), "Primary".into()),
(Some(Intent::Success), "Success".to_string()), (Some(Intent::Success), "Success".into()),
(Some(Intent::Warning), "Warning".to_string()), (Some(Intent::Warning), "Warning".into()),
(Some(Intent::Danger), "Danger".to_string()), (Some(Intent::Danger), "Danger".into()),
]} ].into_iter().collect::<IArray<_>>()}
onchange={self.update_props(ctx, |props, intent| ExampleProps { onchange={self.update_props(ctx, |props, intent| ExampleProps {
intent, intent,
..props ..props
})} })}
value={ctx.props().example_props.intent}
/> />
</div> </div>
} }