Communication Examples Style and Code Clean-up (#3180)

* Button Styles

- Rounded
- Cursor change
- Hover colour

* Centred and Outlined Components

* Realigned Everything

* Amended Margins

* Re-organised File Structure

* Tailwind Conversion

* cargo fmt

* Reduced Button Rounding

* Nightly fmt

* Reorganised rs structure

* New Styling for Grandchild with Grandparent

* Rearranged rs

* Styles for grandparent to grandchild

* Centered

* Arranged rs

* Styles for Parent to Child

* Refactored Child to Parent

* Refactored Parent to Child

* Refactored Grandchild with Grandparent

* Refactored Grandparent to Grandchild

* Grammar fix
This commit is contained in:
Jedd Dryden 2023-04-04 02:50:59 +10:00 committed by GitHub
parent 0f7c2bb276
commit ce3eeec92b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1078 additions and 557 deletions

View File

@ -7,5 +7,5 @@
<link data-trunk rel="sass" href="index.scss" />
</head>
<body></body>
<body class="page"></body>
</html>

View File

@ -1,47 +1,149 @@
body {
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
font-size: 16pt;
}
.page {
min-height: 100vh;
min-width: 100vw;
.button {
background-color: #e3891b; /* Green */
border: 0;
color: white;
padding: 14px 14px;
text-align: center;
font-size: 16pt;
margin: 2px 2px;
width: 100px;
}
line-height: 1.5;
tab-size: 4;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-feature-settings: normal;
.app {
display: flex;
justify-content: center;
flex-direction: row;
}
.button-panel {
display: flex;
justify-content: center;
}
.spacer {
height: 10px;
margin: 0;
line-height: inherit;
}
.parent {
display: flex;
flex-direction: column;
row-gap: 10px;
}
color: rgb(244 244 245);
background-color: rgb(24 24 27);
.child {
display: flex;
justify-content: center;
column-gap: 10px;
align-items: center;
flex-direction: column;
display: flex;
min-height: 100vh;
min-width: 100vw;
}
.child-name {
margin-top: 16px;
min-width: 75px;
.title {
font-size: 2.25rem;
line-height: 2.5rem;
font-weight: inherit;
margin: 0;
margin-bottom: 2rem;
}
// Bodies
.parent-body, .child-body {
border-width: 4px;
border-radius: 1rem;
border-style: solid;
}
.parent-body {
border-color: rgb(22 163 74);
}
.child-body {
border-color: rgb(249 115 22);
flex-grow: 1;
}
// Tags
.parent-tag, .child-tag {
font-weight: 500;
padding-bottom: 0.25rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem;
line-height: 24px;
}
.parent-tag {
background-color: rgb(22 163 74);
}
.child-tag {
background-color: rgb(249 115 22);
}
// Content
.parent-content {
padding-top: 0.75rem;
padding-bottom: 1.25rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
flex-direction: column;
display: flex;
}
.parent-content > span {
font-size: 1.25rem;
line-height: 1.75rem;
}
.parent-content > span > span {
font-weight: 700;
}
.parent-content > span:nth-child(2) {
margin-top: 0.75rem;
margin-bottom: 0.75rem;
}
.parent-content > div {
column-gap: 1.25rem;
display: flex;
margin-top: 0.75rem;
}
.child-content {
padding-top: 1.25rem;
padding-bottom: 1.25rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
justify-content: space-between;
align-items: center;
display: flex;
}
.child-content > span {
font-size: 1.25rem;
line-height: 1.75rem;
}
.child-content > button {
font-weight: 500;
font-size: 1.125rem;
line-height: 1.75rem;
text-transform: none;
font-family: inherit;
padding-top: 0.25rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
background-color: rgb(249 115 22);
border-radius: 0.75rem;
border-width: 0;
color: rgb(244 244 245);
cursor: pointer;
}
.child-content > button:hover {
background-color: rgb(194 65 12);
}

View File

@ -0,0 +1,40 @@
use super::*;
/// The `Child` component is the child of the `Parent` component, and will send updates to the
/// parent using a Callback.
pub struct Child;
#[derive(Clone, PartialEq, Properties)]
pub struct ChildProps {
pub name: AttrValue,
pub on_clicked: Callback<AttrValue>,
}
impl Component for Child {
type Message = ();
type Properties = ChildProps;
fn create(_ctx: &Context<Self>) -> Self {
Self {}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let name = format!("I'm {}.", ctx.props().name);
let my_name = ctx.props().name.clone();
// Here we emit the callback to the parent component, whenever the button is clicked.
let onclick = ctx.props().on_clicked.reform(move |_| my_name.clone());
html! {
<div class="child-body">
<div class="child-tag">
<span>{ "Child" }</span>
</div>
<div class="child-content">
<span>{ name }</span>
<button {onclick}>{"Click"}</button>
</div>
</div>
}
}
}

View File

@ -1,101 +1,9 @@
use child::Child;
use parent::Parent;
use yew::{html, AttrValue, Callback, Component, Context, Html, Properties};
/// The `Parent` component holds some state that is updated when its children are clicked
pub struct Parent {
/// The total number of clicks received
total_clicks: u32,
/// The name of the child that was last clicked
last_updated: Option<AttrValue>,
}
pub enum Msg {
ButtonClick(AttrValue),
}
impl Component for Parent {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self {
total_clicks: 0,
last_updated: None,
}
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::ButtonClick(childs_name) => {
// Keep track of the name of the child that was clicked
self.last_updated = Some(childs_name);
// Increment the total number of clicks
self.total_clicks += 1;
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let msg = format!("My children have been clicked {} times", self.total_clicks);
let last_updated_msg = if let Some(last_updated) = self.last_updated.as_ref() {
format!("The last child that was clicked was {last_updated}")
} else {
"No child has been clicked yet".to_string()
};
let on_clicked = ctx.link().callback(Msg::ButtonClick);
html! {
<div class="app">
<div class="parent">
<h2>{ "Child-to-Parent communication example" }</h2>
<div>{msg}</div>
<div>{last_updated_msg}</div>
<div class="spacer" />
<Child name="Alice" on_clicked={on_clicked.clone()} />
<Child name="Bob" {on_clicked} />
</div>
</div>
}
}
}
/// The `Child` component is the child of the `Parent` component, and will send updates to the
/// parent using a Callback.
pub struct Child;
#[derive(Clone, PartialEq, Properties)]
pub struct ChildProps {
pub name: AttrValue,
pub on_clicked: Callback<AttrValue>,
}
impl Component for Child {
type Message = ();
type Properties = ChildProps;
fn create(_ctx: &Context<Self>) -> Self {
Self {}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let name = format!("{}:", ctx.props().name);
let my_name = ctx.props().name.clone();
// Here we emit the callback to the parent component, whenever the button is clicked.
let onclick = ctx.props().on_clicked.reform(move |_| my_name.clone());
html! {
<div class="child">
<div class="child-name">{name}</div>
<div class="button-panel">
<button class="button" {onclick}>{"Click here!"}</button>
</div>
</div>
}
}
}
mod child;
mod parent;
fn main() {
yew::Renderer::<Parent>::new().render();

View File

@ -0,0 +1,67 @@
use super::*;
pub enum Msg {
ButtonClick(AttrValue),
}
/// The `Parent` component holds some state that is updated when its children are clicked
pub struct Parent {
/// The total number of clicks received
total_clicks: u32,
/// The name of the child that was last clicked
last_updated: Option<AttrValue>,
}
impl Component for Parent {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self {
total_clicks: 0,
last_updated: None,
}
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::ButtonClick(childs_name) => {
// Keep track of the name of the child that was clicked
self.last_updated = Some(childs_name);
// Increment the total number of clicks
self.total_clicks += 1;
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let last_updated_msg = if let Some(last_updated) = self.last_updated.as_ref() {
format!("The last child you clicked was {last_updated}.")
} else {
"Waiting for you to click a child...".to_string()
};
let on_clicked = ctx.link().callback(Msg::ButtonClick);
html! {
<div class="parent">
<div>
<h2 class="title">{ "Child-to-Parent Communication Example" }</h2>
<div class="parent-body">
<div class="parent-tag">
<span>{ "Parent" }</span>
</div>
<div class="parent-content">
<span>{ "My children have been clicked " }<span>{ self.total_clicks }</span>{ " times." }</span>
<span>{ last_updated_msg }</span>
<div>
<Child name="Alice" on_clicked={on_clicked.clone()} />
<Child name="Bob" {on_clicked} />
</div>
</div>
</div>
</div>
</div>
}
}
}

View File

@ -7,5 +7,5 @@
<link data-trunk rel="sass" href="index.scss" />
</head>
<body></body>
<body class="page"></body>
</html>

View File

@ -1,53 +1,179 @@
body {
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
font-size: 16pt;
.page {
min-height: 100vh;
min-width: 100vw;
line-height: 1.5;
tab-size: 4;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-feature-settings: normal;
margin: 0;
line-height: inherit;
}
.button {
background-color: #e3891b; /* Green */
border: 0;
color: white;
padding: 14px 14px;
text-align: center;
font-size: 16pt;
margin: 2px 2px;
width: 100px;
}
.grandparent {
color: rgb(244 244 245);
background-color: rgb(24 24 27);
.app {
display: flex;
justify-content: center;
flex-direction: row;
}
.button-panel {
display: flex;
justify-content: center;
}
.parent {
display: flex;
flex-direction: column;
row-gap: 10px;
}
.child {
display: flex;
justify-content: center;
column-gap: 10px;
}
.status-message {
display: flex;
align-items: center;
}
.spacer {
height: 10px;
}
.child-name {
flex-direction: column;
display: flex;
min-height: 100vh;
min-width: 100vw;
}
.title {
font-size: 2.25rem;
line-height: 2.5rem;
font-weight: inherit;
margin: 0;
margin-bottom: 2rem;
}
// Bodies
.grandparent-body, .parent-body, .child-body {
border-width: 4px;
border-radius: 1rem;
border-style: solid;
}
.grandparent-body {
border-color: rgb(22 163 74);
}
.parent-body, .child-body {
flex-grow: 1;
}
.parent-body {
border-color: rgb(249 115 22);
}
.child-body {
border-color: rgb(147 51 234);
margin-top: 0.5rem;
}
.child-body > div:nth-child(2) {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.child-body > div:nth-child(2) > span {
font-size: 1.25rem;
line-height: 1.75rem;
}
.child-body > div:nth-child(2) > span > span {
font-weight: 700;
}
// Tags
.grandparent-tag, .parent-tag, .child-tag {
font-weight: 500;
padding-bottom: 0.25rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem;
line-height: 24px;
}
.grandparent-tag {
background-color: rgb(22 163 74);
}
.parent-tag {
background-color: rgb(249 115 22);
}
.child-tag {
background-color: rgb(147 51 234);
}
// Content
.grandparent-content {
padding-top: 0.75rem;
padding-bottom: 1.25rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
flex-direction: column;
display: flex;
}
.grandparent-content > span {
font-size: 1.25rem;
line-height: 1.75rem;
}
.grandparent-content > span:nth-child(2) {
margin-top: 0.75rem;
margin-bottom: 0.75rem;
}
.grandparent-content > span > span {
font-weight: 700;
}
.parent-content {
padding-top: 0.75rem;
padding-bottom: 1.25rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
column-gap: 1.25rem;
display: flex;
}
.child-content {
padding-bottom: 1.25rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
justify-content: space-between;
align-items: center;
min-width: 75px;
display: flex;
}
.child-content > span {
font-size: 1.25rem;
line-height: 1.75rem;
}
.child-content > button {
font-weight: 500;
font-size: 1.125rem;
line-height: 1.75rem;
text-transform: none;
font-family: inherit;
padding-top: 0.25rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
background-color: rgb(147 51 234);
border-radius: 0.75rem;
border-width: 0;
color: rgb(244 244 245);
cursor: pointer;
}
.child-content > button:hover {
background-color: rgb(107 33 168);
}

View File

@ -0,0 +1,65 @@
use super::*;
/// The `Child` component is the child of the `Parent` component, and will send and receive updates
/// to/from the grandparent using the context.
pub struct Child {
state: Rc<AppState>,
_listener: ContextHandle<Rc<AppState>>,
}
pub enum ChildMsg {
ContextChanged(Rc<AppState>),
}
#[derive(Clone, Eq, PartialEq, Properties)]
pub struct ChildProps {
pub name: AttrValue,
}
impl Component for Child {
type Message = ChildMsg;
type Properties = ChildProps;
fn create(ctx: &Context<Self>) -> Self {
// Here we fetch the shared state from the context. For a demonstration on the use of
// context in a functional component, have a look at the `examples/contexts` code.
let (state, _listener) = ctx
.link()
.context::<Rc<AppState>>(ctx.link().callback(ChildMsg::ContextChanged))
.expect("context to be set");
Self { state, _listener }
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
ChildMsg::ContextChanged(state) => {
self.state = state;
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let my_name = ctx.props().name.clone();
let name = format!("I'm {my_name}.");
// Here we emit the callback to the grandparent component, whenever the button is clicked.
let onclick = self.state.child_clicked.reform(move |_| (my_name.clone()));
html! {
<div class="child-body">
<div class="child-tag">
<span>{ "Child" }</span>
</div>
<div>
<span>{ "We've been clicked " }<span>{ self.state.total_clicks }</span>{ " times." }</span>
</div>
<div class="child-content">
<span>{ name }</span>
<button {onclick}>{"Click"}</button>
</div>
</div>
}
}
}

View File

@ -0,0 +1,66 @@
use super::*;
pub enum Msg {
ButtonClick(AttrValue),
}
/// Our top-level (grandparent) component that holds a reference to the shared state.
pub struct GrandParent {
state: Rc<AppState>,
}
impl Component for GrandParent {
type Message = Msg;
type Properties = ();
fn create(ctx: &Context<Self>) -> Self {
let child_clicked = ctx.link().callback(Msg::ButtonClick);
let state = Rc::new(AppState {
total_clicks: 0,
child_clicked,
last_clicked: None,
});
Self { state }
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::ButtonClick(childs_name) => {
// Update the shared state
let mut shared_state = Rc::make_mut(&mut self.state);
shared_state.total_clicks += 1;
shared_state.last_clicked = Some(childs_name);
true
}
}
}
fn view(&self, _ctx: &Context<Self>) -> Html {
let app_state = self.state.clone();
let detail_msg = if let Some(last_clicked) = &self.state.last_clicked {
format!("The last child you clicked was {last_clicked}.")
} else {
"Waiting for you to click a grandchild...".to_string()
};
html! {
<ContextProvider<Rc<AppState>> context={app_state}>
<div class="grandparent">
<div>
<h2 class="title">{ "Grandchild-with-Grandparent Communication Example" }</h2>
<div class="grandparent-body">
<div class="grandparent-tag">
<span>{ "Grandparent" }</span>
</div>
<div class="grandparent-content">
<span>{ "My grandchildren have been clicked " }<span>{ self.state.total_clicks }</span>{ " times." }</span>
<span>{detail_msg}</span>
<Parent />
</div>
</div>
</div>
</div>
</ContextProvider<Rc<AppState>>>
}
}
}

View File

@ -1,5 +1,13 @@
use std::rc::Rc;
use child::Child;
use grandparent::GrandParent;
use parent::Parent;
mod child;
mod grandparent;
mod parent;
use yew::{
function_component, html, AttrValue, Callback, Component, Context, ContextHandle,
ContextProvider, Html, Properties,
@ -17,147 +25,6 @@ pub struct AppState {
last_clicked: Option<AttrValue>,
}
/// Our top-level (grandparent) component that holds a reference to the shared state.
pub struct GrandParent {
state: Rc<AppState>,
}
pub enum Msg {
ButtonClick(AttrValue),
}
impl Component for GrandParent {
type Message = Msg;
type Properties = ();
fn create(ctx: &Context<Self>) -> Self {
let child_clicked = ctx.link().callback(Msg::ButtonClick);
let state = Rc::new(AppState {
total_clicks: 0,
child_clicked,
last_clicked: None,
});
Self { state }
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::ButtonClick(childs_name) => {
// Update the shared state
let mut shared_state = Rc::make_mut(&mut self.state);
shared_state.total_clicks += 1;
shared_state.last_clicked = Some(childs_name);
true
}
}
}
fn view(&self, _ctx: &Context<Self>) -> Html {
let app_state = self.state.clone();
let msg = format!(
"My grandchildren have been clicked {} times",
self.state.total_clicks
);
let detail_msg = if let Some(last_clicked) = &self.state.last_clicked {
format!("{last_clicked} was clicked last")
} else {
"No one has been clicked yet".to_string()
};
html! {
<ContextProvider<Rc<AppState>> context={app_state}>
<div class="app">
<div class="parent">
<h2>{ "Grandchild-with-Grandparent communication example" }</h2>
<div>{msg}</div>
<div>{detail_msg}</div>
<div class="spacer" />
<Parent />
</div>
</div>
</ContextProvider<Rc<AppState>>>
}
}
}
/// The `Parent` component is the parent of the `Child` component. It has no logic, and is here to
/// show there is no direct relation between grandchild and grandparent.
#[function_component]
fn Parent() -> Html {
html! {
<>
<Child name="Alice" />
<Child name="Bob" />
</>
}
}
/// The `Child` component is the child of the `Parent` component, and will send and receive updates
/// to/from the grandparent using the context.
pub struct Child {
state: Rc<AppState>,
_listener: ContextHandle<Rc<AppState>>,
}
pub enum ChildMsg {
ContextChanged(Rc<AppState>),
}
#[derive(Clone, Eq, PartialEq, Properties)]
pub struct ChildProps {
pub name: AttrValue,
}
impl Component for Child {
type Message = ChildMsg;
type Properties = ChildProps;
fn create(ctx: &Context<Self>) -> Self {
// Here we fetch the shared state from the context. For a demonstration on the use of
// context in a functional component, have a look at the `examples/contexts` code.
let (state, _listener) = ctx
.link()
.context::<Rc<AppState>>(ctx.link().callback(ChildMsg::ContextChanged))
.expect("context to be set");
Self { state, _listener }
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
ChildMsg::ContextChanged(state) => {
self.state = state;
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let my_name = ctx.props().name.clone();
let name = format!("{my_name}: ");
// Here we emit the callback to the grandparent component, whenever the button is clicked.
let onclick = self.state.child_clicked.reform(move |_| (my_name.clone()));
let msg = format!("We've been clicked: {} times", self.state.total_clicks);
html! {
<div class="child">
<div class="child-name">
<div>{name}</div>
</div>
<div class="button-panel">
<button class="button" {onclick}>{"Click here"}</button>
</div>
<div class="status-message">
<div>{msg}</div>
</div>
</div>
}
}
}
fn main() {
yew::Renderer::<GrandParent>::new().render();
}

View File

@ -0,0 +1,18 @@
use super::*;
/// The `Parent` component is the parent of the `Child` component. It has no logic, and is here to
/// show there is no direct relation between grandchild and grandparent.
#[function_component]
pub fn Parent() -> Html {
html! {
<div class="parent-body">
<div class="parent-tag">
<span>{ "Parent" }</span>
</div>
<div class="parent-content">
<Child name="Alice" />
<Child name="Bob" />
</div>
</div>
}
}

View File

@ -7,5 +7,5 @@
<link data-trunk rel="sass" href="index.scss" />
</head>
<body></body>
<body class="page"></body>
</html>

View File

@ -1,38 +1,167 @@
body {
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
font-size: 16pt;
.page {
min-height: 100vh;
min-width: 100vw;
line-height: 1.5;
tab-size: 4;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-feature-settings: normal;
margin: 0;
line-height: inherit;
}
.button {
background-color: #e3891b; /* Green */
border: 0;
color: white;
padding: 14px 14px;
text-align: center;
font-size: 16pt;
margin: 2px 2px;
width: 100px;
}
.grandparent {
color: rgb(244 244 245);
background-color: rgb(24 24 27);
.app {
display: flex;
justify-content: center;
flex-direction: row;
}
.button-panel {
align-items: center;
flex-direction: column;
display: flex;
justify-content: center;
min-height: 100vh;
min-width: 100vw;
}
.parent {
display: flex;
flex-direction: column;
row-gap: 10px;
.title {
font-size: 2.25rem;
line-height: 2.5rem;
font-weight: inherit;
margin: 0;
margin-bottom: 2rem;
}
.child {
// Bodies
.grandparent-body, .parent-body, .child-body {
border-width: 4px;
border-radius: 1rem;
border-style: solid;
}
.grandparent-body {
border-color: rgb(22 163 74);
}
.parent-body, .child-body {
flex-grow: 1;
}
.parent-body {
border-color: rgb(249 115 22);
}
.child-body {
border-color: rgb(147 51 234);
margin-top: 0.5rem;
}
.child-body > div:nth-child(2) {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.child-body > div:nth-child(2) > span {
font-size: 1.25rem;
line-height: 1.75rem;
}
.child-body > div:nth-child(2) > span > span {
font-weight: 700;
}
// Tags
.grandparent-tag, .parent-tag, .child-tag {
font-weight: 500;
padding-bottom: 0.25rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem;
line-height: 24px;
}
.grandparent-tag {
background-color: rgb(22 163 74);
}
.parent-tag {
background-color: rgb(249 115 22);
}
.child-tag {
background-color: rgb(147 51 234);
}
// Content
.grandparent-content {
padding-top: 1.25rem;
padding-bottom: 1.25rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
flex-direction: column;
display: flex;
justify-content: center;
column-gap: 10px;
}
.grandparent-content > button {
font-weight: 500;
font-size: 1.125rem;
line-height: 1.75rem;
text-transform: none;
font-family: inherit;
padding-top: 0.25rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
margin-bottom: 1.25rem;
background-color: rgb(22 163 74);
border-radius: 0.75rem;
border-width: 0;
color: rgb(244 244 245);
cursor: pointer;
}
.grandparent-content > button:hover {
background-color: rgb(22 101 52);
}
.parent-content {
padding-top: 0.75rem;
padding-bottom: 1.25rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
column-gap: 1.25rem;
display: flex;
}
.child-content {
padding-bottom: 1.25rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
justify-content: space-between;
align-items: center;
display: flex;
}
.child-content > span {
font-size: 1.25rem;
line-height: 1.75rem;
}

View File

@ -0,0 +1,51 @@
use super::*;
/// The `Child` component is the child of the `Parent` component, and will receive updates from the
/// grandparent using the context.
pub struct Child {
state: Rc<AppState>,
_listener: ContextHandle<Rc<AppState>>,
}
pub enum ChildMsg {
ContextChanged(Rc<AppState>),
}
impl Component for Child {
type Message = ChildMsg;
type Properties = ();
fn create(ctx: &Context<Self>) -> Self {
// Here we fetch the shared state from the context. For a demonstration on the use of
// context in a functional component, have a look at the `examples/contexts` code.
let (state, _listener) = ctx
.link()
.context::<Rc<AppState>>(ctx.link().callback(ChildMsg::ContextChanged))
.expect("context to be set");
Self { state, _listener }
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
ChildMsg::ContextChanged(state) => {
self.state = state;
true
}
}
}
fn view(&self, _ctx: &Context<Self>) -> Html {
html! {
<div class="child-body">
<div class="child-tag">
<span>{ "Child" }</span>
</div>
<div class="child-content">
<span>{ "My grandparent has been clicked " }<span>{ self.state.total_clicks }</span>{ " times." }</span>
</div>
</div>
}
}
}

View File

@ -0,0 +1,54 @@
use super::*;
/// Our top-level (grandparent) component that holds a reference to the shared state.
pub struct GrandParent {
state: Rc<AppState>,
}
pub enum Msg {
ButtonClick,
}
impl Component for GrandParent {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
let state = Rc::new(AppState { total_clicks: 0 });
Self { state }
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::ButtonClick => {
Rc::make_mut(&mut self.state).total_clicks += 1;
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let onclick = ctx.link().callback(|_| Msg::ButtonClick);
let app_state = self.state.clone();
html! {
<ContextProvider<Rc<AppState>> context={app_state}>
<div class="grandparent">
<div>
<h2 class="title">{ "Grandparent-to-Grandchild Communication Example" }</h2>
<div class="grandparent-body">
<div class="grandparent-tag">
<span>{ "Grandparent" }</span>
</div>
<div class="grandparent-content">
<button {onclick}>{"Click"}</button>
<Parent />
</div>
</div>
</div>
</div>
</ContextProvider<Rc<AppState>>>
}
}
}

View File

@ -1,5 +1,13 @@
use std::rc::Rc;
use child::Child;
use grandparent::GrandParent;
use parent::Parent;
mod child;
mod grandparent;
mod parent;
use yew::{function_component, html, Component, Context, ContextHandle, ContextProvider, Html};
/// This is the shared state between the parent and child components.
@ -9,111 +17,6 @@ pub struct AppState {
total_clicks: u32,
}
/// Our top-level (grandparent) component that holds a reference to the shared state.
pub struct GrandParent {
state: Rc<AppState>,
}
pub enum Msg {
ButtonClick,
}
impl Component for GrandParent {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
let state = Rc::new(AppState { total_clicks: 0 });
Self { state }
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::ButtonClick => {
Rc::make_mut(&mut self.state).total_clicks += 1;
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let onclick = ctx.link().callback(|_| Msg::ButtonClick);
let app_state = self.state.clone();
html! {
<ContextProvider<Rc<AppState>> context={app_state}>
<div class="app">
<div class="parent">
<h2>{ "Grandparent-to-Grandchild communication example" }</h2>
<div class="button-panel">
<button class="button" {onclick}>{"Click here!"}</button>
</div>
<Parent />
</div>
</div>
</ContextProvider<Rc<AppState>>>
}
}
}
/// The `Parent` component is the parent of the `Child` component. It has no logic, and is here to
/// show there is no direct relation between grandchild and grandparent.
#[function_component]
fn Parent() -> Html {
html! {
<Child />
}
}
/// The `Child` component is the child of the `Parent` component, and will receive updates from the
/// grandparent using the context.
pub struct Child {
state: Rc<AppState>,
_listener: ContextHandle<Rc<AppState>>,
}
pub enum ChildMsg {
ContextChanged(Rc<AppState>),
}
impl Component for Child {
type Message = ChildMsg;
type Properties = ();
fn create(ctx: &Context<Self>) -> Self {
// Here we fetch the shared state from the context. For a demonstration on the use of
// context in a functional component, have a look at the `examples/contexts` code.
let (state, _listener) = ctx
.link()
.context::<Rc<AppState>>(ctx.link().callback(ChildMsg::ContextChanged))
.expect("context to be set");
Self { state, _listener }
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
ChildMsg::ContextChanged(state) => {
self.state = state;
true
}
}
}
fn view(&self, _ctx: &Context<Self>) -> Html {
let msg = format!(
"My grandparent has been clicked {} times",
self.state.total_clicks
);
html! {
<div class="child">
<div>{msg}</div>
</div>
}
}
}
fn main() {
yew::Renderer::<GrandParent>::new().render();
}

View File

@ -0,0 +1,17 @@
use super::*;
/// The `Parent` component is the parent of the `Child` component. It has no logic, and is here to
/// show there is no direct relation between grandchild and grandparent.
#[function_component]
pub fn Parent() -> Html {
html! {
<div class="parent-body">
<div class="parent-tag">
<span>{ "Parent" }</span>
</div>
<div class="parent-content">
<Child />
</div>
</div>
}
}

View File

@ -7,5 +7,5 @@
<link data-trunk rel="sass" href="index.scss" />
</head>
<body></body>
<body class="page"></body>
</html>

View File

@ -1,38 +1,130 @@
body {
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
font-size: 16pt;
}
.page {
min-height: 100vh;
min-width: 100vw;
.button {
background-color: #e3891b; /* Green */
border: 0;
color: white;
padding: 14px 14px;
text-align: center;
font-size: 16pt;
margin: 2px 2px;
width: 100px;
}
line-height: 1.5;
tab-size: 4;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-feature-settings: normal;
.app {
display: flex;
justify-content: center;
flex-direction: row;
}
.button-panel {
display: flex;
justify-content: center;
margin: 0;
line-height: inherit;
}
.parent {
display: flex;
flex-direction: column;
row-gap: 10px;
color: rgb(244 244 245);
background-color: rgb(24 24 27);
justify-content: center;
align-items: center;
flex-direction: column;
display: flex;
min-height: 100vh;
min-width: 100vw;
}
.child {
.title {
font-size: 2.25rem;
line-height: 2.5rem;
font-weight: inherit;
margin: 0;
margin-bottom: 2rem;
}
// Bodies
.parent-body, .child-body {
border-width: 4px;
border-radius: 1rem;
border-style: solid;
}
.parent-body {
border-color: rgb(22 163 74);
}
.child-body {
border-color: rgb(249 115 22);
flex-grow: 1;
}
// Tags
.parent-tag, .child-tag {
font-weight: 500;
padding-bottom: 0.25rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem;
line-height: 24px;
}
.parent-tag {
background-color: rgb(22 163 74);
}
.child-tag {
background-color: rgb(249 115 22);
}
// Content
.parent-content {
padding-top: 1.25rem;
padding-bottom: 1.25rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
flex-direction: column;
display: flex;
justify-content: center;
column-gap: 10px;
}
.parent-content > button {
font-weight: 500;
font-size: 1.125rem;
line-height: 1.75rem;
text-transform: none;
font-family: inherit;
padding-top: 0.25rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
margin-bottom: 1.25rem;
background-color: rgb(22 163 74);
border-radius: 0.75rem;
border-width: 0;
color: rgb(244 244 245);
cursor: pointer;
}
.parent-content > button:hover {
background-color: rgb(22 101 52);
}
.child-content {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.child-content > span {
font-size: 1.25rem;
line-height: 1.75rem;
}
.child-content > span > span {
font-weight: 700;
}

View File

@ -0,0 +1,32 @@
use super::*;
/// The `Child` component is the child of the `Parent` component, and will receive updates from the
/// parent using properties.
pub struct Child;
#[derive(Clone, Eq, PartialEq, Properties)]
pub struct ChildProps {
pub clicks: u32,
}
impl Component for Child {
type Message = ();
type Properties = ChildProps;
fn create(_ctx: &Context<Self>) -> Self {
Self {}
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<div class="child-body">
<div class="child-tag">
<span>{ "Child" }</span>
</div>
<div class="child-content">
<span>{ "My parent has been clicked " }<span>{ ctx.props().clicks }</span>{ " times." }</span>
</div>
</div>
}
}
}

View File

@ -1,78 +1,9 @@
use child::Child;
use parent::Parent;
use yew::{html, Component, Context, Html, Properties};
/// The `Parent` component holds some state that is passed down to the children.
pub struct Parent {
/// The total number of clicks received
nr_of_clicks: u32,
}
pub enum Msg {
ButtonClick,
}
impl Component for Parent {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self { nr_of_clicks: 0 }
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::ButtonClick => {
self.nr_of_clicks += 1;
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let onclick = ctx.link().callback(|_| Msg::ButtonClick);
// Here we pass down "our" nr_of_clicks to the child by setting the "clicks" property.
let clicks = self.nr_of_clicks;
html! {
<div class="app">
<div class="parent">
<h2>{ "Parent-to-Child communication example" }</h2>
<div class="button-panel">
<button class="button" {onclick}>{"Click here!"}</button>
</div>
<Child {clicks} />
</div>
</div>
}
}
}
/// The `Child` component is the child of the `Parent` component, and will receive updates from the
/// parent using properties.
pub struct Child;
#[derive(Clone, Eq, PartialEq, Properties)]
pub struct ChildProps {
pub clicks: u32,
}
impl Component for Child {
type Message = ();
type Properties = ChildProps;
fn create(_ctx: &Context<Self>) -> Self {
Self {}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let msg = format!("My parent has been clicked {} times", ctx.props().clicks);
html! {
<div class="child">
<div>{msg}</div>
</div>
}
}
}
mod child;
mod parent;
fn main() {
yew::Renderer::<Parent>::new().render();

View File

@ -0,0 +1,53 @@
use super::*;
/// The `Parent` component holds some state that is passed down to the children.
pub struct Parent {
/// The total number of clicks received
nr_of_clicks: u32,
}
pub enum Msg {
ButtonClick,
}
impl Component for Parent {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self { nr_of_clicks: 0 }
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::ButtonClick => {
self.nr_of_clicks += 1;
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let onclick = ctx.link().callback(|_| Msg::ButtonClick);
// Here we pass down "our" nr_of_clicks to the child by setting the "clicks" property.
let clicks = self.nr_of_clicks;
html! {
<div class="parent">
<div>
<h2 class="title">{ "Parent-to-Child Communication Example" }</h2>
<div class="parent-body">
<div class="parent-tag">
<span>{ "Parent" }</span>
</div>
<div class="parent-content">
<button {onclick}>{"Click"}</button>
<Child {clicks} />
</div>
</div>
</div>
</div>
}
}
}