This commit is contained in:
nbrr 2020-10-09 19:52:26 +02:00 committed by GitHub
parent 8eca5094cb
commit 5b6b643290
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 504 additions and 15 deletions

View file

@ -7,6 +7,7 @@ use crate::divider::*;
use crate::html_select::*;
use crate::icon::*;
use crate::progressbar::*;
use crate::tag::*;
use crate::text::*;
use crate::tree::*;
@ -166,6 +167,11 @@ impl Component for App {
href="#switch"
onclick=self.link.callback(|_| Msg::GoToMenu(DocMenu::Switch))
/>
<MenuItem
text={html!("Tag")}
href="#tag"
onclick=self.link.callback(|_| Msg::GoToMenu(DocMenu::Tag))
/>
<MenuItem
text={html!("Text")}
href="#text"
@ -207,6 +213,7 @@ impl Component for App {
DocMenu::Tree => html!(<TreeDoc />),
DocMenu::Icon => html!(<IconDoc />),
DocMenu::ProgressBar => html!(<ProgressBarDoc />),
DocMenu::Tag => html!(<TagDoc />),
DocMenu::Menu => html!(),
}
})
@ -243,6 +250,8 @@ pub enum DocMenu {
ProgressBar,
#[to = "/#switch"]
Switch,
#[to = "/#tag"]
Tag,
#[to = "/#text"]
Text,
#[to = "/#tree"]

View file

@ -10,6 +10,7 @@ mod html_select;
mod icon;
mod progressbar;
mod switch;
mod tag;
mod text;
mod tree;

View file

@ -81,7 +81,7 @@ crate::build_example_prop_component! {
..props
})
checked=self.props.animate
label="animate"
label="Animate"
/>
<p>{"Select intent:"}</p>
<Menu>

View file

@ -0,0 +1,100 @@
use yew::prelude::*;
use yewprint::{ConditionalClass, IconName, Intent, Tag};
pub struct Example {
props: ExampleProps,
link: ComponentLink<Self>,
tags: Vec<String>,
}
#[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps {
pub initial_tags: Vec<String>,
pub active: bool,
pub fill: bool,
pub icon: ConditionalClass,
pub intent: Option<Intent>,
pub interactive: bool,
pub large: bool,
pub minimal: bool,
pub multiline: bool,
pub removable: ConditionalClass,
pub right_icon: ConditionalClass,
pub round: bool,
pub reset_tags: u64,
}
pub enum ExampleMsg {
Remove(String),
Click,
}
impl Component for Example {
type Message = ExampleMsg;
type Properties = ExampleProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
let tags = props.initial_tags.clone();
Example { props, link, tags }
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
ExampleMsg::Remove(label) => {
self.tags = self
.tags
.clone()
.into_iter()
.filter(|l| *l != label)
.collect()
}
ExampleMsg::Click => (),
}
true
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
if self.props.reset_tags != props.reset_tags {
self.tags = props.initial_tags.clone();
}
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
self.tags
.iter()
.map(|label| {
let remove = {
let label = label.clone();
self.props.removable.map_some(
self.link
.callback(move |_| ExampleMsg::Remove(label.clone())),
)
};
html! {
<Tag
active=self.props.active
fill=self.props.fill
icon=self.props.icon.map_some(IconName::Print)
intent=self.props.intent
interactive=self.props.interactive
large=self.props.large
minimal=self.props.minimal
multiline=self.props.multiline
right_icon=self.props.right_icon.map_some(IconName::Star)
round=self.props.round
onremove=remove
onclick=self.link.callback(|_| ExampleMsg::Click)
>
{label.clone()}
</Tag>
}
})
.collect::<Html>()
}
}

228
yewprint-doc/src/tag/mod.rs Normal file
View file

@ -0,0 +1,228 @@
mod example;
use crate::ExampleContainer;
use example::*;
use yew::prelude::*;
use yewprint::{Button, Intent, Menu, MenuItem, Switch, H1, H5};
pub struct TagDoc {
callback: Callback<ExampleProps>,
state: ExampleProps,
}
fn initial_tags() -> Vec<String> {
vec![
"Landscape".into(),
"Bird".into(),
"City".into(),
"Bridge".into(),
"Street".into(),
"Why do you go away? So that you can come back. So that you can see the place you \
came from with new eyes and extra colors. And the people there see you differently, \
too. Coming back to where you started is not the same as never leaving."
.into(),
]
}
impl Component for TagDoc {
type Message = ExampleProps;
type Properties = ();
fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
TagDoc {
callback: link.callback(|x| x),
state: ExampleProps {
initial_tags: initial_tags(),
active: false,
fill: false,
icon: false.into(),
intent: None,
interactive: false,
large: false,
minimal: false,
multiline: false,
removable: false.into(),
right_icon: false.into(),
round: false,
reset_tags: 0,
},
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
self.state = msg;
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone();
let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"),
"bp3-code-block"
);
html! {
<div>
<H1 class="docs-title">{"Tag"}</H1>
<ExampleContainer
source=source
props=Some(html!{
<TagProps
callback={self.callback.clone()}
props=example_props.clone()
/>
})
>
<Example with example_props/>
</ExampleContainer>
</div>
}
}
}
crate::build_example_prop_component! {
TagProps for ExampleProps =>
fn view(&self) -> Html {
html! {
<div>
<H5>{"Props"}</H5>
<div>
<Switch
onclick=self.update_props(|props, _| ExampleProps {
active: !props.active,
..props
})
checked=self.props.active
label="Active"
/>
<Switch
onclick=self.update_props(|props, _| ExampleProps {
fill: !props.fill,
..props
})
checked=self.props.fill
label="Fill"
/>
<Switch
onclick=self.update_props(|props, _| ExampleProps {
interactive: !props.interactive,
..props
})
checked=self.props.interactive
label="Interactive"
/>
<Switch
onclick=self.update_props(|props, _| ExampleProps {
large: !props.large,
..props
})
checked=self.props.large
label="Large"
/>
<Switch
onclick=self.update_props(|props, _| ExampleProps {
minimal: !props.minimal,
..props
})
checked=self.props.minimal
label="Minimal"
/>
<Switch
onclick=self.update_props(|props, _| ExampleProps {
multiline: !props.multiline,
..props
})
checked=self.props.multiline
label="Multiline"
/>
<Switch
onclick=self.update_props(|props, _| ExampleProps {
round: !props.round,
..props
})
checked=self.props.round
label="Round"
/>
<Switch
onclick=self.update_props(|props, _| ExampleProps {
removable: !props.removable,
..props
})
checked=self.props.removable
label="Removable"
/>
<Switch
onclick=self.update_props(|props, _| ExampleProps {
icon: !props.icon,
..props
})
checked=self.props.icon
label="Icon"
/>
<Switch
onclick=self.update_props(|props, _| ExampleProps {
right_icon: !props.right_icon,
..props
})
checked=self.props.right_icon
label="Right icon"
/>
<Button
onclick=self.update_props(|props, _| ExampleProps {
reset_tags: props.reset_tags + 1,
..props
})
>
{"Reset tags"}
</Button>
<p>{"Select intent:"}</p>
<Menu>
<MenuItem
onclick=self.update_props(|props, _| ExampleProps {
intent: None,
..props
})
text=html!{"None"}
/>
<MenuItem
onclick=self.update_props(|props, _| ExampleProps {
intent: Some(Intent::Primary),
..props
})
text=html!{"Primary"}
intent=Intent::Primary
/>
<MenuItem
onclick=self.update_props(|props, _| ExampleProps {
intent: Some(Intent::Success),
..props
})
text=html!{"Success"}
intent=Intent::Success
/>
<MenuItem
onclick=self.update_props(|props, _| ExampleProps {
intent: Some(Intent::Warning),
..props
})
text=html!{"Warning"}
intent=Intent::Warning
/>
<MenuItem
onclick=self.update_props(|props, _| ExampleProps {
intent: Some(Intent::Danger),
..props
})
text=html!{"Danger"}
intent=Intent::Danger
/>
</Menu>
</div>
</div>
}
}
}

View file

@ -35,10 +35,9 @@ impl Component for Example {
fn view(&self) -> Html {
html! {
<div style="width: 150px; height: 20px">
<Text
ellipsize=self.props.ellipsize
text=&self.props.text
/>
<Text ellipsize=self.props.ellipsize>
{&self.props.text}
</Text>
</div>
}
}

View file

@ -19,6 +19,8 @@ pub struct Props {
pub title: String,
#[prop_or_default]
pub onclick: Callback<MouseEvent>,
#[prop_or_default]
pub class: String,
pub children: html::Children,
}
@ -51,6 +53,7 @@ impl Component for Button {
self.props.fill.map_some("bp3-fill"),
self.props.minimal.map_some("bp3-minimal"),
self.props.intent,
self.props.class.clone(),
)
onclick={self.props.onclick.clone()}
>

View file

@ -10,6 +10,7 @@ mod icon;
mod menu;
mod progressbar;
mod switch;
mod tag;
mod text;
#[cfg(feature = "tree")]
mod tree;
@ -28,6 +29,7 @@ pub use id_tree;
pub use menu::*;
pub use progressbar::*;
pub use switch::*;
pub use tag::*;
pub use text::*;
#[cfg(feature = "tree")]
pub use tree::*;
@ -35,6 +37,24 @@ pub use tree::*;
use std::ops::{Deref, DerefMut, Not};
use yew::virtual_dom::{Classes, Transformer, VComp};
#[macro_export]
macro_rules! if_html {
(let $pat:pat = $cond:expr => $($body:tt)+) => {
if let $pat = $cond {
html!($($body)+)
} else {
html!()
}
};
($cond:expr => $($body:tt)+) => {
if $cond {
html($(body)+)
} else {
html!()
}
};
}
// NOTE: this class needs to become deprecated when the feature bool_to_option lands in stable
//
// https://github.com/rust-lang/rust/issues/64260

107
yewprint/src/tag.rs Normal file
View file

@ -0,0 +1,107 @@
use crate::{if_html, Button, ConditionalClass, Icon, IconName, Intent, Text};
use yew::prelude::*;
pub struct Tag {
props: Props,
}
#[derive(Clone, PartialEq, Properties)]
pub struct Props {
pub children: Children,
#[prop_or_default]
// FIXME Not clear that this field has any effect without `interactive` on.
pub active: ConditionalClass,
#[prop_or_default]
pub fill: ConditionalClass,
#[prop_or_default]
pub icon: Option<IconName>,
#[prop_or_default]
pub intent: Option<Intent>,
#[prop_or_default]
pub interactive: ConditionalClass,
#[prop_or_default]
pub large: ConditionalClass,
#[prop_or_default]
pub minimal: ConditionalClass,
#[prop_or_default]
pub multiline: ConditionalClass,
#[prop_or_default]
pub onclick: Callback<MouseEvent>,
#[prop_or_default]
pub onremove: Option<Callback<MouseEvent>>,
#[prop_or_default]
pub right_icon: Option<IconName>,
#[prop_or_default]
pub round: ConditionalClass,
#[prop_or_default]
pub title: Option<String>,
}
impl Component for Tag {
type Message = ();
type Properties = Props;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Tag { props }
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
let icon = if_html!(let Some(icon) = self.props.icon => <Icon icon=icon />);
let right_icon =
if_html!(let Some(right_icon) = self.props.right_icon => <Icon icon=right_icon />);
let remove_button = if_html! {
let Some(callback) = self.props.onremove.clone() =>
html!(
<Button
class="bp3-tag-remove"
onclick={callback}
>
<Icon icon=IconName::SmallCross />
</Button>
)
};
html! {
<span
class=(
"bp3-tag",
self.props.intent,
self.props.active.map_some("bp3-active"),
self.props.fill.map_some("bp3-fill"),
self.props.interactive.map_some("bp3-interactive"),
self.props.large.map_some("bp3-large"),
self.props.minimal.map_some("bp3-minimal"),
self.props.round.map_some("bp3-round"),
)
onclick={self.props.onclick.clone()}
>
{icon}
<Text
class="bp3-fill"
ellipsize={!self.props.multiline}
title=self.props.title.clone()
inline=true
>
{self.props.children.clone()}
</Text>
{remove_button}
{right_icon}
</span>
}
}
}

View file

@ -10,7 +10,14 @@ pub struct Props {
#[prop_or_default]
pub ellipsize: ConditionalClass,
#[prop_or_default]
pub text: String,
pub children: Children,
#[prop_or_default]
pub class: String,
/// Wrap text in `span` instead of `div`.
#[prop_or_default]
pub inline: bool,
#[prop_or_default]
pub title: Option<String>,
}
impl Component for Text {
@ -35,15 +42,30 @@ impl Component for Text {
}
fn view(&self) -> Html {
html! {
<p
class=(
"bp3-text",
self.props.ellipsize.map_some("bp3-text-overflow-ellipsis"),
)
>
{self.props.text.clone()}
</p>
if self.props.inline {
html! {
<span
class=(
self.props.class.clone(),
self.props.ellipsize.map_some("bp3-text-overflow-ellipsis"),
)
title?=self.props.title.clone()
>
{self.props.children.clone()}
</span>
}
} else {
html! {
<div
class=(
self.props.class.clone(),
self.props.ellipsize.map_some("bp3-text-overflow-ellipsis"),
)
title?=self.props.title.clone()
>
{self.props.children.clone()}
</div>
}
}
}
}