Rewrite Icon API (#160)

This commit is contained in:
Cecile Tonglet 2022-12-14 17:42:43 +01:00 committed by GitHub
parent ebee832d39
commit b6a6004fcf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 1980 additions and 1880 deletions

1
Cargo.lock generated
View file

@ -2463,7 +2463,6 @@ dependencies = [
"console_error_panic_hook", "console_error_panic_hook",
"gloo", "gloo",
"implicit-clone", "implicit-clone",
"once_cell",
"syntect", "syntect",
"ureq", "ureq",
"wasm-bindgen", "wasm-bindgen",

View file

@ -1,4 +1,4 @@
use crate::{Icon, IconName, Intent, Spinner, ICON_SIZE_LARGE}; use crate::{Icon, IconSize, Intent, Spinner};
use yew::prelude::*; use yew::prelude::*;
use yew::virtual_dom::AttrValue; use yew::virtual_dom::AttrValue;
@ -21,7 +21,7 @@ pub struct ButtonProps {
#[prop_or_default] #[prop_or_default]
pub disabled: bool, pub disabled: bool,
#[prop_or_default] #[prop_or_default]
pub icon: Option<IconName>, pub icon: Option<Icon>,
#[prop_or_default] #[prop_or_default]
pub intent: Option<Intent>, pub intent: Option<Intent>,
#[prop_or_default] #[prop_or_default]
@ -80,11 +80,12 @@ pub fn button(props: &ButtonProps) -> Html {
.then(|| html! { .then(|| html! {
<Spinner <Spinner
class={classes!("bp3-button-spinner")} class={classes!("bp3-button-spinner")}
size={ICON_SIZE_LARGE as f32} size={IconSize::LARGE}
/> />
}) })
} }
{icon.map(|icon| html!(<Icon {icon} />))} <Icon {icon} />
//{icon.map(|icon| html!(<Icon {icon} />))}
{ {
(!children.is_empty()) (!children.is_empty())
.then(|| html! { .then(|| html! {

View file

@ -1,5 +1,5 @@
use crate::icon::ICON_SIZE_LARGE; use crate::icon::IconSize;
use crate::{Icon, IconName, Intent}; use crate::{Icon, Intent};
use yew::prelude::*; use yew::prelude::*;
use yew::virtual_dom::AttrValue; use yew::virtual_dom::AttrValue;
@ -10,7 +10,7 @@ pub struct CalloutProps {
#[prop_or(false)] #[prop_or(false)]
pub without_icon: bool, pub without_icon: bool,
#[prop_or_default] #[prop_or_default]
pub icon: Option<IconName>, pub icon: Option<Icon>,
#[prop_or_default] #[prop_or_default]
pub intent: Option<Intent>, pub intent: Option<Intent>,
#[prop_or_default] #[prop_or_default]
@ -31,14 +31,14 @@ pub fn callout(
) -> Html { ) -> Html {
let icon = if *without_icon { let icon = if *without_icon {
None None
} else if let Some(icon) = icon.clone() {
Some(icon)
} else { } else {
icon.or_else(|| { intent.map(|intent| match intent {
intent.map(|intent| match intent { Intent::Primary => Icon::InfoSign,
Intent::Primary => IconName::InfoSign, Intent::Success => Icon::Tick,
Intent::Success => IconName::Tick, Intent::Warning => Icon::WarningSign,
Intent::Warning => IconName::WarningSign, Intent::Danger => Icon::Error,
Intent::Danger => IconName::Error,
})
}) })
}; };
@ -46,16 +46,12 @@ pub fn callout(
<div <div
class={classes!( class={classes!(
"bp3-callout", "bp3-callout",
icon.map(|_| "bp3-callout-icon"), icon.is_some().then_some("bp3-callout-icon"),
intent, intent,
class.clone(), class.clone(),
)} )}
> >
{ <Icon {icon} size={IconSize::LARGE} />
icon.iter()
.map(|icon| html!{<Icon {icon} icon_size={ICON_SIZE_LARGE}/>})
.collect::<Html>()
}
{ {
title.iter() title.iter()
.map(|title| html!{<h4 class={"bp3-heading"}>{title}</h4>}) .map(|title| html!{<h4 class={"bp3-heading"}>{title}</h4>})

View file

@ -2,19 +2,16 @@ use yew::prelude::*;
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct DividerProps { pub struct DividerProps {
#[prop_or_default]
pub vertical: bool,
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
} }
#[function_component(Divider)] #[function_component(Divider)]
pub fn view(DividerProps { vertical, class }: &DividerProps) -> Html { pub fn view(DividerProps { class }: &DividerProps) -> Html {
html! { html! {
<span <div
class={classes!( class={classes!(
"bp3-divider", "bp3-divider",
vertical.then_some("bp3-vertical"),
class.clone(), class.clone(),
)} )}
/> />

View file

@ -1,7 +1,7 @@
use implicit_clone::{unsync::IArray, ImplicitClone}; use implicit_clone::{unsync::IArray, ImplicitClone};
use std::marker::PhantomData; use std::marker::PhantomData;
use crate::{Icon, IconName}; use crate::Icon;
use web_sys::HtmlSelectElement; use web_sys::HtmlSelectElement;
use yew::prelude::*; use yew::prelude::*;
@ -118,7 +118,7 @@ impl<T: ImplicitClone + PartialEq + 'static> Component for HtmlSelect<T> {
> >
{option_children} {option_children}
</select> </select>
<Icon icon={IconName::DoubleCaretVertical}/> <Icon icon={Icon::DoubleCaretVertical}/>
</div> </div>
} }
} }

View file

@ -1,23 +1,152 @@
use crate::Intent; use crate::Intent;
use implicit_clone::ImplicitClone; use implicit_clone::ImplicitClone;
use std::fmt;
use yew::html::IntoPropValue;
use yew::prelude::*; use yew::prelude::*;
include!("icon_svg_paths.rs"); include!("icon_svg_paths.rs");
pub const ICON_SIZE_STANDARD: i32 = 16; #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub const ICON_SIZE_LARGE: i32 = 20; pub struct IconSize(pub f64);
impl Default for IconName { impl IconSize {
fn default() -> Self { pub const STANDARD: IconSize = IconSize(16.0);
IconName::Blank pub const LARGE: IconSize = IconSize(20.0);
pub fn as_f64(&self) -> f64 {
self.0 as f64
}
pub fn as_f32(&self) -> f32 {
self.0 as f32
} }
} }
impl ImplicitClone for IconName {} impl Default for IconSize {
#[inline]
fn default() -> Self {
Self::STANDARD
}
}
#[derive(Clone, PartialEq, Properties)] impl fmt::Display for IconSize {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
macro_rules! generate_into_prop_value {
($($ty:ty,)*) => {
$(
impl IntoPropValue<IconSize> for $ty {
fn into_prop_value(self) -> IconSize {
IconSize(self.into())
}
}
impl IntoPropValue<IconSize> for &$ty {
fn into_prop_value(self) -> IconSize {
IconSize((*self).into())
}
}
)*
}
}
#[rustfmt::skip]
generate_into_prop_value!(
u8, u16, u32,
i8, i16, i32,
f32,
);
impl IntoPropValue<f32> for IconSize {
fn into_prop_value(self) -> f32 {
self.as_f32()
}
}
impl IntoPropValue<f64> for IconSize {
fn into_prop_value(self) -> f64 {
self.as_f64()
}
}
impl Default for Icon {
#[inline]
fn default() -> Self {
Icon::Blank
}
}
impl ImplicitClone for Icon {}
impl Icon {
pub fn render(&self) -> Html {
self.render_with_props(&Default::default())
}
pub fn render_with_props(
&self,
IconProps {
icon,
class,
title,
color: fill,
intent,
size,
onclick,
}: &IconProps,
) -> Html {
if let Icon::Custom(html) = icon {
return html.clone();
}
let paths = if *size == IconSize::STANDARD {
icon_svg_paths_16(icon)
} else {
icon_svg_paths_20(icon)
};
let pixel_grid_size = if *size >= IconSize::LARGE {
IconSize::LARGE
} else {
IconSize::STANDARD
};
let icon_string = AttrValue::from(format!("{:?}", icon));
let width = AttrValue::from(format!("{size}"));
let height = width.clone();
html! {
<span
class={classes!("bp3-icon", class.clone(), intent)}
{onclick}
>
<svg
{fill}
data-icon={&icon_string}
{width}
{height}
viewBox={format!("0 0 {pixel_grid_size} {pixel_grid_size}")}
>
<desc>{title.clone().unwrap_or(icon_string)}</desc>
{
paths
.iter()
.map(|x| html! {
<path d={*x} fillRule="evenodd" />
})
.collect::<Html>()
}
</svg>
</span>
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Properties)]
pub struct IconProps { pub struct IconProps {
pub icon: IconName, pub icon: Icon,
#[prop_or_default] #[prop_or_default]
pub class: Classes, pub class: Classes,
#[prop_or_default] #[prop_or_default]
@ -26,60 +155,45 @@ pub struct IconProps {
pub color: Option<AttrValue>, pub color: Option<AttrValue>,
#[prop_or_default] #[prop_or_default]
pub intent: Option<Intent>, pub intent: Option<Intent>,
#[prop_or(16)] #[prop_or_default]
pub icon_size: i32, pub size: IconSize,
#[prop_or_default] #[prop_or_default]
pub onclick: Callback<MouseEvent>, pub onclick: Callback<MouseEvent>,
} }
#[function_component(Icon)] impl Component for Icon {
pub fn icon( type Properties = IconProps;
IconProps { type Message = ();
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(*icon)
};
let pixel_grid_size = if *icon_size >= ICON_SIZE_LARGE {
ICON_SIZE_LARGE
} else {
ICON_SIZE_STANDARD
};
let icon_string = AttrValue::from(format!("{:?}", icon));
let width = AttrValue::from(format!("{icon_size}"));
let height = width.clone();
html! { fn create(ctx: &Context<Self>) -> Self {
<span ctx.props().icon.clone()
class={classes!("bp3-icon", class.clone(), intent)} }
{onclick}
> fn view(&self, ctx: &Context<Self>) -> Html {
<svg self.render_with_props(ctx.props())
{fill} }
data-icon={&icon_string} }
{width}
{height} impl IntoPropValue<Icon> for &Option<Icon> {
viewBox={format!("0 0 {pixel_grid_size} {pixel_grid_size}")} fn into_prop_value(self) -> Icon {
> self.clone().unwrap_or_else(|| Icon::Custom(html!()))
<desc>{title.clone().unwrap_or(icon_string)}</desc> }
{ }
paths
.iter() impl IntoPropValue<Icon> for Option<Icon> {
.map(|x| html! { fn into_prop_value(self) -> Icon {
<path d={*x} fillRule="evenodd" /> self.unwrap_or_else(|| Icon::Custom(html!()))
}) }
.collect::<Html>() }
}
</svg> impl IntoPropValue<Icon> for Html {
</span> fn into_prop_value(self) -> Icon {
Icon::Custom(self)
}
}
impl IntoPropValue<Icon> for Option<Html> {
fn into_prop_value(self) -> Icon {
Icon::Custom(self.unwrap_or_default())
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
use crate::{Icon, IconName}; use crate::Icon;
use yew::prelude::*; use yew::prelude::*;
const MIN_HORIZONTAL_PADDING: i32 = 10; const MIN_HORIZONTAL_PADDING: i32 = 10;
@ -65,7 +65,7 @@ pub struct InputGroupProps {
#[prop_or_default] #[prop_or_default]
pub placeholder: AttrValue, pub placeholder: AttrValue,
#[prop_or_default] #[prop_or_default]
pub left_icon: Option<IconName>, pub left_icon: Option<Icon>,
#[prop_or_default] #[prop_or_default]
pub left_element: Option<Html>, pub left_element: Option<Html>,
#[prop_or_default] #[prop_or_default]

View file

@ -1,4 +1,4 @@
use crate::{Icon, IconName, Intent, H6}; use crate::{Icon, Intent, H6};
use yew::prelude::*; use yew::prelude::*;
use yew::virtual_dom::AttrValue; use yew::virtual_dom::AttrValue;
@ -52,7 +52,7 @@ pub struct MenuItemProps {
#[prop_or_default] #[prop_or_default]
pub intent: Option<Intent>, pub intent: Option<Intent>,
#[prop_or_default] #[prop_or_default]
pub icon: Option<IconName>, pub icon: Option<Icon>,
#[prop_or_default] #[prop_or_default]
pub icon_html: Option<Html>, pub icon_html: Option<Html>,
#[prop_or_default] #[prop_or_default]
@ -101,7 +101,7 @@ pub fn menu_item(
html html
} else { } else {
html! { html! {
<Icon icon={IconName::Blank} /> <Icon icon={Icon::Blank} />
} }
} }
} }

View file

@ -1,4 +1,4 @@
use crate::{Button, ButtonGroup, ControlGroup, IconName, InputGroup, Intent}; use crate::{Button, ButtonGroup, ControlGroup, Icon, InputGroup, Intent};
use std::fmt::Display; use std::fmt::Display;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::{Add, Bound, RangeBounds, Sub}; use std::ops::{Add, Bound, RangeBounds, Sub};
@ -46,7 +46,7 @@ where
#[prop_or_default] #[prop_or_default]
pub placeholder: AttrValue, pub placeholder: AttrValue,
#[prop_or_default] #[prop_or_default]
pub left_icon: Option<IconName>, pub left_icon: Option<Icon>,
#[prop_or_default] #[prop_or_default]
pub left_element: Option<Html>, pub left_element: Option<Html>,
#[prop_or_default] #[prop_or_default]
@ -155,13 +155,13 @@ where
html! { html! {
<ButtonGroup vertical=true class={classes!("bp3-fixed")}> <ButtonGroup vertical=true class={classes!("bp3-fixed")}>
<Button <Button
icon={IconName::ChevronUp} icon={Icon::ChevronUp}
disabled={button_up_disabled} disabled={button_up_disabled}
onclick={ctx.link().callback(|_| Msg::Up)} onclick={ctx.link().callback(|_| Msg::Up)}
{intent} {intent}
/> />
<Button <Button
icon={IconName::ChevronDown} icon={Icon::ChevronDown}
disabled={button_down_disabled} disabled={button_down_disabled}
onclick={ctx.link().callback(|_| Msg::Down)} onclick={ctx.link().callback(|_| Msg::Down)}
{intent} {intent}

View file

@ -1,4 +1,4 @@
use crate::{Button, IconName}; use crate::{Button, Icon};
use gloo::timers::callback::Timeout; use gloo::timers::callback::Timeout;
use implicit_clone::ImplicitClone; use implicit_clone::ImplicitClone;
use std::cell::RefCell; use std::cell::RefCell;
@ -282,7 +282,7 @@ impl Component for Panel {
<Button <Button
class={classes!("bp3-panel-stack-header-back")} class={classes!("bp3-panel-stack-header-back")}
style={"padding-right:0"} style={"padding-right:0"}
icon={IconName::ChevronLeft} icon={Icon::ChevronLeft}
minimal={true} minimal={true}
small={true} small={true}
onclick={onclose.reform(|_| ())} onclick={onclose.reform(|_| ())}

View file

@ -1,4 +1,4 @@
use crate::{Icon, IconName, Intent, Text}; use crate::{Icon, Intent, Text};
use yew::prelude::*; use yew::prelude::*;
use yew::virtual_dom::AttrValue; use yew::virtual_dom::AttrValue;
@ -12,7 +12,7 @@ pub struct TagProps {
#[prop_or_default] #[prop_or_default]
pub fill: bool, pub fill: bool,
#[prop_or_default] #[prop_or_default]
pub icon: Option<IconName>, pub icon: Option<Icon>,
#[prop_or_default] #[prop_or_default]
pub intent: Option<Intent>, pub intent: Option<Intent>,
#[prop_or_default] #[prop_or_default]
@ -28,7 +28,7 @@ pub struct TagProps {
#[prop_or_default] #[prop_or_default]
pub onremove: Option<Callback<MouseEvent>>, pub onremove: Option<Callback<MouseEvent>>,
#[prop_or_default] #[prop_or_default]
pub right_icon: Option<IconName>, pub right_icon: Option<Icon>,
#[prop_or_default] #[prop_or_default]
pub round: bool, pub round: bool,
#[prop_or_default] #[prop_or_default]
@ -60,18 +60,6 @@ pub fn tag(
style, style,
}: &TagProps, }: &TagProps,
) -> Html { ) -> Html {
let icon = icon.map(|icon| {
html! {
<Icon {icon} />
}
});
let right_icon = right_icon.map(|icon| {
html! {
<Icon {icon} />
}
});
let remove_button = onremove.clone().map(|onclick| { let remove_button = onremove.clone().map(|onclick| {
html! { html! {
<button <button
@ -79,7 +67,7 @@ pub fn tag(
{onclick} {onclick}
tabindex={interactive.then_some("0")} tabindex={interactive.then_some("0")}
> >
<Icon icon={IconName::SmallCross} /> <Icon icon={Icon::SmallCross} />
</button> </button>
} }
}); });
@ -100,7 +88,7 @@ pub fn tag(
{style} {style}
{onclick} {onclick}
> >
{icon} <Icon {icon} />
<Text <Text
class={classes!("bp3-fill")} class={classes!("bp3-fill")}
ellipsize={!multiline} ellipsize={!multiline}
@ -109,7 +97,7 @@ pub fn tag(
> >
{children.clone()} {children.clone()}
</Text> </Text>
{right_icon} <Icon icon={right_icon} />
{remove_button} {remove_button}
</span> </span>
} }

View file

@ -1,5 +1,5 @@
use crate::collapse::Collapse; use crate::collapse::Collapse;
use crate::icon::{Icon, IconName}; use crate::icon::Icon;
use crate::Intent; use crate::Intent;
use gloo::timers::callback::Timeout; use gloo::timers::callback::Timeout;
use id_tree::*; use id_tree::*;
@ -66,7 +66,7 @@ pub struct TreeProps<T: Clone + PartialEq> {
pub struct NodeData<T> { pub struct NodeData<T> {
pub disabled: bool, pub disabled: bool,
pub has_caret: bool, pub has_caret: bool,
pub icon: Option<IconName>, pub icon: Icon,
pub icon_color: Option<AttrValue>, pub icon_color: Option<AttrValue>,
pub icon_intent: Option<Intent>, pub icon_intent: Option<Intent>,
pub is_expanded: bool, pub is_expanded: bool,
@ -81,7 +81,7 @@ impl<T: Default> Default for NodeData<T> {
Self { Self {
disabled: false, disabled: false,
has_caret: false, has_caret: false,
icon: None, icon: Default::default(),
icon_color: None, icon_color: None,
icon_intent: None, icon_intent: None,
is_expanded: false, is_expanded: false,
@ -98,7 +98,7 @@ impl<T: Clone> Clone for NodeData<T> {
Self { Self {
disabled: self.disabled, disabled: self.disabled,
has_caret: self.has_caret, has_caret: self.has_caret,
icon: self.icon, icon: self.icon.clone(),
icon_color: self.icon_color.clone(), icon_color: self.icon_color.clone(),
icon_intent: self.icon_intent, icon_intent: self.icon_intent,
is_expanded: self.is_expanded, is_expanded: self.is_expanded,
@ -178,7 +178,7 @@ impl<T: 'static + Clone + PartialEq> Tree<T> {
<TreeNode <TreeNode
disabled={data.disabled} disabled={data.disabled}
has_caret={data.has_caret} has_caret={data.has_caret}
icon={data.icon} icon={data.icon.clone()}
icon_color={data.icon_color.clone()} icon_color={data.icon_color.clone()}
icon_intent={data.icon_intent} icon_intent={data.icon_intent}
is_expanded={data.is_expanded} is_expanded={data.is_expanded}
@ -212,7 +212,7 @@ struct TreeNodeProps {
node_id: NodeId, node_id: NodeId,
disabled: bool, disabled: bool,
has_caret: bool, has_caret: bool,
icon: Option<IconName>, icon: Icon,
icon_color: Option<AttrValue>, icon_color: Option<AttrValue>,
icon_intent: Option<Intent>, icon_intent: Option<Intent>,
is_expanded: bool, is_expanded: bool,
@ -314,7 +314,7 @@ impl Component for TreeNode {
"bp3-tree-node-caret-closed" "bp3-tree-node-caret-closed"
}, },
)} )}
icon={IconName::ChevronRight} icon={Icon::ChevronRight}
onclick={self.handler_caret_click.clone()} onclick={self.handler_caret_click.clone()}
/> />
} }
@ -326,7 +326,7 @@ impl Component for TreeNode {
} }
<Icon <Icon
class={classes!("bp3-tree-node-icon")} class={classes!("bp3-tree-node-icon")}
icon={ctx.props().icon.unwrap_or(IconName::Blank)} icon={ctx.props().icon.clone()}
color={ctx.props().icon_color.clone()} color={ctx.props().icon_color.clone()}
intent={ctx.props().icon_intent} intent={ctx.props().icon_intent}
/> />

View file

@ -30,16 +30,17 @@ pub(crate) fn generate_icons() -> Result<()> {
for map in re_map.captures_iter(icon_svg_paths.as_str()) { for map in re_map.captures_iter(icon_svg_paths.as_str()) {
src.push_str("fn icon_svg_paths_"); src.push_str("fn icon_svg_paths_");
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: &Icon) -> &'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_upper_camel_case(); let key = item[1].to_upper_camel_case();
src.push_str("IconName::"); src.push_str("Icon::");
src.push_str(&key); src.push_str(&key);
src.push_str(" => &"); src.push_str(" => &");
src.push_str(&item[2]); src.push_str(&item[2]);
src.push_str(",\n"); src.push_str(",\n");
keys.insert(key); keys.insert(key);
} }
src.push_str("Icon::Custom(_) => &[],\n");
src.push_str(" }}\n\n"); src.push_str(" }}\n\n");
} }
@ -48,19 +49,20 @@ pub(crate) fn generate_icons() -> Result<()> {
let mut keys: Vec<_> = keys.iter().collect(); let mut keys: Vec<_> = keys.iter().collect();
keys.sort(); keys.sort();
src.push_str( src.push_str(
"#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]\n\ "#[derive(Debug, Clone, PartialEq)]\n\
pub enum IconName {\n", pub enum Icon {\n",
); );
for icon in &keys { for icon in &keys {
src.push_str(icon); src.push_str(icon);
src.push_str(",\n"); src.push_str(",\n");
} }
src.push_str("Custom(Html),\n");
src.push_str("}\n\n"); src.push_str("}\n\n");
src.push_str("impl IconName {\n"); src.push_str("impl Icon {\n");
src.push_str("pub const ALL: &[IconName] = &[\n"); src.push_str("pub const ALL: &[Icon] = &[\n");
for icon in keys { for icon in keys {
src.push_str("IconName::"); src.push_str("Icon::");
src.push_str(icon); src.push_str(icon);
src.push_str(",\n"); src.push_str(",\n");
} }

View file

@ -17,7 +17,6 @@ crate-type = ["cdylib"]
console_error_panic_hook = { version = "0.1.7", optional = true } console_error_panic_hook = { version = "0.1.7", optional = true }
gloo = "0.8" gloo = "0.8"
implicit-clone = "0.3.3" implicit-clone = "0.3.3"
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"] }
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size # `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size

View file

@ -24,7 +24,7 @@ use crate::text_area::*;
use crate::tree::*; use crate::tree::*;
use yew::prelude::*; use yew::prelude::*;
use yew_router::prelude::*; use yew_router::prelude::*;
use yewprint::{IconName, Menu, MenuItem}; use yewprint::{Icon, Menu, MenuItem};
#[function_component(AppRoot)] #[function_component(AppRoot)]
pub fn app_root() -> Html { pub fn app_root() -> Html {
@ -84,9 +84,9 @@ impl Component for App {
"Dark theme" "Dark theme"
}; };
let go_to_theme_icon = if self.dark_theme { let go_to_theme_icon = if self.dark_theme {
IconName::Flash Icon::Flash
} else { } else {
IconName::Moon Icon::Moon
}; };
let menu = html! { let menu = html! {

View file

@ -1,5 +1,5 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, ButtonGroup, IconName}; use yewprint::{Button, ButtonGroup, Icon};
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
@ -19,9 +19,9 @@ pub fn example(props: &ExampleProps) -> Html {
vertical={props.vertical} vertical={props.vertical}
style={"margin:0;"} style={"margin:0;"}
> >
<Button icon={IconName::Database}> {"Queries"}</Button> <Button icon={Icon::Database}> {"Queries"}</Button>
<Button icon={IconName::Function}>{"Functions"}</Button> <Button icon={Icon::Function}>{"Functions"}</Button>
<Button icon={IconName::Cog}>{"Options"}</Button> <Button icon={Icon::Cog}>{"Options"}</Button>
</ButtonGroup> </ButtonGroup>
} }
} }

View file

@ -1,6 +1,6 @@
use implicit_clone::{unsync::IArray, ImplicitClone}; use implicit_clone::{unsync::IArray, ImplicitClone};
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, ControlGroup, HtmlSelect, IconName, InputGroup}; use yewprint::{Button, ControlGroup, HtmlSelect, Icon, InputGroup};
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
@ -25,7 +25,7 @@ pub fn example(ExampleProps { fill, vertical }: &ExampleProps) -> Html {
].into_iter().collect::<IArray<_>>()} ].into_iter().collect::<IArray<_>>()}
/> />
<InputGroup placeholder="Find filters..." /> <InputGroup placeholder="Find filters..." />
<Button icon={IconName::ArrowRight} /> <Button icon={Icon::ArrowRight} />
</ControlGroup> </ControlGroup>
} }
} }

View file

@ -1,22 +1,22 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, ButtonGroup, Divider}; use yewprint::{Button, ButtonGroup, Divider, Icon};
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub vertical: bool, pub vertical: bool,
} }
#[function_component(Example)] #[function_component(Example)]
pub fn example(props: &ExampleProps) -> Html { pub fn example(ExampleProps { vertical }: &ExampleProps) -> Html {
html! { html! {
<ButtonGroup vertical={props.vertical}> <ButtonGroup minimal=true {vertical} >
<Button>{"File"}</Button> <Button>{"File"}</Button>
<Button>{"Edit"}</Button> <Button>{"Edit"}</Button>
<Divider vertical={props.vertical} /> <Divider />
<Button>{"Create"}</Button> <Button>{"Create"}</Button>
<Button>{"Delete"}</Button> <Button>{"Delete"}</Button>
<Divider vertical={props.vertical} /> <Divider />
// <Button icon=IconName::Add /> <Button icon={Icon::Add} />
// <Button icon=IconName::Remove /> <Button icon={Icon::Remove} />
</ButtonGroup> </ButtonGroup>
} }
} }

View file

@ -1,5 +1,5 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, Collapse, IconName, Intent}; use yewprint::{Button, Collapse, Icon, Intent};
pub struct ExampleContainer { pub struct ExampleContainer {
collapsed: bool, collapsed: bool,
@ -53,7 +53,7 @@ impl Component for ExampleContainer {
</div> </div>
<div class={classes!("docs-source")}> <div class={classes!("docs-source")}>
<Button <Button
icon={IconName::Code} icon={Icon::Code}
fill={{true}} fill={{true}}
intent={{Intent::Primary}} intent={{Intent::Primary}}
minimal={{true}} minimal={{true}}

View file

@ -3,7 +3,7 @@ mod example;
use crate::ExampleContainer; use crate::ExampleContainer;
use example::*; use example::*;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, IconName, Switch, H1, H5}; use yewprint::{Button, Icon, Switch, H1, H5};
pub struct HtmlSelectDoc { pub struct HtmlSelectDoc {
callback: Callback<ExampleProps>, callback: Callback<ExampleProps>,
@ -101,7 +101,7 @@ crate::build_example_prop_component! {
/> />
<H5>{"Example"}</H5> <H5>{"Example"}</H5>
<Button <Button
icon={IconName::Refresh} icon={Icon::Refresh}
onclick={self.update_props(ctx, |props, _| ExampleProps { onclick={self.update_props(ctx, |props, _| ExampleProps {
reset: props.reset.wrapping_add(1), reset: props.reset.wrapping_add(1),
..props ..props

View file

@ -1,22 +1,18 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Icon, IconName, Intent}; use yewprint::{Icon, Intent};
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps { pub struct ExampleProps {
pub icon_name: IconName, pub icon: Icon,
pub intent: Option<Intent>, pub intent: Option<Intent>,
pub icon_size: i32, pub size: i32,
} }
#[function_component(Example)] #[function_component(Example)]
pub fn example(props: &ExampleProps) -> Html { pub fn example(ExampleProps { icon, intent, size }: &ExampleProps) -> Html {
html! { html! {
<div> <div>
<Icon <Icon {icon} {intent} {size} />
icon={props.icon_name}
intent={props.intent}
icon_size={props.icon_size}
/>
</div> </div>
} }
} }

View file

@ -3,10 +3,9 @@ mod example;
use crate::ExampleContainer; use crate::ExampleContainer;
use example::*; use example::*;
use implicit_clone::unsync::IArray; use implicit_clone::unsync::IArray;
use once_cell::sync::Lazy;
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, InputGroup, Intent, Slider, Text, H1, H5};
pub struct IconDoc { pub struct IconDoc {
callback: Callback<ExampleProps>, callback: Callback<ExampleProps>,
@ -20,12 +19,16 @@ pub enum IconDocMsg {
SearchIcon(String), SearchIcon(String),
} }
static ICON_LIST: Lazy<Vec<(String, IconName)>> = Lazy::new(|| { thread_local! {
IconName::ALL static ICON_LIST: Vec<(String, Icon)> = {
.iter() gloo::console::log!("init");
.map(|x| (format!("{:?}", x).to_lowercase(), *x)) Icon::ALL
.collect() .iter()
}); .cloned()
.map(|x| (format!("{:?}", x).to_lowercase(), x))
.collect()
};
}
impl Component for IconDoc { impl Component for IconDoc {
type Message = IconDocMsg; type Message = IconDocMsg;
@ -35,9 +38,9 @@ impl Component for IconDoc {
IconDoc { IconDoc {
callback: ctx.link().callback(|x| IconDocMsg::Example(x)), callback: ctx.link().callback(|x| IconDocMsg::Example(x)),
state: ExampleProps { state: ExampleProps {
icon_name: IconName::Print, icon: Icon::Print,
intent: None, intent: None,
icon_size: 16, size: 16,
}, },
search_string: Default::default(), search_string: Default::default(),
} }
@ -59,25 +62,26 @@ impl Component for IconDoc {
); );
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.with(|list| {
.iter() list.iter()
.filter_map(|(icon_name, icon)| { .filter_map(|(icon_name, icon)| {
icon_name icon_name
.contains(&search_string) .contains(&search_string)
.then_some(*icon) .then_some(icon)
.map(|icon| { .map(|icon| {
html! { html! {
<div class={classes!("docs-icon-list-item")}> <div class={classes!("docs-icon-list-item")}>
<Icon <Icon
{icon} {icon}
icon_size=20 size=20
/> />
<Text>{format!("{:?}", icon)}</Text> <Text>{format!("{:?}", icon)}</Text>
</div> </div>
} }
}) })
}) })
.collect::<Html>(); .collect::<Html>()
});
html! { html! {
<div> <div>
@ -99,7 +103,7 @@ impl Component for IconDoc {
large=true large=true
fill=true fill=true
round=true round=true
left_icon={IconName::Search} left_icon={Icon::Search}
placeholder="Search for icons..." placeholder="Search for icons..."
value={self.search_string.clone()} value={self.search_string.clone()}
oninput={ctx.link().callback(|e: InputEvent| { oninput={ctx.link().callback(|e: InputEvent| {
@ -131,24 +135,25 @@ crate::build_example_prop_component! {
<input <input
class="bp3-input" class="bp3-input"
onchange={self.update_props(ctx, |props, e: Event| { onchange={self.update_props(ctx, |props, e: Event| {
let icon_name = e.target_dyn_into::<HtmlInputElement>() let icon = e.target_dyn_into::<HtmlInputElement>()
.map(|x| x.value().to_lowercase()) .map(|x| x.value().to_lowercase())
.as_deref() .as_deref()
.and_then(|x| { .and_then(|x| {
ICON_LIST ICON_LIST
.iter() .with(|list| list
.find_map(move |(icon_name, icon)| { .iter()
(icon_name == x).then_some(*icon) .find_map(move |(icon_name, icon)| {
}) (icon_name == x).then_some(icon.clone())
}))
}); });
ExampleProps { ExampleProps {
icon_name: icon_name.unwrap_or_default(), icon: icon.unwrap_or_default(),
..props ..props
} }
})} })}
type="text" type="text"
value={format!("{:?}", ctx.props().example_props.icon_name)} value={format!("{:?}", ctx.props().example_props.icon)}
/> />
<p <p
style="margin-top: 5px;" style="margin-top: 5px;"
@ -175,14 +180,14 @@ crate::build_example_prop_component! {
{"Select icon size"} {"Select icon size"}
</p> </p>
<Slider<i32> <Slider<i32>
selected={ctx.props().example_props.icon_size} selected={ctx.props().example_props.size}
values={option_labels} values={option_labels}
onchange={self.update_props(ctx, |props, icon_size| ExampleProps { onchange={self.update_props(ctx, |props, size| ExampleProps {
icon_size, size,
..props ..props
})} })}
value_label={ value_label={
format!("{}", ctx.props().example_props.icon_size) format!("{}", ctx.props().example_props.size)
} }
/> />
</div> </div>

View file

@ -1,7 +1,7 @@
use gloo::dialogs::alert; use gloo::dialogs::alert;
use web_sys::HtmlInputElement; use web_sys::HtmlInputElement;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, IconName, InputGroup, Tag}; use yewprint::{Button, Icon, InputGroup, Tag};
pub struct Example { pub struct Example {
histogram_value: String, histogram_value: String,
@ -92,7 +92,7 @@ impl Component for Example {
small={ctx.props().small} small={ctx.props().small}
round={ctx.props().round} round={ctx.props().round}
disabled={ctx.props().disabled} disabled={ctx.props().disabled}
left_icon={IconName::Filter} left_icon={Icon::Filter}
placeholder={"Filter histogram..."} placeholder={"Filter histogram..."}
value={self.histogram_value.clone()} value={self.histogram_value.clone()}
oninput={ctx.link().callback(|e: InputEvent| { oninput={ctx.link().callback(|e: InputEvent| {
@ -121,7 +121,7 @@ impl Component for Example {
})} })}
right_element={{ html! { right_element={{ html! {
<Button <Button
icon={IconName::Lock} icon={Icon::Lock}
minimal={true} minimal={true}
disabled={ctx.props().disabled} disabled={ctx.props().disabled}
/> />
@ -133,7 +133,7 @@ impl Component for Example {
small={ctx.props().small} small={ctx.props().small}
round={ctx.props().round} round={ctx.props().round}
disabled={ctx.props().disabled} disabled={ctx.props().disabled}
left_icon={IconName::Tag} left_icon={Icon::Tag}
placeholder={"Find tags"} placeholder={"Find tags"}
value={self.tags_value.clone()} value={self.tags_value.clone()}
oninput={ctx.link().callback(|e: InputEvent| { oninput={ctx.link().callback(|e: InputEvent| {

View file

@ -1,6 +1,6 @@
use crate::{DocMenu, Logo}; use crate::{DocMenu, Logo};
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Icon, IconName, Menu, MenuDivider, MenuItem}; use yewprint::{Icon, Menu, MenuDivider, MenuItem};
pub struct Example {} pub struct Example {}
@ -26,7 +26,7 @@ impl Component for Example {
}; };
let share_icon = html! { let share_icon = html! {
<Icon icon={IconName::Share} /> <Icon icon={Icon::Share} />
}; };
html! { html! {
@ -38,21 +38,21 @@ impl Component for Example {
/> />
<MenuDivider /> <MenuDivider />
<MenuItem <MenuItem
icon={IconName::NewTextBox} icon={Icon::NewTextBox}
text={html!("New text box")} text={html!("New text box")}
href={"#menu"} href={"#menu"}
onclick={ctx.link() onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::Menu))} .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
/> />
<MenuItem <MenuItem
icon={IconName::NewObject} icon={Icon::NewObject}
text={html!("New object")} text={html!("New object")}
href={"#menu"} href={"#menu"}
onclick={ctx.link() onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::Menu))} .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
/> />
<MenuItem <MenuItem
icon={IconName::NewLink} icon={Icon::NewLink}
text={html!("New link")} text={html!("New link")}
href={"#menu"} href={"#menu"}
onclick={ctx.link() onclick={ctx.link()
@ -60,7 +60,7 @@ impl Component for Example {
/> />
<MenuDivider /> <MenuDivider />
<MenuItem <MenuItem
icon={IconName::Cog} icon={Icon::Cog}
text={html!("Settings")} text={html!("Settings")}
label={share_icon} label={share_icon}
href={"#menu"} href={"#menu"}
@ -71,7 +71,7 @@ impl Component for Example {
<Menu> <Menu>
<MenuDivider title={html!("Edit")} /> <MenuDivider title={html!("Edit")} />
<MenuItem <MenuItem
icon={IconName::Cut} icon={Icon::Cut}
text={html!("Cut")} text={html!("Cut")}
label={html!("Ctrl+X")} label={html!("Ctrl+X")}
href={"#menu"} href={"#menu"}
@ -79,7 +79,7 @@ impl Component for Example {
.callback(|_| Msg::GoToMenu(DocMenu::Menu))} .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
/> />
<MenuItem <MenuItem
icon={IconName::Duplicate} icon={Icon::Duplicate}
text={html!("Copy")} text={html!("Copy")}
label={html!("Ctrl+C")} label={html!("Ctrl+C")}
href={"#menu"} href={"#menu"}
@ -87,28 +87,28 @@ impl Component for Example {
.callback(|_| Msg::GoToMenu(DocMenu::Menu))} .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
/> />
<MenuItem <MenuItem
icon={IconName::Clipboard} icon={Icon::Clipboard}
text={html!("Paste")} text={html!("Paste")}
label={html!("Ctrl+V")} label={html!("Ctrl+V")}
disabled=true disabled=true
/> />
<MenuDivider title={html!("Text")} /> <MenuDivider title={html!("Text")} />
<MenuItem <MenuItem
icon={IconName::AlignLeft} icon={Icon::AlignLeft}
text={html!("Alignment")} text={html!("Alignment")}
href={"#menu"} href={"#menu"}
onclick={ctx.link() onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::Menu))} .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
/> />
<MenuItem <MenuItem
icon={IconName::Style} icon={Icon::Style}
text={html!("Style")} text={html!("Style")}
href={"#menu"} href={"#menu"}
onclick={ctx.link() onclick={ctx.link()
.callback(|_| Msg::GoToMenu(DocMenu::Menu))} .callback(|_| Msg::GoToMenu(DocMenu::Menu))}
/> />
<MenuItem <MenuItem
icon={IconName::Asterisk} icon={Icon::Asterisk}
text={html!("Miscellaneous")} text={html!("Miscellaneous")}
href={"#menu"} href={"#menu"}
onclick={ctx.link().callback(|_| Msg::GoToMenu(DocMenu::Menu))} onclick={ctx.link().callback(|_| Msg::GoToMenu(DocMenu::Menu))}

View file

@ -1,5 +1,5 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::{Button, Callout, IconName, Intent, NumericInput}; use yewprint::{Button, Callout, Icon, Intent, NumericInput};
pub struct Example { pub struct Example {
value: i32, value: i32,
@ -74,7 +74,7 @@ impl Component for Example {
onchange={ctx.link().callback(|x| Msg::UpdateValue(x))} onchange={ctx.link().callback(|x| Msg::UpdateValue(x))}
{disable_buttons} {disable_buttons}
{buttons_on_the_left} {buttons_on_the_left}
left_icon={left_icon.then_some(IconName::Dollar)} left_icon={left_icon.then_some(Icon::Dollar)}
{intent} {intent}
/> />
<NumericInput<i32> <NumericInput<i32>
@ -88,11 +88,11 @@ impl Component for Example {
onchange={ctx.link().callback(|x| Msg::UpdateValueTwo(x))} onchange={ctx.link().callback(|x| Msg::UpdateValueTwo(x))}
{disable_buttons} {disable_buttons}
{buttons_on_the_left} {buttons_on_the_left}
left_icon={left_icon.then_some(IconName::Dollar)} left_icon={left_icon.then_some(Icon::Dollar)}
{intent} {intent}
/> />
<Button <Button
icon={IconName::Refresh} icon={Icon::Refresh}
onclick={ctx.link().callback(|_| Msg::Reset)} onclick={ctx.link().callback(|_| Msg::Reset)}
> >
{"Reset at 4"} {"Reset at 4"}

View file

@ -1,6 +1,6 @@
use implicit_clone::unsync::IArray; use implicit_clone::unsync::IArray;
use yew::prelude::*; use yew::prelude::*;
use yewprint::{IconName, Intent, Tag}; use yewprint::{Icon, Intent, Tag};
pub struct Example { pub struct Example {
tags: Vec<String>, tags: Vec<String>,
@ -84,13 +84,13 @@ impl Component for Example {
<Tag <Tag
active={ctx.props().active} active={ctx.props().active}
fill={ctx.props().fill} fill={ctx.props().fill}
icon={ctx.props().icon.then_some(IconName::Print)} icon={ctx.props().icon.then_some(Icon::Print)}
intent={ctx.props().intent} intent={ctx.props().intent}
interactive={ctx.props().interactive} interactive={ctx.props().interactive}
large={ctx.props().large} large={ctx.props().large}
minimal={ctx.props().minimal} minimal={ctx.props().minimal}
multiline={ctx.props().multiline} multiline={ctx.props().multiline}
right_icon={ctx.props().right_icon.then_some(IconName::Star)} right_icon={ctx.props().right_icon.then_some(Icon::Star)}
round={ctx.props().round} round={ctx.props().round}
onremove={remove} onremove={remove}
onclick={ctx.link().callback(|_| ExampleMsg::Click)} onclick={ctx.link().callback(|_| ExampleMsg::Click)}

View file

@ -4,7 +4,7 @@ use crate::ExampleContainer;
use example::*; use example::*;
use implicit_clone::unsync::{IArray, IString}; 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, Icon, Intent, Switch, H1, H5};
pub struct TagDoc { pub struct TagDoc {
callback: Callback<ExampleProps>, callback: Callback<ExampleProps>,
@ -190,7 +190,7 @@ crate::build_example_prop_component! {
value={ctx.props().example_props.intent} value={ctx.props().example_props.intent}
/> />
<Button <Button
icon={IconName::Refresh} icon={Icon::Refresh}
onclick={self.update_props(ctx, |props, _| ExampleProps { onclick={self.update_props(ctx, |props, _| ExampleProps {
reset_tags: props.reset_tags + 1, reset_tags: props.reset_tags + 1,
..props.clone() ..props.clone()

View file

@ -1,6 +1,6 @@
use yew::prelude::*; use yew::prelude::*;
use yewprint::id_tree::{InsertBehavior, Node, NodeId, TreeBuilder}; use yewprint::id_tree::{InsertBehavior, Node, NodeId, TreeBuilder};
use yewprint::{Icon, IconName, Intent, NodeData, Tree, TreeData}; use yewprint::{Icon, Intent, NodeData, Tree, TreeData};
pub struct Example { pub struct Example {
tree: TreeData<i32>, tree: TreeData<i32>,
@ -31,7 +31,7 @@ impl Component for Example {
let dir1 = tree let dir1 = tree
.insert( .insert(
Node::new(NodeData { Node::new(NodeData {
icon: Some(IconName::FolderClose), icon: Icon::FolderClose,
label: "Big directory".into(), label: "Big directory".into(),
has_caret: true, has_caret: true,
data: 1, data: 1,
@ -44,7 +44,7 @@ impl Component for Example {
let dir2 = tree let dir2 = tree
.insert( .insert(
Node::new(NodeData { Node::new(NodeData {
icon: Some(IconName::FolderClose), icon: Icon::FolderClose,
label: format!("Directory {}", i + 1).into(), label: format!("Directory {}", i + 1).into(),
has_caret: true, has_caret: true,
data: 1, data: 1,
@ -56,7 +56,7 @@ impl Component for Example {
for i in 0..10 { for i in 0..10 {
tree.insert( tree.insert(
Node::new(NodeData { Node::new(NodeData {
icon: Some(IconName::Document), icon: Icon::Document,
label: format!("File {}", i + 1).into(), label: format!("File {}", i + 1).into(),
data: i, data: i,
..Default::default() ..Default::default()
@ -68,10 +68,10 @@ impl Component for Example {
} }
tree.insert( tree.insert(
Node::new(NodeData { Node::new(NodeData {
icon: Some(IconName::Tag), icon: Icon::Tag,
icon_intent: Some(Intent::Primary), icon_intent: Some(Intent::Primary),
label: "Outer file".into(), label: "Outer file".into(),
secondary_label: Some(html!(<Icon icon={IconName::EyeOpen} />)), secondary_label: Some(html!(<Icon icon={Icon::EyeOpen} />)),
data: 3, data: 3,
..Default::default() ..Default::default()
}), }),
@ -93,11 +93,11 @@ impl Component for Example {
let node = tree.get_mut(&node_id).unwrap(); let node = tree.get_mut(&node_id).unwrap();
let data = node.data_mut(); let data = node.data_mut();
data.is_expanded ^= true; data.is_expanded ^= true;
data.icon = Some(if data.is_expanded { data.icon = if data.is_expanded {
IconName::FolderOpen Icon::FolderOpen
} else { } else {
IconName::FolderClose Icon::FolderClose
}) };
} }
Msg::SelectNode(node_id) => { Msg::SelectNode(node_id) => {
let mut tree = self.tree.borrow_mut(); let mut tree = self.tree.borrow_mut();