Example props (#25)

This commit is contained in:
Cecile Tonglet 2020-09-28 15:24:04 +02:00 committed by GitHub
parent ac387f9789
commit 03ea949699
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 426 additions and 132 deletions

2
.gitignore vendored
View file

@ -1,3 +1,3 @@
/target
target
/public
/*.tgz

26
Cargo.lock generated
View file

@ -690,6 +690,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c"
dependencies = [
"cfg-if",
"serde",
"serde_json",
"wasm-bindgen-macro",
]
@ -854,6 +856,7 @@ dependencies = [
"regex",
"web-sys",
"yew",
"yewtil",
]
[[package]]
@ -866,3 +869,26 @@ dependencies = [
"yew",
"yewprint",
]
[[package]]
name = "yewtil"
version = "0.3.1"
source = "git+https://github.com/yewstack/yew.git?rev=1507c21b#1507c21b39e7941f7b9fe8ff5af792cf55be1f6f"
dependencies = [
"futures",
"log",
"wasm-bindgen",
"wasm-bindgen-futures",
"yew",
"yewtil-macro",
]
[[package]]
name = "yewtil-macro"
version = "0.1.0"
source = "git+https://github.com/yewstack/yew.git?rev=1507c21b#1507c21b39e7941f7b9fe8ff5af792cf55be1f6f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View file

@ -66,7 +66,7 @@ Roadmap
- [ ] AnchorButton
- [ ] [ButtonGroup](https://blueprintjs.com/docs/#core/components/button-group)
- depends on: Button
- [ ] [Callout](https://blueprintjs.com/docs/#core/components/callout)
- [x] [Callout](https://blueprintjs.com/docs/#core/components/callout)
- [ ] [Card](https://blueprintjs.com/docs/#core/components/card)
- [x] [Collapse](https://blueprintjs.com/docs/#core/components/collapse)
- [ ] [CollapsibleList](https://blueprintjs.com/docs/#core/components/collapsible-list)

View file

@ -49,7 +49,7 @@
white-space: pre-wrap;
}
.docs-example-wrapper .bp3-code-block > pre {
.docs-example-wrapper .bp3-code-block pre {
margin: 0;
white-space: pre-wrap;
}
@ -64,25 +64,25 @@
background-color: #293742;
}
.docs-example-wrapper > .docs-source > .bp3-button {
.docs-source > .bp3-button {
background-color: #ebf1f5;
}
.bp3-dark .docs-example-wrapper > .docs-source > .bp3-button {
.bp3-dark .docs-source > .bp3-button {
background-color: #394b59;
}
.docs-example-wrapper > .docs-source {
.docs-source {
margin-bottom: 40px;
margin-top: 10px;
}
.docs-example-wrapper > .docs-source .bp3-code-block {
.docs-source .bp3-code-block {
filter: invert(1) hue-rotate(180deg) contrast(2);
background-color: #002b36;
}
.bp3-dark .docs-example-wrapper > .docs-source .bp3-code-block {
.bp3-dark .docs-source .bp3-code-block {
filter: inherit;
}
</style>

View file

@ -4,6 +4,13 @@ use yewprint::Button;
pub struct Example {
link: ComponentLink<Self>,
counter: i64,
props: ExampleProps,
}
#[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps {
pub minimal: bool,
pub fill: bool,
}
pub enum Msg {
@ -12,10 +19,14 @@ pub enum Msg {
impl Component for Example {
type Message = Msg;
type Properties = ();
type Properties = ExampleProps;
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Example { counter: 0, link }
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
Example {
counter: 0,
link,
props,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
@ -25,16 +36,27 @@ impl Component for Example {
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<div>
<p> {"Counter: "} { self.counter }</p>
<p>{"Counter: "}{self.counter}</p>
<div>
<Button onclick=self.link.callback(|_| Msg::AddOne)>{ "Add 1" }</Button>
<Button
onclick=self.link.callback(|_| Msg::AddOne)
minimal=self.props.minimal
fill=self.props.fill
>
{"Add 1"}
</Button>
</div>
</div>
}

View file

@ -1,16 +1,31 @@
use yew::prelude::*;
mod example;
pub struct ButtonDoc;
use crate::ExampleContainer;
use example::*;
use yew::prelude::*;
use yewprint::{Switch, H1, H5};
pub struct ButtonDoc {
callback: Callback<ExampleProps>,
state: ExampleProps,
}
impl Component for ButtonDoc {
type Message = ();
type Message = ExampleProps;
type Properties = ();
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self {
ButtonDoc
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
ButtonDoc {
callback: link.callback(|x| x),
state: ExampleProps {
minimal: false,
fill: false,
},
}
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
fn update(&mut self, msg: Self::Message) -> ShouldRender {
self.state = msg;
true
}
@ -19,12 +34,55 @@ impl Component for ButtonDoc {
}
fn view(&self) -> Html {
let source = crate::include_example!();
let example_props = self.state.clone();
let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"),
"bp3-code-block"
);
html! {
<div>
<h1>{"Button"}</h1>
<div>{source}</div>
<H1 class="docs-title">{"Button"}</H1>
<div>
<ExampleContainer
source=source
props=Some(html! {
<ButtonProps
callback={self.callback.clone()}
props=example_props.clone()
/>
})
>
<Example with example_props />
</ExampleContainer>
</div>
</div>
}
}
}
crate::build_example_prop_component! {
ButtonProps for ExampleProps =>
fn view(&self) -> Html {
html! {
<div>
<H5>{"Props"}</H5>
<Switch
onclick=self.update_props(|props| ExampleProps {
minimal: !props.minimal,
..props
})
checked=self.props.minimal
label="Minimal"
/>
<Switch
onclick=self.update_props(|props| ExampleProps {
fill: !props.fill,
..props
})
checked=self.props.fill
label="Fill"
/>
</div>
}
}

View file

@ -1,66 +1,48 @@
use yew::prelude::*;
use yewprint::{Callout, Intent, Menu, MenuItem, Switch};
use yewprint::{Callout, Intent};
pub struct Example {
link: ComponentLink<Self>,
intent: Option<Intent>,
show_icon: bool,
show_title: bool,
props: ExampleProps,
}
pub enum Msg {
ChangeIntent(Option<Intent>),
ToggleIcon,
ToggleTitle,
#[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps {
pub intent: Option<Intent>,
pub show_icon: bool,
pub show_title: bool,
}
impl Component for Example {
type Message = Msg;
type Properties = ();
type Message = ();
type Properties = ExampleProps;
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Example {
link,
intent: None,
show_icon: false,
show_title: true,
}
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Example { props }
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::ChangeIntent(new_intent) => self.intent = new_intent,
Msg::ToggleIcon => self.show_icon = !self.show_icon,
Msg::ToggleTitle => self.show_title = !self.show_title,
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
let title = if self.show_title {
let title = if self.props.show_title {
Some("Visually important content")
} else {
None
};
html! {
<Callout title=title without_icon=self.show_icon intent=self.intent>
<p> {"The Callout element's background reflects its intent, if any."}</p>
<div>
<Switch onclick=self.link.callback(|_| Msg::ToggleIcon) checked=self.show_icon label={ "Show/hide icon" } />
<Switch onclick=self.link.callback(|_| Msg::ToggleTitle) checked=self.show_title label={ "Show/hide title" } />
<p> {"Select intent:"}</p>
<Menu>
<MenuItem onclick=self.link.callback(|_| Msg::ChangeIntent(None)) text=html!{"None"}/>
<MenuItem onclick=self.link.callback(|_| Msg::ChangeIntent(Some(Intent::Primary))) text=html!{"Primary"}/>
<MenuItem onclick=self.link.callback(|_| Msg::ChangeIntent(Some(Intent::Success))) text=html!{"Success"}/>
<MenuItem onclick=self.link.callback(|_| Msg::ChangeIntent(Some(Intent::Warning))) text=html!{"Warning"}/>
<MenuItem onclick=self.link.callback(|_| Msg::ChangeIntent(Some(Intent::Danger))) text=html!{"Danger"}/>
</Menu>
</div>
<Callout title=title without_icon=!self.props.show_icon intent=self.props.intent>
<p>{"The Callout element's background reflects its intent, if any."}</p>
</Callout>
}
}

View file

@ -1,16 +1,32 @@
use yew::prelude::*;
mod example;
pub struct CalloutDoc;
use crate::ExampleContainer;
use example::*;
use yew::prelude::*;
use yewprint::{Intent, Menu, MenuItem, Switch, H1, H5};
pub struct CalloutDoc {
callback: Callback<ExampleProps>,
state: ExampleProps,
}
impl Component for CalloutDoc {
type Message = ();
type Message = ExampleProps;
type Properties = ();
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self {
CalloutDoc
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
CalloutDoc {
callback: link.callback(|x| x),
state: ExampleProps {
show_icon: false,
intent: None,
show_title: true,
},
}
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
fn update(&mut self, msg: Self::Message) -> ShouldRender {
self.state = msg;
true
}
@ -19,12 +35,97 @@ impl Component for CalloutDoc {
}
fn view(&self) -> Html {
let source = crate::include_example!();
let example_props = self.state.clone();
let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"),
"bp3-code-block"
);
html! {
<div>
<h1>{"Callout"}</h1>
<div>{source}</div>
<H1 class="docs-title">{"Callout"}</H1>
<ExampleContainer
source=source
props=Some(html! {
<CalloutProps
callback={self.callback.clone()}
props=example_props.clone()
/>
})
>
<Example with example_props />
</ExampleContainer>
</div>
}
}
}
crate::build_example_prop_component! {
CalloutProps for ExampleProps =>
fn view(&self) -> Html {
html! {
<div>
<H5>{"Props"}</H5>
<div>
<Switch
onclick=self.update_props(|props| ExampleProps {
show_icon: !props.show_icon,
..props
})
checked=self.props.show_icon
label="Show/hide icon"
/>
<Switch
onclick=self.update_props(|props| ExampleProps {
show_title: !props.show_title,
..props
})
checked=self.props.show_title
label="Show/hide title"
/>
<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

@ -1,5 +1,5 @@
use yew::prelude::*;
use yewprint::{Button,Collapse};
use yewprint::{Button, Collapse};
pub struct Example {
link: ComponentLink<Self>,

View file

@ -1,4 +1,9 @@
mod example;
use crate::ExampleContainer;
use example::*;
use yew::prelude::*;
use yewprint::H1;
pub struct CollapseDoc;
@ -19,12 +24,17 @@ impl Component for CollapseDoc {
}
fn view(&self) -> Html {
let source = crate::include_example!();
let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"),
"bp3-code-block"
);
html! {
<div>
<h1>{"Tree"}</h1>
<div>{source}</div>
<H1 class="docs-title">{"Collapse"}</H1>
<ExampleContainer source=source>
<Example />
</ExampleContainer>
</div>
}
}

View file

@ -15,6 +15,8 @@ pub enum Msg {
pub struct Props {
pub source: yew::virtual_dom::VNode,
pub children: html::Children,
#[prop_or_default]
pub props: Option<yew::virtual_dom::VNode>,
}
impl Component for ExampleContainer {
@ -36,17 +38,34 @@ impl Component for ExampleContainer {
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
// TODO: never re-render this component? How to optimize this
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<div class="docs-example-wrapper">
<div class="docs-example-frame docs-example-frame-row">
<div class="docs-example">
{self.props.children.clone()}
</div>
{
if let Some(props) = self.props.props.clone() {
html! {
<div class="docs-example-options">
{props}
</div>
}
} else {
html!()
}
}
</div>
<div class="docs-source">
<Button
icon=IconName::Code
@ -70,25 +89,47 @@ impl Component for ExampleContainer {
}
#[macro_export]
macro_rules! include_example {
() => {{
use crate::ExampleContainer;
let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"),
"bp3-code-block"
);
mod source {
// TODO: example.rs files are not formatted because of this include
include!("example.rs");
macro_rules! build_example_prop_component {
($name:ident for $prop_component:ty => $($view:tt)*) => {
#[derive(Clone, PartialEq, Properties)]
pub struct $name {
callback: Callback<$prop_component>,
props: $prop_component,
}
use source::Example;
html! {
<ExampleContainer source={source}>
<Example />
</ExampleContainer>
impl Component for $name {
type Message = ();
type Properties = Self;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
props
}
}};
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props.props || self.callback != props.callback {
self.props = props.props;
self.callback = props.callback;
true
} else {
false
}
}
$($view)*
}
impl $name {
fn update_props(
&self,
updater: impl Fn($prop_component) -> $prop_component + 'static,
) -> Callback<MouseEvent> {
let props = self.props.clone();
self.callback.clone().reform(move |_| updater(props.clone()))
}
}
};
}

View file

@ -1,4 +1,9 @@
mod example;
use crate::ExampleContainer;
use example::*;
use yew::prelude::*;
use yewprint::H1;
pub struct IconDoc;
@ -19,12 +24,17 @@ impl Component for IconDoc {
}
fn view(&self) -> Html {
let source = crate::include_example!();
let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"),
"bp3-code-block"
);
html! {
<div>
<h1>{"Icon"}</h1>
<div>{source}</div>
<H1 class="docs-title">{"Icon"}</H1>
<ExampleContainer source=source>
<Example />
</ExampleContainer>
</div>
}
}

View file

@ -19,19 +19,8 @@ macro_rules! log {
#[macro_export]
macro_rules! include_raw_html {
($file:expr, $class:expr) => {{
($file:expr $(, $class:expr)?) => {{
yew::virtual_dom::VNode::VRef(yew::web_sys::Node::from({
let div = crate::include_raw_html!(element $file);
div.set_class_name($class);
div
}))
}};
($file:expr) => {{
yew::virtual_dom::VNode::VRef(yew::web_sys::Node::from({
crate::include_raw_html!(element $file)
}))
}};
(element $file:expr) => {{
let div = web_sys::window()
.unwrap()
.document()
@ -39,7 +28,9 @@ macro_rules! include_raw_html {
.create_element("div")
.unwrap();
div.set_inner_html(include_str!($file));
$(div.set_class_name($class);)*
div
}))
}};
}

View file

@ -1,5 +1,5 @@
use yew::prelude::*;
use yewprint::*;
use yewprint::{Switch, H1};
pub struct SwitchDoc {
props: Props,
@ -30,7 +30,7 @@ impl Component for SwitchDoc {
fn view(&self) -> Html {
html! {
<div>
<h1>{"Switch"}</h1>
<H1 class="docs-title">{"Switch"}</H1>
<Switch
onclick=self.props.onclick.clone()
checked=self.props.dark_theme

View file

@ -1,6 +1,6 @@
use yew::prelude::*;
use yewprint::{Tree, Icon, NodeData, IconName, Intent, TreeData};
use yewprint::id_tree::{InsertBehavior, Node, TreeBuilder, NodeId};
use yewprint::id_tree::{InsertBehavior, Node, NodeId, TreeBuilder};
use yewprint::{Icon, IconName, Intent, NodeData, Tree, TreeData};
pub struct Example {
tree: TreeData<i32>,

View file

@ -1,4 +1,9 @@
mod example;
use crate::ExampleContainer;
use example::*;
use yew::prelude::*;
use yewprint::H1;
pub struct TreeDoc;
@ -19,12 +24,17 @@ impl Component for TreeDoc {
}
fn view(&self) -> Html {
let source = crate::include_example!();
let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"),
"bp3-code-block"
);
html! {
<div>
<h1>{"Tree"}</h1>
<div>{source}</div>
<H1 class="docs-title">{"Tree"}</H1>
<ExampleContainer source=source>
<Example />
</ExampleContainer>
</div>
}
}

View file

@ -15,6 +15,7 @@ is-it-maintained-open-issues = { repository = "cecton/yewprint" }
yew = { git = "https://github.com/yewstack/yew.git", rev = "1507c21b" }
web-sys = "0.3"
id_tree = "1.7"
yewtil = { git = "https://github.com/yewstack/yew.git", rev = "1507c21b", features = ["pure"] }
[build-dependencies]
regex = { version = "1", default-features = false, features = ["std"] }

View file

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

View file

@ -76,7 +76,7 @@ impl Component for Icon {
height={self.props.icon_size}
viewBox={format!("0 0 {x} {x}", x=pixel_grid_size)}
>
<desc>{self.props.title.clone().unwrap_or_else(|| icon_string)}</desc>
<desc>{self.props.title.clone().unwrap_or(icon_string)}</desc>
{
paths.iter()
.map(|x| html! {

View file

@ -1,6 +1,7 @@
mod buttons;
mod callout;
mod collapse;
mod html_elements;
mod icon;
mod menu;
mod switch;
@ -9,6 +10,7 @@ mod tree;
pub use buttons::*;
pub use callout::*;
pub use collapse::*;
pub use html_elements::*;
pub use icon::*;
pub use id_tree;
pub use menu::*;
@ -43,7 +45,7 @@ impl From<bool> for ConditionalClass {
}
impl ConditionalClass {
pub fn map_some(&self, value: &'static str) -> Option<&'static str> {
pub fn map_some<T>(&self, value: T) -> Option<T> {
if self.0 {
Some(value)
} else {

View file

@ -113,7 +113,7 @@ impl Component for MenuItem {
"bp3-menu-item",
self.props.active.map_some("bp3-active"),
self.props.intent
.and(self.props.active.map_some(Intent::Primary.as_ref())),
.or_else(|| self.props.active.map_some(Intent::Primary)),
self.props.class.clone(),
)
onclick={self.props.onclick.clone()}