mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
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:
parent
0f7c2bb276
commit
ce3eeec92b
@ -7,5 +7,5 @@
|
||||
<link data-trunk rel="sass" href="index.scss" />
|
||||
</head>
|
||||
|
||||
<body></body>
|
||||
<body class="page"></body>
|
||||
</html>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
40
examples/communication_child_to_parent/src/child.rs
Normal file
40
examples/communication_child_to_parent/src/child.rs
Normal 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>
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
67
examples/communication_child_to_parent/src/parent.rs
Normal file
67
examples/communication_child_to_parent/src/parent.rs
Normal 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>
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,5 +7,5 @@
|
||||
<link data-trunk rel="sass" href="index.scss" />
|
||||
</head>
|
||||
|
||||
<body></body>
|
||||
<body class="page"></body>
|
||||
</html>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>>>
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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>
|
||||
}
|
||||
}
|
||||
@ -7,5 +7,5 @@
|
||||
<link data-trunk rel="sass" href="index.scss" />
|
||||
</head>
|
||||
|
||||
<body></body>
|
||||
<body class="page"></body>
|
||||
</html>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>>>
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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>
|
||||
}
|
||||
}
|
||||
@ -7,5 +7,5 @@
|
||||
<link data-trunk rel="sass" href="index.scss" />
|
||||
</head>
|
||||
|
||||
<body></body>
|
||||
<body class="page"></body>
|
||||
</html>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
32
examples/communication_parent_to_child/src/child.rs
Normal file
32
examples/communication_parent_to_child/src/child.rs
Normal 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>
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
53
examples/communication_parent_to_child/src/parent.rs
Normal file
53
examples/communication_parent_to_child/src/parent.rs
Normal 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>
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user