update the dioxus cratesio readme, add many more examples (#5501)
* update the dioxus cratesio readme * fix example * fix header * add a bunch more examples
This commit is contained in:
1 parent
74e7d20f80
commit
cd54f5953b
30 files changed
+1403
-235
No files matched your search
@@ -0,0 +1,45 @@
|
||||
//! Checkboxes and radio groups.
|
||||
//!
|
||||
//! Checkboxes expose their checked state through the FormEvent — read `evt.checked()`.
|
||||
//! Radio groups share a `name` attribute so only one option can be selected at a time;
|
||||
//! the selected value comes through `evt.value()`.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut subscribed = use_signal(|| false);
|
||||
let mut color = use_signal(|| "red".to_string());
|
||||
|
||||
rsx! {
|
||||
h2 { "Checkbox" }
|
||||
label {
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
checked: subscribed(),
|
||||
oninput: move |evt| subscribed.set(evt.checked()),
|
||||
}
|
||||
" Subscribe to updates"
|
||||
}
|
||||
p { "Subscribed: {subscribed}" }
|
||||
|
||||
h2 { "Radio group" }
|
||||
// Radio inputs sharing a `name` are mutually exclusive
|
||||
for value in ["red", "green", "blue"] {
|
||||
label {
|
||||
input {
|
||||
r#type: "radio",
|
||||
name: "color",
|
||||
value,
|
||||
checked: color() == value,
|
||||
oninput: move |evt| color.set(evt.value()),
|
||||
}
|
||||
" {value}"
|
||||
}
|
||||
}
|
||||
p { color: "{color}", "Chosen color: {color}" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//! Accepting children on a component.
|
||||
//!
|
||||
//! Any component can take a `children: Element` prop. Anything nested inside the component
|
||||
//! in `rsx!` is passed in as `children`. This is how you build reusable layout and container
|
||||
//! components like cards, modals, or page shells.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
Card {
|
||||
h2 { "A card" }
|
||||
p { "Any rsx! can go inside — including other components." }
|
||||
}
|
||||
|
||||
Card {
|
||||
p { "A second card with different children." }
|
||||
button { "Click me" }
|
||||
}
|
||||
|
||||
Section { title: "Reusable layout",
|
||||
p { "The section component takes both a title prop and children." }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Card(children: Element) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
border: "1px solid #ccc",
|
||||
border_radius: "8px",
|
||||
padding: "12px",
|
||||
margin: "8px 0",
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Section(title: String, children: Element) -> Element {
|
||||
rsx! {
|
||||
section {
|
||||
h3 { "{title}" }
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//! Defining and using components.
|
||||
//!
|
||||
//! A component is just a function annotated with `#[component]` that returns an `Element`.
|
||||
//! Its arguments are its props — Dioxus generates a builder so you can construct the
|
||||
//! component inside `rsx!` with a struct-like syntax.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
h1 { "Greetings" }
|
||||
Greeting { name: "Alice" }
|
||||
Greeting { name: "Bob", excited: true }
|
||||
Greeting { name: "Charlie", excited: false }
|
||||
|
||||
h2 { "User cards" }
|
||||
UserCard { name: "Ada Lovelace", role: "Mathematician" }
|
||||
UserCard { name: "Grace Hopper", role: "Computer Scientist" }
|
||||
}
|
||||
}
|
||||
|
||||
// Components must start with an uppercase letter.
|
||||
// Props are taken as named arguments — `name` is required, and `excited` has a default.
|
||||
#[component]
|
||||
fn Greeting(name: String, #[props(default)] excited: bool) -> Element {
|
||||
let punctuation = if excited { "!" } else { "." };
|
||||
rsx! {
|
||||
p { "Hello, {name}{punctuation}" }
|
||||
}
|
||||
}
|
||||
|
||||
// Components can take any Clone + PartialEq type as a prop.
|
||||
#[component]
|
||||
fn UserCard(name: String, role: String) -> Element {
|
||||
rsx! {
|
||||
div { border: "1px solid #ccc", padding: "8px", margin: "4px",
|
||||
strong { "{name}" }
|
||||
" — "
|
||||
em { "{role}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//! Conditional rendering.
|
||||
//!
|
||||
//! `if`, `else`, and `match` all work directly inside `rsx!`. You can also return different
|
||||
//! `rsx!` blocks early from a component function. Elements outside of a conditional will
|
||||
//! render normally — only the branched portion swaps as state changes.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
enum Status {
|
||||
Loading,
|
||||
Ready,
|
||||
Error,
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut logged_in = use_signal(|| false);
|
||||
let mut status = use_signal(|| Status::Loading);
|
||||
|
||||
rsx! {
|
||||
h1 { "Conditional rendering" }
|
||||
|
||||
// `if/else` chains render the matching branch
|
||||
if logged_in() {
|
||||
p { "Welcome back!" }
|
||||
} else {
|
||||
p { "Please log in." }
|
||||
}
|
||||
button { onclick: move |_| logged_in.toggle(), "Toggle login" }
|
||||
|
||||
// `match` works too — each arm returns rsx!
|
||||
div {
|
||||
match status() {
|
||||
Status::Loading => rsx! { p { "⏳ Loading..." } },
|
||||
Status::Ready => rsx! { p { "✅ Ready!" } },
|
||||
Status::Error => rsx! { p { "❌ Something went wrong." } },
|
||||
}
|
||||
}
|
||||
button { onclick: move |_| status.set(Status::Loading), "Loading" }
|
||||
button { onclick: move |_| status.set(Status::Ready), "Ready" }
|
||||
button { onclick: move |_| status.set(Status::Error), "Error" }
|
||||
|
||||
// `if` without an `else` is also valid — nothing renders when false
|
||||
if logged_in() && status() == Status::Ready {
|
||||
p { "You are logged in and the app is ready." }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//! The classic counter — the "hello world" of reactive UI.
|
||||
//!
|
||||
//! `use_signal` creates a reactive value. Whenever the signal changes, any part of the UI
|
||||
//! that reads it will re-render automatically. Signals are copy-cheap, so you can freely
|
||||
//! move them into event handlers and closures.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
rsx! {
|
||||
h1 { "Count: {count}" }
|
||||
button { onclick: move |_| count += 1, "Increment" }
|
||||
button { onclick: move |_| count -= 1, "Decrement" }
|
||||
button { onclick: move |_| count.set(0), "Reset" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//! Applying classes and styles based on state.
|
||||
//!
|
||||
//! Any attribute can interpolate signals and expressions using `"{value}"` formatting.
|
||||
//! For `class:`, you can also build the class string inline with if/else, and individual
|
||||
//! CSS properties can be set as named attributes like `color:` or `background_color:`.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut is_active = use_signal(|| false);
|
||||
let mut size = use_signal(|| 16);
|
||||
|
||||
// Build a class string from state
|
||||
let classes = if is_active() { "btn active" } else { "btn" };
|
||||
|
||||
rsx! {
|
||||
// CSS properties are plain named attributes — snake_case in Rust, kebab-case in CSS
|
||||
style { {STYLE} }
|
||||
|
||||
button {
|
||||
class: classes,
|
||||
onclick: move |_| is_active.toggle(),
|
||||
"Toggle active (currently {is_active})"
|
||||
}
|
||||
|
||||
// Or compute the class string ahead of time for anything more complex
|
||||
div {
|
||||
class: if is_active() { "box highlight" } else { "box" },
|
||||
"This box changes class with the button."
|
||||
}
|
||||
|
||||
hr {}
|
||||
|
||||
// Style values can be driven directly by signals
|
||||
p {
|
||||
font_size: "{size}px",
|
||||
color: if size() > 24 { "crimson" } else { "black" },
|
||||
"Font size: {size}px"
|
||||
}
|
||||
button { onclick: move |_| size += 2, "Bigger" }
|
||||
button { onclick: move |_| size -= 2, "Smaller" }
|
||||
}
|
||||
}
|
||||
|
||||
const STYLE: &str = r#"
|
||||
.btn { padding: 6px 12px; border: 1px solid #888; border-radius: 4px; }
|
||||
.btn.active { background: #4CAF50; color: white; border-color: #2E7D32; }
|
||||
.box { padding: 12px; margin-top: 12px; border: 1px dashed #999; }
|
||||
.box.highlight { background: #fff3cd; border-color: #d39e00; }
|
||||
"#;
|
||||
@@ -0,0 +1,45 @@
|
||||
//! Passing event handlers as props.
|
||||
//!
|
||||
//! Components can accept callbacks using `EventHandler<T>`, which is Dioxus's equivalent
|
||||
//! of React's `onClick` prop or a closure. Call the handler with `.call(value)` from the
|
||||
//! child; the parent decides what happens when the event fires.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut log = use_signal(Vec::<String>::new);
|
||||
|
||||
rsx! {
|
||||
h1 { "Custom event handlers" }
|
||||
|
||||
// The parent owns the handler — the child just fires it
|
||||
FancyButton {
|
||||
label: "Say hi",
|
||||
onpress: move |name: String| log.write().push(format!("Hi, {name}!")),
|
||||
}
|
||||
FancyButton {
|
||||
label: "Say bye",
|
||||
onpress: move |name: String| log.write().push(format!("Bye, {name}!")),
|
||||
}
|
||||
|
||||
ul {
|
||||
for entry in log.iter() {
|
||||
li { "{entry}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn FancyButton(label: String, onpress: EventHandler<String>) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |_| onpress.call(label.clone()),
|
||||
"{label}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//! Handling form submission and deserializing into a struct.
|
||||
//!
|
||||
//! `evt.parsed_values::<T>()` takes the form's named inputs and deserializes them straight
|
||||
//! into your own type via serde. This skips the chore of pulling each field out of the
|
||||
//! values map by hand — just define a struct whose fields match the `name` attributes,
|
||||
//! derive `Deserialize`, and you're done.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use serde::Deserialize;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
// Field names must match the `name` attribute on each input
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct Signup {
|
||||
username: String,
|
||||
email: String,
|
||||
role: String,
|
||||
// Checkboxes are present in the form data only when checked, so model optional fields
|
||||
// with Option<String>. Use `#[serde(default)]` so the field doesn't fail to deserialize
|
||||
// when the input is missing.
|
||||
#[serde(default)]
|
||||
newsletter: Option<String>,
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut submitted = use_signal(|| None::<Signup>);
|
||||
|
||||
rsx! {
|
||||
h1 { "Sign up" }
|
||||
|
||||
form {
|
||||
onsubmit: move |evt| {
|
||||
// prevent_default stops the browser from navigating the page on submit
|
||||
evt.prevent_default();
|
||||
|
||||
// Deserialize directly into our struct
|
||||
match evt.parsed_values::<Signup>() {
|
||||
Ok(signup) => submitted.set(Some(signup)),
|
||||
Err(err) => eprintln!("failed to parse form: {err}"),
|
||||
}
|
||||
},
|
||||
|
||||
div {
|
||||
label { r#for: "username", "Username: " }
|
||||
input { r#type: "text", id: "username", name: "username", required: true }
|
||||
}
|
||||
|
||||
div {
|
||||
label { r#for: "email", "Email: " }
|
||||
input { r#type: "email", id: "email", name: "email", required: true }
|
||||
}
|
||||
|
||||
div {
|
||||
label { r#for: "role", "Role: " }
|
||||
select { id: "role", name: "role",
|
||||
option { value: "developer", "Developer" }
|
||||
option { value: "designer", "Designer" }
|
||||
option { value: "manager", "Manager" }
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
label { r#for: "newsletter",
|
||||
input { r#type: "checkbox", id: "newsletter", name: "newsletter", value: "yes" }
|
||||
" Subscribe to newsletter"
|
||||
}
|
||||
}
|
||||
|
||||
button { r#type: "submit", "Submit" }
|
||||
}
|
||||
|
||||
if let Some(signup) = submitted() {
|
||||
h2 { "Welcome, {signup.username}!" }
|
||||
ul {
|
||||
li { "Email: {signup.email}" }
|
||||
li { "Role: {signup.role}" }
|
||||
li { "Newsletter: {signup.newsletter.is_some()}" }
|
||||
}
|
||||
details {
|
||||
summary { "Raw struct" }
|
||||
pre { "{signup:#?}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//! Controlled text inputs.
|
||||
//!
|
||||
//! To read text from an input, attach an `oninput` handler and call `evt.value()` to get the
|
||||
//! current value as a `String`. Binding the `value:` attribute to a signal makes the input
|
||||
//! "controlled" — the signal is the single source of truth, so clearing it clears the input.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut name = use_signal(String::new);
|
||||
let mut message = use_signal(|| "Type something below.".to_string());
|
||||
|
||||
rsx! {
|
||||
h1 { "Hello, {name}!" }
|
||||
|
||||
input {
|
||||
r#type: "text",
|
||||
placeholder: "Your name",
|
||||
// Binding `value` to a signal makes this a controlled input
|
||||
value: "{name}",
|
||||
oninput: move |evt| name.set(evt.value()),
|
||||
}
|
||||
|
||||
button {
|
||||
onclick: move |_| name.set(String::new()),
|
||||
"Clear"
|
||||
}
|
||||
|
||||
hr {}
|
||||
|
||||
p { "{message}" }
|
||||
textarea {
|
||||
rows: 3,
|
||||
cols: 40,
|
||||
value: "{message}",
|
||||
oninput: move |evt| message.set(evt.value()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//! Rendering lists.
|
||||
//!
|
||||
//! Use a `for` loop inside `rsx!` to render a list from an iterator. When list items can
|
||||
//! be reordered, added, or removed, provide a stable `key` so Dioxus can efficiently
|
||||
//! reuse existing elements rather than re-rendering them all.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut items = use_signal(|| {
|
||||
vec![
|
||||
"apple".to_string(),
|
||||
"banana".to_string(),
|
||||
"cherry".to_string(),
|
||||
]
|
||||
});
|
||||
let mut next_id = use_signal(|| 0);
|
||||
|
||||
rsx! {
|
||||
h1 { "Grocery list" }
|
||||
|
||||
button {
|
||||
onclick: move |_| {
|
||||
let n = next_id();
|
||||
next_id += 1;
|
||||
items.push(format!("item {n}"));
|
||||
},
|
||||
"Add item"
|
||||
}
|
||||
button {
|
||||
onclick: move |_| { items.pop(); },
|
||||
"Remove last"
|
||||
}
|
||||
|
||||
ul {
|
||||
// The `key` is a stable identifier used to match up items across renders.
|
||||
// Here we use the item's text, but in real apps prefer a database id.
|
||||
for item in items.iter() {
|
||||
li { key: "{item}", "{item}" }
|
||||
}
|
||||
}
|
||||
|
||||
// You can also use iterator adapters with .map() for anything more complex than a for loop
|
||||
p { "Uppercased:" }
|
||||
ul {
|
||||
{items.iter().map(|item| rsx! { li { "{item.to_uppercase()}" } })}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user