https://github.com/modelfoxdotdev/pinwheel
https://github.com/modelfoxdotdev/pinwheel
Last synced: about 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/modelfoxdotdev/pinwheel
- Owner: modelfoxdotdev
- Created: 2021-06-16T15:06:06.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2023-05-12T15:08:04.000Z (over 2 years ago)
- Last Synced: 2025-06-27T05:32:14.066Z (7 months ago)
- Language: Rust
- Homepage:
- Size: 67.4 KB
- Stars: 22
- Watchers: 1
- Forks: 3
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Pinwheel
Pinwheel is a library for writing web user interfaces with Rust.
## Example
The example below increments the value in a `
` each time a `` is pressed.
```rust
let body = dom::window().unwrap().document().unwrap().body().unwrap().into();
let count = Mutable::new(0);
let on_click = {
let count = count.clone();
move |_| {
count.replace_with(|count| *count + 1);
}
};
let count_text = count.signal().map(|count| p().child(count.to_string()));
let increment_button = button().onclick(on_click).child("Increment");
let counter = div()
.style(style::DISPLAY, "grid")
.style(style::JUSTIFY_CONTENT, "start")
.child_signal(count_text)
.child(increment_button);
App::new(body, counter).forget();
```
## Features
### Fine-Grained Reactivity
Pinwheel uses the [futures-signals](https://lib.rs/futures-signals) crate to update exactly the right DOM nodes as your application's state changes. No virtual DOM required!
### Isomorphic Rendering
When compiled for the browser, Pinwheel renders by creating DOM nodes. On the server, it renders by writing HTML to a string.
```rust
let root = p().child("Hello, World!");
// On the server...
assert_eq!(root.to_string(), "
Hello, World!
");
// On the client...
App::new(dom_node, root).forget();
```
### Partial Hydration
After server rendering, make a subset of your app interactive on the client. In the example below, `dynamic_component` will render on the server and the client, but `static_component` will render only on the server.
```rust
let root = p().child("Hello, World!");
// On the server...
let html = div()
.child(static_component)
.child(Dehydrate::new("hydration_id", dynamic_component))
.to_string();
// On the client...
hydrate("hydration_id");
```
### Macro-free Builders
Pinwheel provides statically typed builders for DOM elements with no macros, so you get all the benefits of `rustfmt` formatting and `rust-analyzer` autocomplete.
```rust
let count_p = count.signal().map(|count| p().child(count.to_string()));
let increment_button = button().onclick(on_click).child("Increment");
let root = div()
.style(style::DISPLAY, "grid")
.style(style::JUSTIFY_CONTENT, "start")
.child_signal(count_p)
.child(increment_button);
```
### Components
Organize your application into self-contained components.
```rust
use pinwheel::prelude::*;
struct Alert {
title: String,
color: Option,
children: Vec,
}
impl Component for Alert {
fn into_node(self) -> Node {
div()
.style(style::BACKGROUND_COLOR, self.color)
.child(h1().child(self.title))
.children(self.children)
.into_node()
}
}
```
### Component Builders
Components frequently have a few required fields and many optional fields. Pinwheel provides a derive macro to make using these components easy.
```rust
#[derive(ComponentBuilder)]
struct Alert {
// required field
title: String,
// optional field
#[optional]
color: Option,
#[children]
children: Vec,
}
```
Now, you can make an alert with the builder pattern.
```rust
Alert::new("Alert!")
.color("green".to_owned())
.child("An alert occurred!")
```