Skip to content

Commit

Permalink
initial experimental component API example
Browse files Browse the repository at this point in the history
  • Loading branch information
glennsl committed May 1, 2021
1 parent e1d82f5 commit 262527b
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ members = [
"examples/custom_elements",
"examples/drop_zone",
"examples/el_key",
"examples/experimental_component",
"examples/graphql",
"examples/i18n",
"examples/markdown",
Expand Down
11 changes: 11 additions & 0 deletions examples/experimental_component/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "experimental_component"
version = "0.1.0"
authors = ["glennsl"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
seed = {path = "../../"}
27 changes: 27 additions & 0 deletions examples/experimental_component/Makefile.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
extend = "../../Makefile.toml"

# ---- BUILD ----

[tasks.build]
alias = "default_build"

[tasks.build_release]
alias = "default_build_release"

# ---- START ----

[tasks.start]
alias = "default_start"

[tasks.start_release]
alias = "default_start_release"

# ---- TEST ----

[tasks.test_firefox]
alias = "default_test_firefox"

# ---- LINT ----

[tasks.clippy]
alias = "default_clippy"
16 changes: 16 additions & 0 deletions examples/experimental_component/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## Experimental component API example

Demonstrates a component API that has:

- Labeled required properties, ensured to be present at compile-time
- Labeled optional properties
- Polymorphic for all properties
- A convenient consumer interface, through a stupidly simple macro that is very easy to understand

---

```bash
cargo make start
```

Open [127.0.0.1:8000](http://127.0.0.1:8000) in your browser.
18 changes: 18 additions & 0 deletions examples/experimental_component/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Experimental component API example</title>
</head>

<body>
<section id="app"></section>
<script type="module">
import init from '/pkg/package.js';
init('/pkg/package_bg.wasm');
</script>
</body>

</html>
94 changes: 94 additions & 0 deletions examples/experimental_component/src/button.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#![allow(dead_code)]

use seed::{prelude::*, *};
use std::borrow::Cow;
use std::rc::Rc;

pub struct Button<S> {
pub label: S,
}

impl<S: Into<Cow<'static, str>>> Button<S> {
pub fn into_component<Ms>(self) -> ButtonComponent<Ms> {
ButtonComponent {
label: self.label.into(),
outlined: false,
disabled: false,
on_clicks: Vec::new(),
}
}
}

pub struct ButtonComponent<Ms: 'static> {
label: Cow<'static, str>,
outlined: bool,
disabled: bool,
on_clicks: Vec<Rc<dyn Fn() -> Ms>>,
}

impl<Ms> ButtonComponent<Ms> {
pub const fn outlined(mut self, outlined: bool) -> Self {
self.outlined = outlined;
self
}

pub const fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}

pub fn on_click(mut self, on_click: impl FnOnce() -> Ms + Clone + 'static) -> Self {
self.on_clicks.push(Rc::new(move || on_click.clone()()));
self
}

pub fn into_node(self) -> Node<Ms> {
let attrs = {
let mut attrs = attrs! {};

if self.disabled {
attrs.add(At::from("aria-disabled"), true);
attrs.add(At::TabIndex, -1);
attrs.add(At::Disabled, AtValue::None);
}

attrs
};

let css = {
let color = "teal";

let mut css = style! {
St::TextDecoration => "none",
};

if self.outlined {
css.merge(style! {
St::Color => color,
St::BackgroundColor => "transparent",
St::Border => format!("{} {} {}", px(2), "solid", color),
});
} else {
css.merge(style! { St::Color => "white", St::BackgroundColor => color });
};

if self.disabled {
css.merge(style! {St::Opacity => 0.5});
} else {
css.merge(style! {St::Cursor => "pointer"});
}

css
};

let mut button = button![css, attrs, self.label];

if !self.disabled {
for on_click in self.on_clicks {
button.add_event_handler(ev(Ev::Click, move |_| on_click()));
}
}

button
}
}
86 changes: 86 additions & 0 deletions examples/experimental_component/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use seed::{prelude::*, *};

mod button;
use button::Button;

// ------ ------
// Init
// ------ ------

fn init(_: Url, _: &mut impl Orders<Msg>) -> Model {
Model::default()
}

// ------ ------
// Model
// ------ ------

type Model = i32;

// ------ ------
// Update
// ------ ------

enum Msg {
Increment(i32),
Decrement(i32),
}

#[allow(clippy::needless_pass_by_value)]
fn update(msg: Msg, model: &mut Model, _: &mut impl Orders<Msg>) {
match msg {
Msg::Increment(d) => *model += d,
Msg::Decrement(d) => *model -= d,
}
}

// ------ ------
// View
// ------ ------

#[allow(clippy::trivially_copy_pass_by_ref)]
fn view(model: &Model) -> Node<Msg> {
div![
style! {
St::Display => "flex"
St::AlignItems => "center",
},
comp![Button { label: "-100" },
disabled => true,
on_click => || Msg::Decrement(100),
],
comp![Button { label: "-10" }, on_click => || Msg::Decrement(10)],
comp![Button { label: "-1" },
outlined => true,
on_click => || Msg::Decrement(1),
],
div![style! { St::Margin => "0 1em" }, model],
comp![Button { label: "+1" },
outlined => true,
on_click => || Msg::Increment(1),
],
comp![Button { label: "+10" }, on_click => || Msg::Increment(10)],
comp![Button { label: "+100" },
disabled => true,
on_click => || Msg::Increment(100),
]
]
}

#[macro_export]
macro_rules! comp {
($init:expr, $($opt_field:ident => $opt_val:expr),* $(,)?) => {
$init.into_component()
$( .$opt_field($opt_val) )*
.into_node()
};
}

// ------ ------
// Start
// ------ ------

#[wasm_bindgen(start)]
pub fn start() {
App::start("app", init, update, view);
}

0 comments on commit 262527b

Please sign in to comment.