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"]
[dependencies]
gloo = "0.6"
id_tree = { version = "1.7", optional = true }
gloo = "0.8"
id_tree = { version = "1.8", optional = true }
implicit-clone = "0.3.5"
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["DomRect", "Element", "Event", "HtmlSelectElement"] }
yew = "0.19"
yew = "0.20"
[build-dependencies]
heck = "0.3"
heck = "0.4"
regex = { version = "1", default-features = false, features = ["std", "unicode-perl"] }
[workspace]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,7 +19,7 @@ pub struct CollapseProps {
#[prop_or_default]
pub is_open: bool,
#[prop_or_default]
pub children: html::Children,
pub children: Children,
#[prop_or_default]
pub keep_children_mounted: bool,
#[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 {
match self.animation_state {
AnimationState::Open | AnimationState::Opening => {}

View file

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

View file

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

View file

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

View file

@ -1,3 +1,4 @@
use implicit_clone::{unsync::IArray, ImplicitClone};
use std::marker::PhantomData;
use crate::{Icon, IconName};
@ -11,7 +12,7 @@ pub struct HtmlSelect<T: Clone + PartialEq + 'static> {
}
#[derive(Debug, Clone, PartialEq, Properties)]
pub struct HtmlSelectProps<T: Clone + PartialEq + 'static> {
pub struct HtmlSelectProps<T: ImplicitClone + PartialEq + 'static> {
#[prop_or_default]
pub fill: bool,
#[prop_or_default]
@ -21,19 +22,17 @@ pub struct HtmlSelectProps<T: Clone + PartialEq + 'static> {
#[prop_or_default]
pub disabled: bool,
#[prop_or_default]
pub icon: Option<IconName>,
#[prop_or_default]
pub title: Option<String>,
pub title: Option<AttrValue>,
#[prop_or_default]
pub onchange: Callback<T>,
#[prop_or_default]
pub value: Option<T>,
pub options: Vec<(T, String)>,
pub options: IArray<(T, AttrValue)>,
#[prop_or_default]
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 Properties = HtmlSelectProps<T>;
@ -58,10 +57,10 @@ impl<T: Clone + PartialEq + 'static> Component for HtmlSelect<T> {
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(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 select.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 {
let option_children = ctx
.props()
.options
let Self::Properties {
fill,
minimal,
large,
disabled,
title,
onchange: _,
value,
options,
class,
} = &ctx.props();
let option_children = options
.iter()
.map(|(value, label)| {
let selected = ctx
.props()
.value
.as_ref()
.map(|x| value == x)
.unwrap_or_default();
.map(|(this_value, label)| {
let selected = value.as_ref().map(|x| &this_value == x).unwrap_or_default();
html! {
<option selected={selected}>
<option {selected}>
{label}
</option>
}
@ -98,18 +102,18 @@ impl<T: Clone + PartialEq + 'static> Component for HtmlSelect<T> {
<div
class={classes!(
"bp3-html-select",
ctx.props().minimal.then_some("bp3-minimal"),
ctx.props().large.then_some("bp3-large"),
ctx.props().fill.then_some("bp3-fill"),
ctx.props().disabled.then_some("bp3-disabled"),
ctx.props().class.clone(),
minimal.then_some("bp3-minimal"),
large.then_some("bp3-large"),
fill.then_some("bp3-fill"),
disabled.then_some("bp3-disabled"),
class.clone(),
)}
>
<select
disabled={ctx.props().disabled}
value={String::new()}
disabled={*disabled}
onchange={ctx.link().callback(|x| x)}
title={ctx.props().title.clone()}
value={"".to_string()}
{title}
ref={self.select_element.clone()}
>
{option_children}

View file

@ -1,4 +1,5 @@
use crate::Intent;
use implicit_clone::ImplicitClone;
use yew::prelude::*;
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)]
pub struct IconProps {
pub icon: IconName,
#[prop_or_default]
pub class: Classes,
#[prop_or_default]
pub title: Option<String>,
pub title: Option<AttrValue>,
#[prop_or_default]
pub color: Option<String>,
pub color: Option<AttrValue>,
#[prop_or_default]
pub intent: Option<Intent>,
#[prop_or(16)]
@ -30,34 +33,47 @@ pub struct IconProps {
}
#[function_component(Icon)]
pub fn icon(props: &IconProps) -> Html {
let paths = if props.icon_size == ICON_SIZE_STANDARD {
icon_svg_paths_16(props.icon)
pub fn icon(
IconProps {
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 {
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
} else {
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! {
<span
class={classes!("bp3-icon", props.class.clone(), props.intent)}
onclick={props.onclick.clone()}
class={classes!("bp3-icon", class.clone(), intent)}
{onclick}
>
<svg
fill={props.color.clone()}
data-icon={icon_string.clone()}
width={props.icon_size.to_string()}
height={props.icon_size.to_string()}
viewBox={format!("0 0 {x} {x}", x=pixel_grid_size)}
{fill}
data-icon={&icon_string}
{width}
{height}
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! {
<path d={*x} fillRule="evenodd" />
})

View file

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

View file

@ -64,6 +64,7 @@ pub use text_area::*;
#[cfg(feature = "tree")]
pub use tree::*;
use implicit_clone::ImplicitClone;
use yew::Classes;
// See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
@ -87,6 +88,8 @@ pub enum Intent {
Danger,
}
impl ImplicitClone for Intent {}
impl From<Intent> for Classes {
fn from(intent: Intent) -> Self {
use Intent::*;
@ -146,14 +149,16 @@ impl Elevation {
impl Default for Elevation {
fn default() -> Self {
Elevation::Level0
Self::Level0
}
}
impl ImplicitClone for Elevation {}
impl From<Elevation> for Classes {
fn from(elevation: Elevation) -> Self {
use Elevation::*;
Classes::from(match elevation {
Self::from(match elevation {
Level0 => "bp3-elevation-0",
Level1 => "bp3-elevation-1",
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,
#[prop_or_default]
pub r#ref: NodeRef,
pub children: html::Children,
pub children: Children,
}
#[function_component(Menu)]
@ -32,7 +32,7 @@ pub fn menu(props: &MenuProps) -> Html {
#[derive(Clone, PartialEq, Properties)]
pub struct MenuItemProps {
#[prop_or_default]
pub text: yew::virtual_dom::VNode,
pub text: Html,
#[prop_or_default]
pub text_class: Classes,
#[prop_or_default]
@ -44,7 +44,7 @@ pub struct MenuItemProps {
#[prop_or_default]
pub href: Option<AttrValue>,
#[prop_or_default]
pub label: Option<yew::virtual_dom::VNode>,
pub label: Option<Html>,
#[prop_or_default]
pub label_class: Classes,
// TODO: pub multiline: bool, (requires <Text>)
@ -57,32 +57,47 @@ pub struct MenuItemProps {
pub icon_html: Option<Html>,
#[prop_or_default]
pub onclick: Callback<MouseEvent>,
// TODO: pub children: html::Children,
// TODO: pub children: Children,
}
#[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! {
<li>
<a
class={classes!(
"bp3-menu-item",
props.active.then_some("bp3-active"),
props.disabled.then_some("bp3-disabled"),
props.intent
.or_else(|| props.active.then_some(Intent::Primary)),
props.class.clone(),
active.then_some("bp3-active"),
disabled.then_some("bp3-disabled"),
intent
.or_else(|| active.then_some(Intent::Primary)),
class.clone(),
)}
href={(!props.disabled).then(|| props.href.clone()).flatten()}
tabIndex={(!props.disabled).then_some("0")}
onclick={(!props.disabled).then(|| props.onclick.clone())}
href={(!disabled).then(|| href.clone()).flatten()}
tabIndex={(!disabled).then_some("0")}
onclick={(!disabled).then(|| onclick.clone())}
>
{
if let Some(icon_name) = props.icon {
if let Some(icon_name) = icon {
html! {
<Icon icon={icon_name} />
}
} else if let Some(html) = props.icon_html.clone() {
} else if let Some(html) = icon_html.clone() {
html
} else {
html! {
@ -90,16 +105,16 @@ pub fn menu_item(props: &MenuItemProps) -> Html {
}
}
}
<div class={classes!("bp3-text", "bp3-fill", props.text_class.clone())}>
{props.text.clone()}
<div class={classes!("bp3-text", "bp3-fill", text_class.clone())}>
{text.clone()}
</div>
{
if let Some(label) = props.label.clone() {
if let Some(label) = label.clone() {
html! {
<span
class={classes!(
"bp3-menu-item-label",
props.label_class.clone())}
label_class.clone())}
>
{label}
</span>
@ -117,7 +132,7 @@ pub fn menu_item(props: &MenuItemProps) -> Html {
#[derive(Clone, PartialEq, Properties)]
pub struct MenuDividerProps {
#[prop_or_default]
pub title: Option<yew::virtual_dom::VNode>,
pub title: Option<Html>,
}
#[function_component(MenuDivider)]

View file

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

View file

@ -1,5 +1,6 @@
use crate::{Button, IconName};
use gloo::timers::callback::Timeout;
use implicit_clone::ImplicitClone;
use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;
@ -42,6 +43,8 @@ pub struct PanelStackState {
action: Option<StateAction>,
}
impl ImplicitClone for PanelStackState {}
impl PanelStackState {
pub fn new(content: Html) -> PanelBuilder<fn(Option<Html>, Html) -> Self, Html, Self> {
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;
true
}
@ -293,8 +296,8 @@ impl Component for Panel {
html! {
<div class={classes} style={style}>
<div class="bp3-panel-stack-header">
<span>{back_button.unwrap_or_default()}</span>
{ctx.props().title.clone().unwrap_or_default()}
<span>{back_button}</span>
{ctx.props().title.clone()}
<span/>
</div>
{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;
true
}

View file

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

View file

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

View file

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

View file

@ -1,12 +1,12 @@
use crate::Intent;
use std::borrow::Cow;
use implicit_clone::{unsync::IArray, ImplicitClone};
use std::marker::PhantomData;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
use web_sys::Element;
use yew::prelude::*;
pub struct Slider<T: Clone + PartialEq + 'static> {
pub struct Slider<T: ImplicitClone + PartialEq + 'static> {
mouse_move: Closure<dyn FnMut(MouseEvent)>,
mouse_up: Closure<dyn FnMut(MouseEvent)>,
handle_ref: NodeRef,
@ -17,7 +17,7 @@ pub struct Slider<T: Clone + PartialEq + 'static> {
}
#[derive(Clone, PartialEq, Properties)]
pub struct SliderProps<T: Clone + PartialEq + 'static> {
pub struct SliderProps<T: ImplicitClone + PartialEq + 'static> {
#[prop_or_default]
pub class: Classes,
#[prop_or_default]
@ -25,9 +25,9 @@ pub struct SliderProps<T: Clone + PartialEq + 'static> {
#[prop_or_default]
pub intent: Option<Intent>,
#[prop_or_default]
pub value_label: Option<Cow<'static, str>>,
pub value_label: Option<AttrValue>,
pub onchange: Callback<T>,
pub values: Vec<(T, Option<Cow<'static, str>>)>,
pub values: IArray<(T, Option<AttrValue>)>,
pub selected: Option<T>,
}
@ -38,7 +38,7 @@ pub enum Msg {
Keyboard(KeyboardEvent),
}
impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
impl<T: ImplicitClone + PartialEq + 'static> Component for Slider<T> {
type Message = Msg;
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 (value, _) =
ctx.props().values.get(position).unwrap_or_else(|| {
ctx.props().values.last().expect("No value in the vec")
let (value, _) = ctx.props().values.get(position).unwrap_or_else(|| {
ctx.props()
.values
.last()
.cloned()
.expect("No value in the array")
});
if Some(value) != ctx.props().selected.as_ref() {
ctx.props().onchange.emit(value.clone());
if Some(&value) != ctx.props().selected.as_ref() {
ctx.props().onchange.emit(value);
}
true
@ -139,7 +142,7 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
.props()
.values
.iter()
.position(|(value, _)| Some(value) == ctx.props().selected.as_ref())
.position(|(value, _)| Some(value) == ctx.props().selected)
.map(|i| i.saturating_sub(1))
.unwrap_or(0);
let (value, _) = ctx.props().values[index].clone();
@ -153,20 +156,16 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
.props()
.values
.iter()
.position(|(value, _)| Some(value) == ctx.props().selected.as_ref())
.position(|(value, _)| Some(value) == ctx.props().selected)
.map(|i| i.saturating_add(1))
.unwrap_or(0);
let (value, _) = ctx
.props()
.values
.get(index)
.unwrap_or_else(|| {
ctx.props().values.last().expect(
let (value, _) = ctx.props().values.get(index).unwrap_or_else(|| {
let (value, label) = ctx.props().values.last().expect(
"Already check, \
there are at least 2 values in ctx.props().options; qed",
)
})
.clone();
);
(value.clone(), label.clone())
});
ctx.props().onchange.emit(value);
true
}
@ -181,14 +180,14 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
.props()
.values
.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 {
ctx.props()
.values
.iter()
.enumerate()
.filter_map(|(i, (_, label))| {
label.clone().map(|x| {
label.map(|x| {
html! {
<div
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))}
tabindex=0
>
{value_label.clone().unwrap_or_default()}
{value_label.clone()}
</span>
}
}
@ -334,7 +333,7 @@ impl<T: Clone + PartialEq + 'static> Component for Slider<T> {
ref={self.handle_ref.clone()}
style="left: calc(50% - 8px);"
>
{value_label.clone().unwrap_or_default()}
{value_label.clone()}
</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,
)
};
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}",
R = R,
R2 = R * 2.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! {
<div
@ -54,18 +55,18 @@ pub fn spinner(props: &SpinnerProps) -> Html {
class={classes!("bp3-spinner-animation")}
>
<svg
width={size.to_string()}
height={size.to_string()}
{width}
{height}
stroke-width={stroke_width.to_string()}
viewBox={view_box}
>
<path
class={classes!("bp3-spinner-track")}
d={spinner_track.clone()}
d={&spinner_track}
/>
<path
class={classes!("bp3-spinner-head")}
d={spinner_track}
d={&spinner_track}
pathLength={PATH_LENGTH.to_string()}
stroke-dasharray={format!("{} {}", PATH_LENGTH, PATH_LENGTH)}
stroke-dashoffset={stroke_offset.to_string()}

View file

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

View file

@ -1,22 +1,23 @@
use implicit_clone::{unsync::IArray, ImplicitClone};
use std::collections::{hash_map::DefaultHasher, HashMap};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use web_sys::HtmlElement;
use yew::prelude::*;
pub struct Tabs<T: Clone + PartialEq + Hash + 'static> {
pub struct Tabs<T: ImplicitClone + PartialEq + Hash + 'static> {
tab_refs: HashMap<u64, NodeRef>,
indicator_ref: NodeRef,
phantom: PhantomData<T>,
}
#[derive(Clone, PartialEq, Properties)]
pub struct TabsProps<T: Clone + PartialEq> {
pub struct TabsProps<T: ImplicitClone + PartialEq + 'static> {
#[prop_or_default]
pub animate: bool,
#[prop_or_default]
pub default_selected_tab_id: Option<T>,
pub id: String,
pub id: AttrValue,
#[prop_or_default]
pub large: bool,
#[prop_or_default]
@ -28,10 +29,10 @@ pub struct TabsProps<T: Clone + PartialEq> {
pub onchange: Callback<T>,
#[prop_or_default]
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 Properties = TabsProps<T>;
@ -194,3 +195,5 @@ pub struct Tab<T> {
pub title_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)]
pub struct TagProps {
#[prop_or_default]
pub children: html::Children,
pub children: Children,
#[prop_or_default]
// FIXME Not clear that this field has any effect without `interactive` on.
pub active: bool,
@ -40,25 +40,44 @@ pub struct TagProps {
}
#[function_component(Tag)]
pub fn tag(props: &TagProps) -> Html {
let icon = props.icon.map(|icon| {
pub fn tag(
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! {
<Icon icon={icon} />
<Icon {icon} />
}
});
let right_icon = props.right_icon.map(|icon| {
let right_icon = right_icon.map(|icon| {
html! {
<Icon icon={icon} />
<Icon {icon} />
}
});
let remove_button = props.onremove.clone().map(|callback| {
let remove_button = onremove.clone().map(|onclick| {
html! {
<button
class={classes!("bp3-tag-remove")}
onclick={callback}
tabindex={props.interactive.then_some("0")}
{onclick}
tabindex={interactive.then_some("0")}
>
<Icon icon={IconName::SmallCross} />
</button>
@ -69,29 +88,29 @@ pub fn tag(props: &TagProps) -> Html {
<span
class={classes!(
"bp3-tag",
props.intent,
props.active.then_some("bp3-active"),
props.fill.then_some("bp3-fill"),
props.interactive.then_some("bp3-interactive"),
props.large.then_some("bp3-large"),
props.minimal.then_some("bp3-minimal"),
props.round.then_some("bp3-round"),
props.class.clone(),
intent,
active.then_some("bp3-active"),
fill.then_some("bp3-fill"),
interactive.then_some("bp3-interactive"),
large.then_some("bp3-large"),
minimal.then_some("bp3-minimal"),
round.then_some("bp3-round"),
class.clone(),
)}
style={props.style.clone()}
onclick={props.onclick.clone()}
{style}
{onclick}
>
{icon.unwrap_or_default()}
{icon}
<Text
class={classes!("bp3-fill")}
ellipsize={!props.multiline}
title={props.title.clone()}
ellipsize={!multiline}
{title}
inline=true
>
{props.children.clone()}
{children.clone()}
</Text>
{right_icon.unwrap_or_default()}
{remove_button.unwrap_or_default()}
{right_icon}
{remove_button}
</span>
}
}

View file

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

View file

@ -7,10 +7,9 @@ pub struct TextAreaProps {
pub class: Classes,
#[prop_or_default]
pub fill: bool,
//TODO pub grow_vertically: bool,
#[prop_or_default]
pub grow_vertically: bool,
#[prop_or_default]
pub input_ref: NodeRef,
pub r#ref: NodeRef,
#[prop_or_default]
pub intent: Option<Intent>,
#[prop_or_default]
@ -18,23 +17,33 @@ pub struct TextAreaProps {
#[prop_or_default]
pub small: bool,
#[prop_or_default]
pub onchange: Option<Callback<Event>>,
pub onchange: Callback<Event>,
}
#[function_component(TextArea)]
pub fn text_area(props: &TextAreaProps) -> Html {
let classes = classes!(
"bp3-input",
props.intent,
props.class.clone(),
props.fill.then_some("bp3-fill"),
props.small.then_some("bp3-small"),
props.large.then_some("bp3-large"),
);
pub fn text_area(
TextAreaProps {
class,
fill,
r#ref,
intent,
large,
small,
onchange,
}: &TextAreaProps,
) -> Html {
html! {
<textarea
class={classes}
onchange={props.onchange.clone()}
class={classes!(
"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::icon::{Icon, IconName};
use crate::Intent;
use gloo::timers::callback::Timeout;
use id_tree::*;
use std::cell::{Ref, RefCell, RefMut};
use std::collections::hash_map::DefaultHasher;
@ -18,7 +19,7 @@ pub struct TreeData<T> {
impl<T> PartialEq for TreeData<T> {
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 has_caret: bool,
pub icon: Option<IconName>,
pub icon_color: Option<String>,
pub icon_color: Option<AttrValue>,
pub icon_intent: Option<Intent>,
pub is_expanded: bool,
pub is_selected: bool,
pub label: yew::virtual_dom::VNode,
pub secondary_label: Option<yew::virtual_dom::VNode>,
pub label: Html,
pub secondary_label: Option<Html>,
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.
impl<T: 'static + Clone + PartialEq> Tree<T> {
fn render_children(
&self,
ctx: &Context<Self>,
node_id: &NodeId,
depth: u32,
) -> yew::virtual_dom::VNode {
fn render_children(&self, ctx: &Context<Self>, node_id: &NodeId, depth: u32) -> Html {
let tree = ctx.props().tree.borrow();
let node = tree.get(node_id).unwrap();
let children = node.children();
@ -207,6 +203,8 @@ impl<T: 'static + Clone + PartialEq> Tree<T> {
struct TreeNode {
handler_caret_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)]
@ -215,16 +213,16 @@ struct TreeNodeProps {
disabled: bool,
has_caret: bool,
icon: Option<IconName>,
icon_color: Option<String>,
icon_color: Option<AttrValue>,
icon_intent: Option<Intent>,
is_expanded: bool,
is_selected: bool,
label: yew::virtual_dom::VNode,
secondary_label: Option<yew::virtual_dom::VNode>,
label: Html,
secondary_label: Option<Html>,
on_collapse: Option<Callback<(NodeId, MouseEvent)>>,
on_expand: Option<Callback<(NodeId, MouseEvent)>>,
onclick: Option<Callback<(NodeId, MouseEvent)>>,
children: html::Children,
children: yew::Children,
depth: u32,
}
@ -261,6 +259,7 @@ impl Component for TreeNode {
TreeNode {
handler_caret_click: ctx.link().callback(TreeNodeMessage::CaretClick),
handler_click: ctx.link().callback(TreeNodeMessage::Click),
callback_timeout: None,
}
}
@ -269,21 +268,20 @@ impl Component for TreeNode {
return false;
}
let node_id = ctx.props().node_id.clone();
match msg {
TreeNodeMessage::CaretClick(event) => {
if ctx.props().is_expanded {
if let Some(on_collapse) = ctx.props().on_collapse.as_ref() {
event.stop_propagation();
on_collapse.emit((ctx.props().node_id.clone(), event));
if let Some(on_collapse) = ctx.props().on_collapse.clone() {
self.register_callback(on_collapse, (node_id, event));
}
} else if let Some(on_expand) = ctx.props().on_expand.as_ref() {
event.stop_propagation();
on_expand.emit((ctx.props().node_id.clone(), event));
} else if let Some(on_expand) = ctx.props().on_expand.clone() {
self.register_callback(on_expand, (node_id, event));
}
}
TreeNodeMessage::Click(event) => {
if let Some(onclick) = ctx.props().onclick.as_ref() {
onclick.emit((ctx.props().node_id.clone(), event));
if let Some(onclick) = ctx.props().onclick.clone() {
self.register_callback(onclick, (node_id, event));
}
}
}
@ -306,16 +304,16 @@ impl Component for TreeNode {
>
{
if ctx.props().has_caret {
let mut class = Classes::from("bp3-tree-node-caret");
class.push(if ctx.props().is_expanded {
html! {
<Icon
class={classes!(
"bp3-tree-node-caret",
if ctx.props().is_expanded {
"bp3-tree-node-caret-open"
} else {
"bp3-tree-node-caret-closed"
});
html! {
<Icon
class={classes!(class.to_string())}
},
)}
icon={IconName::ChevronRight}
onclick={self.handler_caret_click.clone()}
/>
@ -353,4 +351,17 @@ impl Component for TreeNode {
</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
[dependencies]
env_logger = "0.9"
env_logger = "0.10"
log = "0.4"
xtask-wasm = "0.1.3"
xtask-wasm = "0.1.9"
yewprint-css = { path = "../yewprint-css" }

View file

@ -19,4 +19,4 @@ flate2 = "1"
log = "0.4"
serde = { version = "1", features = ["derive"] }
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
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.6", optional = true }
gloo = "0.6"
console_error_panic_hook = { version = "0.1.7", optional = true }
gloo = "0.8"
implicit-clone = "0.3.3"
once_cell = "1.16"
wasm-bindgen = "0.2"
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.
wee_alloc = { version = "0.4.5", optional = true }
yew = "0.19"
yew-router = "0.16"
yew = { version = "0.20", features = ["csr"] }
yew-router = "0.17"
yewprint = { path = ".." }
[build-dependencies]
build-data = "0.1.3"
syntect = "4.4.0"
syntect = "0.5.0"
[dev-dependencies]
ureq = "2.4"
ureq = "2.5"

View file

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

View file

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

View file

@ -2,8 +2,9 @@ mod example;
use crate::ExampleContainer;
use example::*;
use implicit_clone::unsync::IArray;
use yew::prelude::*;
use yewprint::{Switch, H1, H5};
use yewprint::{HtmlSelect, Intent, Switch, H1, H5};
pub struct ButtonDoc {
callback: Callback<ExampleProps>,
@ -26,6 +27,7 @@ impl Component for ButtonDoc {
large: false,
active: false,
disabled: false,
intent: None,
},
}
}
@ -136,6 +138,20 @@ crate::build_example_prop_component! {
checked={ctx.props().example_props.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>
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -42,7 +42,7 @@ pub use logo::*;
#[macro_export]
macro_rules! include_raw_html {
($file:expr $(, $class:expr)?) => {{
yew::virtual_dom::VNode::VRef(web_sys::Node::from({
Html::VRef(web_sys::Node::from({
let div = web_sys::window()
.unwrap()
.document()
@ -136,7 +136,8 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
pub fn run_app() -> Result<(), wasm_bindgen::JsValue> {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
yew::start_app::<app::AppRoot>();
yew::Renderer::<app::AppRoot>::new().render();
Ok(())
}

View file

@ -14,6 +14,7 @@ pub struct ExampleProps {
pub disable_buttons: bool,
pub buttons_on_the_left: bool,
pub left_icon: bool,
pub intent: Option<Intent>,
}
pub enum Msg {
@ -50,32 +51,45 @@ impl Component for Example {
}
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! {
<>
<NumericInput<i32>
disabled={ctx.props().disabled}
fill={ctx.props().large}
{disabled}
{fill}
{large}
value={self.value}
bounds={-105..}
increment=10
placeholder={String::from("Greater or equal to -105...")}
onchange={ctx.link().callback(|x| Msg::UpdateValue(x))}
disable_buttons={ctx.props().disable_buttons}
buttons_on_the_left={ctx.props().buttons_on_the_left}
left_icon={ctx.props().left_icon.then_some(IconName::Dollar)}
{disable_buttons}
{buttons_on_the_left}
left_icon={left_icon.then_some(IconName::Dollar)}
{intent}
/>
<NumericInput<i32>
disabled={ctx.props().disabled}
fill={ctx.props().fill}
large={ctx.props().large}
{disabled}
{fill}
{large}
value={self.value_two}
bounds={-10..=10}
increment=1
placeholder={String::from("Integer between -10 and 10")}
onchange={ctx.link().callback(|x| Msg::UpdateValueTwo(x))}
disable_buttons={ctx.props().disable_buttons}
buttons_on_the_left={ctx.props().buttons_on_the_left}
left_icon={ctx.props().left_icon.then_some(IconName::Dollar)}
{disable_buttons}
{buttons_on_the_left}
left_icon={left_icon.then_some(IconName::Dollar)}
{intent}
/>
<Button
icon={IconName::Refresh}
@ -84,8 +98,8 @@ impl Component for Example {
{"Reset at 4"}
</Button>
<Callout
title={"Selected values"}
intent={Intent::Primary}
title="Selected values"
{intent}
>
<ul>
<li>{self.value}</li>

View file

@ -2,8 +2,9 @@ mod example;
use crate::ExampleContainer;
use example::*;
use implicit_clone::unsync::IArray;
use yew::prelude::*;
use yewprint::{Switch, H1, H5};
use yewprint::{HtmlSelect, Intent, Switch, H1, H5};
pub struct NumericInputDoc {
callback: Callback<ExampleProps>,
@ -24,6 +25,7 @@ impl Component for NumericInputDoc {
disable_buttons: false,
buttons_on_the_left: false,
left_icon: false,
intent: None,
},
}
}
@ -116,6 +118,20 @@ crate::build_example_prop_component! {
checked={ctx.props().example_props.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>
}
}

View file

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

View file

@ -1,3 +1,4 @@
use implicit_clone::{unsync::IArray, ImplicitClone};
use yew::prelude::*;
use yewprint::{Label, Radio, RadioGroup};
@ -61,11 +62,11 @@ impl Component for Example {
{"Determine Lunch"}
</Label>
))}
options={vec![
(Lunch::Soup, "Soup".to_string()),
(Lunch::Salad, "Salad".to_string()),
(Lunch::Sandwich, "Sandwich".to_string()),
]}
options={[
(Lunch::Soup, "Soup".into()),
(Lunch::Salad, "Salad".into()),
(Lunch::Sandwich, "Sandwich".into()),
].into_iter().collect::<IArray<_>>()}
value={self.selected_value}
onchange={ctx.link().callback(|v| Msg::ValueUpdate(v))}
inline={ctx.props().inline}
@ -84,3 +85,5 @@ pub enum Lunch {
Salad,
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 yewprint::{Intent, Slider, Tag};
@ -53,7 +53,7 @@ impl Component for Example {
let percentage_labels = (0..=100)
.step_by(1)
.map(|x| (x, (x % 10 == 0).then(|| format!("{}%", x).into())))
.collect::<Vec<_>>();
.collect::<IArray<_>>();
html! {
<>
@ -62,7 +62,7 @@ impl Component for Example {
>
<Slider<f64>
selected={self.float}
values={vec![
values={[
(0.0, Some("0".into())),
(0.1, None),
(0.2, None),
@ -74,7 +74,7 @@ impl Component for Example {
(0.8, None),
(0.9, None),
(1.0, Some("1".into())),
]}
].into_iter().collect::<IArray<_>>()}
intent={ctx.props().intent}
onchange={ctx.link().callback(|x| Msg::FloatUpdate(x))}
/>
@ -90,24 +90,26 @@ impl Component for Example {
values={percentage_labels}
selected={self.integer}
intent={ctx.props().intent}
value_label={Cow::Owned(format!("{}%", self.integer))}
value_label={format!("{}%", self.integer)}
onchange={ctx.link().callback(|x| Msg::IntegerUpdate(x))}
/>
<Slider<LogLevel>
values={vec![
values={[
(LogLevel::Off, Some("OFF".into())),
(LogLevel::Error, Some("ERROR".into())),
(LogLevel::Warn, Some("WARN".into())),
(LogLevel::Info, Some("INFO".into())),
(LogLevel::Debug, Some("DEBUG".into())),
(LogLevel::Trace, Some("TRACE".into())),
]}
].into_iter().collect::<IArray<_>>()}
intent={ctx.props().intent}
selected={self.log_level}
onchange={ctx.link().callback(|x| Msg::LogLevelUpdate(x))}
/>
<Slider<()>
values={vec![((), Some("Neo".into()))]}
values={[
((), Some("Neo".into()))
].into_iter().collect::<IArray<_>>()}
intent={ctx.props().intent}
selected={()}
onchange={ctx.link().callback(|_| Msg::Noop)}
@ -126,3 +128,5 @@ pub enum LogLevel {
Error,
Off,
}
impl ImplicitClone for LogLevel {}

View file

@ -2,6 +2,7 @@ mod example;
use crate::ExampleContainer;
use example::*;
use implicit_clone::unsync::IArray;
use yew::prelude::*;
use yewprint::{HtmlSelect, Intent, Switch, H1, H2, H5};
@ -119,17 +120,18 @@ crate::build_example_prop_component! {
/>
<p>{"Select intent:"}</p>
<HtmlSelect<Option<Intent>>
options={vec![
(None, "None".to_string()),
(Some(Intent::Primary), "Primary".to_string()),
(Some(Intent::Success), "Success".to_string()),
(Some(Intent::Warning), "Warning".to_string()),
(Some(Intent::Danger), "Danger".to_string()),
]}
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>
}

View file

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

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ use yewprint::Text;
#[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps {
pub ellipsize: bool,
pub text: String,
pub text: AttrValue,
}
#[function_component(Example)]

View file

@ -20,7 +20,7 @@ impl Component for TextDoc {
callback: ctx.link().callback(|x| x),
state: ExampleProps {
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| {
if let Some(input) = e.target_dyn_into::<HtmlInputElement>() {
ExampleProps {
text: input.value(),
text: input.value().into(),
..props
}
} else {
ExampleProps {
text: "Hello, world!".to_string(),
text: "Hello, world!".into(),
..props
}
}

View file

@ -2,6 +2,7 @@ mod example;
use crate::ExampleContainer;
use example::*;
use implicit_clone::unsync::IArray;
use yew::prelude::*;
use yewprint::{HtmlSelect, Intent, Switch, H1, H5};
@ -91,17 +92,18 @@ crate::build_example_prop_component! {
label={html!("Small")}
/>
<HtmlSelect<Option<Intent>>
options={vec![
(None, "None".to_string()),
(Some(Intent::Primary), "Primary".to_string()),
(Some(Intent::Success), "Success".to_string()),
(Some(Intent::Warning), "Warning".to_string()),
(Some(Intent::Danger), "Danger".to_string()),
]}
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>
}