瀏覽代碼

Merge pull request #1939 from DioxusLabs/jk/examples-overhaul

Add more examples, document examples, add css to most examples
Jonathan Kelley 1 年之前
父節點
當前提交
77c80ea715
共有 76 個文件被更改,包括 1452 次插入954 次删除
  1. 1 0
      Cargo.lock
  2. 2 1
      Cargo.toml
  3. 16 28
      examples/all_events.rs
  4. 23 0
      examples/assets/clock.css
  5. 26 0
      examples/assets/counter.css
  6. 16 0
      examples/assets/crm.css
  7. 12 0
      examples/assets/custom_assets.css
  8. 0 0
      examples/assets/dog_app.css
  9. 24 0
      examples/assets/events.css
  10. 22 0
      examples/assets/file_upload.css
  11. 40 0
      examples/assets/flat_router.css
  12. 12 0
      examples/assets/links.css
  13. 43 0
      examples/assets/radio.css
  14. 29 0
      examples/assets/roulette.css
  15. 13 0
      examples/assets/router.css
  16. 18 9
      examples/backgrounded_futures.rs
  17. 9 4
      examples/calculator.rs
  18. 18 26
      examples/calculator_mutable.rs
  19. 28 7
      examples/clock.rs
  20. 22 9
      examples/control_focus.rs
  21. 0 53
      examples/counter.rs
  22. 52 0
      examples/counters.rs
  23. 27 16
      examples/crm.rs
  24. 8 1
      examples/custom_assets.rs
  25. 4 10
      examples/custom_html.rs
  26. 5 2
      examples/disabled.rs
  27. 31 13
      examples/dog_app.rs
  28. 9 3
      examples/dynamic_asset.rs
  29. 23 6
      examples/error_handle.rs
  30. 19 1
      examples/eval.rs
  31. 21 20
      examples/file_explorer.rs
  32. 22 12
      examples/file_upload.rs
  33. 0 17
      examples/filedragdrop.rs
  34. 31 19
      examples/flat_router.rs
  35. 1 1
      examples/form.rs
  36. 38 0
      examples/future.rs
  37. 5 1
      examples/generic_component.rs
  38. 41 3
      examples/global.rs
  39. 11 0
      examples/hello_world.rs
  40. 0 162
      examples/inputs.rs
  41. 40 22
      examples/link.rs
  42. 7 2
      examples/login_form.rs
  43. 5 0
      examples/memo_chain.rs
  44. 6 0
      examples/multiwindow.rs
  45. 12 4
      examples/optional_props.rs
  46. 27 12
      examples/overlay.rs
  47. 6 8
      examples/popup.rs
  48. 7 1
      examples/read_size.rs
  49. 7 0
      examples/readme.rs
  50. 9 5
      examples/reducer.rs
  51. 53 31
      examples/router.rs
  52. 261 258
      examples/rsx_usage.rs
  53. 7 0
      examples/scroll_to_top.rs
  54. 7 0
      examples/shortcut.rs
  55. 2 0
      examples/shorthand.rs
  56. 8 0
      examples/signals.rs
  57. 4 0
      examples/simple_list.rs
  58. 21 10
      examples/simple_router.rs
  59. 5 0
      examples/spread.rs
  60. 3 0
      examples/ssr.rs
  61. 0 36
      examples/stale_memo.rs
  62. 5 0
      examples/streams.rs
  63. 1 2
      examples/suspense.rs
  64. 7 1
      examples/svg.rs
  65. 0 28
      examples/tasks.rs
  66. 0 21
      examples/textarea.rs
  67. 45 23
      examples/todomvc.rs
  68. 27 18
      examples/video_stream.rs
  69. 20 1
      examples/web_component.rs
  70. 86 44
      examples/window_event.rs
  71. 7 0
      examples/window_focus.rs
  72. 6 0
      examples/window_zoom.rs
  73. 9 2
      packages/desktop/src/webview.rs
  74. 8 0
      packages/html/src/events/form.rs
  75. 1 1
      packages/mobile/Cargo.toml
  76. 11 0
      packages/router/src/components/link.rs

+ 1 - 0
Cargo.lock

@@ -2558,6 +2558,7 @@ dependencies = [
  "serde",
  "serde_json",
  "tokio",
+ "warp",
 ]
 
 [[package]]

+ 2 - 1
Cargo.toml

@@ -125,6 +125,7 @@ publish = false
 manganis = { workspace = true, optional = true}
 reqwest = { version = "0.11.9", features = ["json"], optional = true}
 http-range = {version = "0.1.5", optional = true }
+warp = { version = "0.3.0", optional = true }
 
 [dev-dependencies]
 dioxus = { workspace = true, features = ["router"] }
@@ -154,7 +155,7 @@ server = ["dioxus/axum"]
 default = ["dioxus/desktop"]
 web = ["dioxus/web"]
 collect-assets = ["manganis"]
-http = ["reqwest", "http-range"]
+http = ["reqwest", "http-range", "warp"]
 
 [[example]]
 name = "login_form"

+ 16 - 28
examples/all_events.rs

@@ -1,3 +1,8 @@
+//! This example shows how to listen to all events on a div and log them to the console.
+//!
+//! The primary demonstration here is the properties on the events themselves, hoping to give you some inspiration
+//! on adding interactivity to your own application.
+
 use dioxus::prelude::*;
 use std::{collections::VecDeque, fmt::Debug, rc::Rc};
 
@@ -5,41 +10,24 @@ fn main() {
     launch(app);
 }
 
-const MAX_EVENTS: usize = 8;
-
-const CONTAINER_STYLE: &str = r#"
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-"#;
-
-const RECT_STYLE: &str = r#"
-    background: deepskyblue;
-    height: 50vh;
-    width: 50vw;
-    color: white;
-    padding: 20px;
-    margin: 20px;
-    text-aligh: center;
-"#;
-
 fn app() -> Element {
-    let mut events = use_signal(|| VecDeque::new() as VecDeque<Rc<dyn Debug>>);
+    // Using a VecDeque so its cheap to pop old events off the front
+    let mut events = use_signal(VecDeque::new);
 
+    // All events and their data implement Debug, so we can re-cast them as Rc<dyn Debug> instead of their specific type
     let mut log_event = move |event: Rc<dyn Debug>| {
-        let mut events = events.write();
-
-        if events.len() >= MAX_EVENTS {
-            events.pop_front();
+        // Only store the last 20 events
+        if events.read().len() >= 20 {
+            events.write().pop_front();
         }
-
-        events.push_back(event);
+        events.write().push_back(event);
     };
 
     rsx! {
-        div { style: "{CONTAINER_STYLE}",
+        style { {include_str!("./assets/events.css")} }
+        div { id: "container",
             // focusing is necessary to catch keyboard events
-            div { style: "{RECT_STYLE}", tabindex: 0,
+            div { id: "receiver", tabindex: 0,
                 onmousemove: move |event| log_event(event.data()),
                 onclick: move |event| log_event(event.data()),
                 ondoubleclick: move |event| log_event(event.data()),
@@ -57,7 +45,7 @@ fn app() -> Element {
 
                 "Hover, click, type or scroll to see the info down below"
             }
-            div {
+            div { id: "log",
                 for event in events.read().iter() {
                     div { "{event:?}" }
                 }

+ 23 - 0
examples/assets/clock.css

@@ -0,0 +1,23 @@
+html body {
+    margin: 0;
+    padding: 0;
+    height: 100vh;
+    font-family: 'Courier New', Courier, monospace;
+}
+
+#app {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
+    height: 100vh;
+    background-color: plum;
+    font-size: 6em;
+    color: aliceblue;
+}
+
+#title {
+    font-size: 0.5em;
+    color: black;
+    margin-bottom: 0.5em;
+}

+ 26 - 0
examples/assets/counter.css

@@ -0,0 +1,26 @@
+html body {
+    font-family: Arial, sans-serif;
+    margin: 20px;
+    padding: 0;
+    display: flex;
+    justify-content: center;
+    font-size: 2rem;
+    height: 100vh;
+    background-color: #f0f0f0;
+}
+
+#controls {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-top: 20px;
+}
+
+button {
+    padding: 5px 10px;
+    margin: 0 5px;
+}
+
+input {
+    width: 50px;
+}

+ 16 - 0
examples/assets/crm.css

@@ -0,0 +1,16 @@
+body {
+    background-color: #f4f4f4;
+    font-family: 'Open Sans', sans-serif;
+    font-size: 14px;
+    line-height: 1.42857143;
+    color: #333;
+    margin: 20px;
+    padding: 20px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+
+.red {
+    background-color: rgb(202, 60, 60) !important;
+}

+ 12 - 0
examples/assets/custom_assets.css

@@ -0,0 +1,12 @@
+body {
+    background-color: #f0f0f0;
+    font-family: Arial, sans-serif;
+    margin: 0;
+    padding: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    text-align: center;
+    height: 100vh;
+    width: 100vw;
+}

+ 0 - 0
examples/assets/dog_app.css


+ 24 - 0
examples/assets/events.css

@@ -0,0 +1,24 @@
+#container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+
+#receiver {
+    background: deepskyblue;
+    height: 30vh;
+    width: 80vw;
+    color: white;
+    padding: 20px;
+    margin: 20px;
+    text-align: center;
+}
+
+#log {
+    background: lightgray;
+    padding: 20px;
+    margin: 20px;
+    overflow-y: scroll;
+    align-items: start;
+    text-align: left;
+}

+ 22 - 0
examples/assets/file_upload.css

@@ -0,0 +1,22 @@
+body {
+    font-family: Arial, sans-serif;
+    margin: 0;
+    padding: 0;
+    background-color: #f4f4f4;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 100vh;
+    flex-direction: column;
+    gap: 20px;
+}
+
+#drop-zone {
+    border: 2px dashed #ccc;
+    border-radius: 3px;
+    padding: 20px;
+    text-align: center;
+    cursor: pointer;
+    margin: 20px;
+    background-color: rgba(225, 124, 225, 0);
+}

+ 40 - 0
examples/assets/flat_router.css

@@ -0,0 +1,40 @@
+body {
+    font-family: Arial, sans-serif;
+    margin: 20px;
+    padding: 20px;
+    background-color: #f4f4f4;
+    height: 100vh;
+}
+
+nav {
+    display: flex;
+    justify-content: space-around;
+}
+
+.nav-btn {
+    text-decoration: none;
+    color: black;
+}
+
+a {
+    padding: 10px;
+    border: none;
+    border-radius: 5px;
+    cursor: pointer;
+}
+
+/* button hover effect */
+a:hover {
+    background-color: #dd6a6a;
+}
+
+#content {
+    border: 2px dashed #ccc;
+    padding-top: 20px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 100%;
+    flex-direction: column;
+    gap: 20px;
+}

+ 12 - 0
examples/assets/links.css

@@ -0,0 +1,12 @@
+#external-links {
+    display: flex;
+    flex-direction: column;
+}
+
+#nav {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 1rem;
+    background-color: #f4f4f4;
+}

+ 43 - 0
examples/assets/radio.css

@@ -0,0 +1,43 @@
+body {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    text-align: center;
+}
+
+button {
+    margin: 10px;
+    padding: 10px;
+    border: none;
+    border-radius: 5px;
+    background-color: #f0f0f0;
+    cursor: pointer;
+}
+
+#pause {
+    background-color: #ff0000;
+}
+
+#play {
+    background-color: #00ff00;
+}
+
+
+.bounce {
+    animation: boomBox 0.5s infinite;
+}
+
+@keyframes boomBox {
+    0% {
+        transform: scale(1.0);
+    }
+
+    50% {
+        transform: scale(2);
+    }
+
+    100% {
+        transform: scale(1.0);
+    }
+}

+ 29 - 0
examples/assets/roulette.css

@@ -0,0 +1,29 @@
+#main {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+}
+
+#roulette-grid {
+    margin: 10px;
+    display: grid;
+    grid-template-columns: repeat(9, 1fr);
+    grid-gap: 10px;
+    margin: 0 auto;
+    padding: 20px;
+}
+
+#roulette-grid>input {
+    color: white;
+    font-size: 20px;
+    width: 50px;
+}
+
+#roulette-grid>input:nth-child(odd) {
+    background-color: red;
+}
+
+#roulette-grid>input:nth-child(even) {
+    background-color: black;
+}

+ 13 - 0
examples/assets/router.css

@@ -0,0 +1,13 @@
+#navbar {
+    display: flex;
+    justify-content: flex-start;
+    gap: 1rem;
+    align-items: center;
+}
+
+#blog-list {
+    display: flex;
+    flex-direction: column;
+    align-items: start;
+    gap: 1rem;
+}

+ 18 - 9
examples/backgrounded_futures.rs

@@ -1,3 +1,12 @@
+//! Backgrounded futures example
+//!
+//! This showcases how use_future, use_memo, and use_effect will stop running if the component returns early.
+//! Generally you should avoid using early returns around hooks since most hooks are not properly designed to
+//! handle early returns. However, use_future *does* pause the future when the component returns early, and so
+//! hooks that build on top of it like use_memo and use_effect will also pause.
+//!
+//! This example is more of a demonstration of the behavior than a practical use case, but it's still interesting to see.
+
 use dioxus::prelude::*;
 
 fn main() {
@@ -10,17 +19,17 @@ fn app() -> Element {
 
     let child = use_memo(move || {
         rsx! {
-            Child {
-                count
-            }
+            Child { count }
         }
     });
 
     rsx! {
+        // Some toggle/controls to show the child or increment the count
         button { onclick: move |_| show_child.toggle(), "Toggle child" }
         button { onclick: move |_| count += 1, "Increment count" }
+
         if show_child() {
-            {child.cloned()}
+            {child()}
         }
     }
 }
@@ -44,12 +53,12 @@ fn Child(count: Signal<i32>) -> Element {
         }
     });
 
-    use_effect(move || {
-        println!("Child count: {}", count());
-    });
+    use_effect(move || println!("Child count: {}", count()));
 
     rsx! {
-        "hellO!"
-        {early}
+        div {
+            "Child component"
+            {early}
+        }
     }
 }

+ 9 - 4
examples/calculator.rs

@@ -1,7 +1,12 @@
-/*
-This example is a simple iOS-style calculator. This particular example can run any platform - Web, Mobile, Desktop.
-This calculator version uses React-style state management. All state is held as individual use_states.
-*/
+//! Calculator
+//!
+//! This example is a simple iOS-style calculator. Instead of wrapping the state in a single struct like the
+//! `calculate_mutable` example, this example uses several closures to manage actions with the state. Most
+//! components will start like this since it's the quickest way to start adding state to your app. The `Signal` type
+//! in Dioxus is `Copy` - meaning you don't need to clone it to use it in a closure.
+//!
+//! Notice how our logic is consolidated into just a few callbacks instead of a single struct. This is a rather organic
+//! way to start building state management in Dioxus, and it's a great way to start.
 
 use dioxus::events::*;
 use dioxus::html::input_data::keyboard_types::Key;

+ 18 - 26
examples/calculator_mutable.rs

@@ -1,38 +1,28 @@
-#![allow(non_snake_case)]
-
-//! Example: Calculator
-//! -------------------
-//!
-//! Some components benefit through the use of "Models". Models are a single block of encapsulated state that allow mutative
-//! methods to be performed on them. Dioxus exposes the ability to use the model pattern through the "use_model" hook.
-//!
-//! Models are commonly used in the "Model-View-Component" approach for building UI state.
-//!
-//! `use_model` is basically just a fancy wrapper around set_state, but saves a "working copy" of the new state behind a
-//! RefCell. To modify the working copy, you need to call "get_mut" which returns the RefMut. This makes it easy to write
-//! fully encapsulated apps that retain a certain feel of native Rusty-ness. A calculator app is a good example of when this
-//! is useful.
+//! This example showcases a simple calculator using an approach to state management where the state is composed of only
+//! a single signal. Since Dioxus implements traditional React diffing, state can be consolidated into a typical Rust struct
+//! with methods that take `&mut self`. For many use cases, this is a simple way to manage complex state without wrapping
+//! everything in a signal.
 //!
-//! Do note that "get_mut" returns a `RefMut` (a lock over a RefCell). If two `RefMut`s are held at the same time (ie in a loop)
-//! the RefCell will panic and crash. You can use `try_get_mut` or `.modify` to avoid this problem, or just not hold two
-//! RefMuts at the same time.
+//! Generally, you'll want to split your state into several signals if you have a large application, but for small
+//! applications, or focused components, this is a great way to manage state.
 
 use dioxus::desktop::tao::dpi::LogicalSize;
 use dioxus::desktop::{Config, WindowBuilder};
-use dioxus::events::*;
 use dioxus::html::input_data::keyboard_types::Key;
 use dioxus::html::MouseEvent;
 use dioxus::prelude::*;
 
 fn main() {
-    let cfg = Config::new().with_window(
-        WindowBuilder::new()
-            .with_title("Calculator Demo")
-            .with_resizable(false)
-            .with_inner_size(LogicalSize::new(320.0, 530.0)),
-    );
-
-    LaunchBuilder::desktop().with_cfg(cfg).launch(app);
+    LaunchBuilder::desktop()
+        .with_cfg(
+            Config::new().with_window(
+                WindowBuilder::new()
+                    .with_title("Calculator Demo")
+                    .with_resizable(false)
+                    .with_inner_size(LogicalSize::new(320.0, 530.0)),
+            ),
+        )
+        .launch(app);
 }
 
 const STYLE: &str = include_str!("./assets/calculator.css");
@@ -109,6 +99,7 @@ struct Calculator {
     waiting_for_operand: bool,
     cur_val: f64,
 }
+
 #[derive(Clone)]
 enum Operator {
     Add,
@@ -116,6 +107,7 @@ enum Operator {
     Mul,
     Div,
 }
+
 impl Calculator {
     fn new() -> Self {
         Calculator {

+ 28 - 7
examples/clock.rs

@@ -1,3 +1,8 @@
+//! A simple little clock that updates the time every few milliseconds.
+//!
+//! Neither Rust nor Tokio have an interval function, so we just sleep until the next update.
+//! Tokio timer's don't work on WASM though, so you'll need to use a slightly different approach if you're targeting the web.
+
 use dioxus::prelude::*;
 
 fn main() {
@@ -5,20 +10,36 @@ fn main() {
 }
 
 fn app() -> Element {
-    let mut count = use_signal(|| 0);
+    let mut millis = use_signal(|| 0);
 
     use_future(move || async move {
+        // Save our initial timea
+        let start = std::time::Instant::now();
+
         loop {
-            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
-            count += 1;
+            // In lieu of an interval, we just sleep until the next update
+            let now = tokio::time::Instant::now();
+            tokio::time::sleep_until(now + std::time::Duration::from_millis(27)).await;
+
+            // Update the time, using a more precise approach of getting the duration since we started the timer
+            millis.set(start.elapsed().as_millis() as i64);
         }
     });
 
-    use_effect(move || {
-        println!("High-Five counter: {}", count());
-    });
+    // Format the time as a string
+    // This is rather cheap so it's fine to leave it in the render function
+    let time = format!(
+        "{:02}:{:02}:{:03}",
+        millis() / 1000 / 60 % 60,
+        millis() / 1000 % 60,
+        millis() % 1000
+    );
 
     rsx! {
-        div { "High-Five counter: {count}" }
+        style { {include_str!("./assets/clock.css")} }
+        div { id: "app",
+            div { id: "title", "Carpe diem 🎉" }
+            div { id: "clock-display", "{time}" }
+        }
     }
 }

+ 22 - 9
examples/control_focus.rs

@@ -1,3 +1,8 @@
+//! Managing focus
+//!
+//! This example shows how to manage focus in a Dioxus application. We implement a "roulette" that focuses on each input
+//! in the grid every few milliseconds until the user interacts with the inputs.
+
 use std::rc::Rc;
 
 use dioxus::prelude::*;
@@ -7,6 +12,7 @@ fn main() {
 }
 
 fn app() -> Element {
+    // Element data is stored as Rc<MountedData> so we can clone it and pass it around
     let mut elements = use_signal(Vec::<Rc<MountedData>>::new);
     let mut running = use_signal(|| true);
 
@@ -14,7 +20,7 @@ fn app() -> Element {
         let mut focused = 0;
 
         loop {
-            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
+            tokio::time::sleep(std::time::Duration::from_millis(50)).await;
 
             if !running() {
                 continue;
@@ -31,17 +37,24 @@ fn app() -> Element {
     });
 
     rsx! {
-        div {
-            h1 { "Input Roulette" }
+        style { {include_str!("./assets/roulette.css")} }
+        h1 { "Input Roulette" }
+        button { onclick: move |_| running.toggle(), "Toggle roulette" }
+        div { id: "roulette-grid",
+            // Restart the roulette if the user presses escape
+            onkeydown: move |event| {
+                if event.code().to_string() == "Escape" {
+                    running.set(true);
+                }
+            },
+
+            // Draw the grid of inputs
             for i in 0..100 {
                 input {
+                    r#type: "number",
                     value: "{i}",
-                    onmounted: move |cx| {
-                        elements.write().push(cx.data());
-                    },
-                    oninput: move |_| {
-                        running.set(false);
-                    }
+                    onmounted: move |cx| elements.write().push(cx.data()),
+                    oninput: move |_| running.set(false),
                 }
             }
         }

+ 0 - 53
examples/counter.rs

@@ -1,53 +0,0 @@
-//! Comparison example with leptos' counter example
-//! https://github.com/leptos-rs/leptos/blob/main/examples/counters/src/lib.rs
-
-use dioxus::prelude::*;
-
-fn main() {
-    launch(app);
-}
-
-fn app() -> Element {
-    let mut counters = use_signal(|| vec![0, 0, 0]);
-    let sum = use_memo(move || counters.read().iter().copied().sum::<i32>());
-
-    rsx! {
-        div {
-            button { onclick: move |_| counters.write().push(0), "Add counter" }
-            button {
-                onclick: move |_| {
-                    counters.write().pop();
-                },
-                "Remove counter"
-            }
-            p { "Total: {sum}" }
-            for i in 0..counters.len() {
-                Child { i, counters }
-            }
-        }
-    }
-}
-
-#[component]
-fn Child(counters: Signal<Vec<i32>>, i: usize) -> Element {
-    rsx! {
-        li {
-            button { onclick: move |_| counters.write()[i] -= 1, "-1" }
-            input {
-                value: "{counters.read()[i]}",
-                oninput: move |e| {
-                    if let Ok(value) = e.value().parse::<i32>() {
-                        counters.write()[i] = value;
-                    }
-                }
-            }
-            button { onclick: move |_| counters.write()[i] += 1, "+1" }
-            button {
-                onclick: move |_| {
-                    counters.write().remove(i);
-                },
-                "x"
-            }
-        }
-    }
-}

+ 52 - 0
examples/counters.rs

@@ -0,0 +1,52 @@
+//! A simple counters example that stores a list of items in a vec and then iterates over them.
+
+use dioxus::prelude::*;
+
+fn main() {
+    launch(app);
+}
+
+fn app() -> Element {
+    // Store the counters in a signal
+    let mut counters = use_signal(|| vec![0, 0, 0]);
+
+    // Whenver the counters change, sum them up
+    let sum = use_memo(move || counters.read().iter().copied().sum::<i32>());
+
+    rsx! {
+        style { {include_str!("./assets/counter.css")} }
+
+        div { id: "controls",
+            button { onclick: move |_| counters.write().push(0), "Add counter" }
+            button { onclick: move |_| { counters.write().pop(); }, "Remove counter" }
+        }
+
+        h3 { "Total: {sum}" }
+
+        // Calling `iter` on a Signal<Vec<>> gives you a GenerationalRef to each entry in the vec
+        // We enumerate to get the idx of each counter, which we use later to modify the vec
+        for (i, counter) in counters.iter().enumerate() {
+            // We need a key to uniquely identify each counter. You really shouldn't be using the index, so we're using
+            // the counter value itself.
+            //
+            // If we used the index, and a counter is removed, dioxus would need to re-write the contents of all following
+            // counters instead of simply removing the one that was removed
+            //
+            // You should use a stable identifier for the key, like a unique id or the value of the counter itself
+            li { key: "{i}",
+                button { onclick: move |_| counters.write()[i] -= 1, "-1" }
+                input {
+                    r#type: "number",
+                    value: "{counter}",
+                    oninput: move |e| {
+                        if let Ok(value) = e.parsed() {
+                            counters.write()[i] = value;
+                        }
+                    }
+                }
+                button { onclick: move |_| counters.write()[i] += 1, "+1" }
+                button { onclick: move |_| { counters.write().remove(i); }, "x" }
+            }
+        }
+    }
+}

+ 27 - 16
examples/crm.rs

@@ -1,4 +1,14 @@
-//! Tiny CRM: A port of the Yew CRM example to Dioxus.
+//! Tiny CRM - A simple CRM app using the Router component and global signals
+//!
+//! This shows how to use the `Router` component to manage different views in your app. It also shows how to use global
+//! signals to manage state across the entire app.
+//!
+//! We could simply pass the state as a prop to each component, but this is a good example of how to use global state
+//! in a way that works across pages.
+//!
+//! We implement a number of important details here too, like focusing inputs, handling form submits, navigating the router,
+//! platform-specific configuration, and importing 3rd party CSS libaries.
+
 use dioxus::prelude::*;
 
 fn main() {
@@ -16,7 +26,7 @@ fn main() {
                     integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
                     crossorigin: "anonymous"
                 }
-                style { {r#" .red { background-color: rgb(202, 60, 60) !important; } "#} }
+                style { {include_str!("./assets/crm.css")} }
                 h1 { "Dioxus CRM Example" }
                 Router::<Route> {}
             }
@@ -32,23 +42,24 @@ struct Client {
     description: String,
 }
 
+/// The pages of the app, each with a route
 #[derive(Routable, Clone)]
 enum Route {
     #[route("/")]
-    ClientList,
+    List,
 
     #[route("/new")]
-    ClientAdd,
+    New,
 
     #[route("/settings")]
     Settings,
 }
 
 #[component]
-fn ClientList() -> Element {
+fn List() -> Element {
     rsx! {
         h2 { "List of Clients" }
-        Link { to: Route::ClientAdd, class: "pure-button pure-button-primary", "Add Client" }
+        Link { to: Route::New, class: "pure-button pure-button-primary", "Add Client" }
         Link { to: Route::Settings, class: "pure-button", "Settings" }
         for client in CLIENTS.read().iter() {
             div { class: "client", style: "margin-bottom: 50px",
@@ -60,12 +71,12 @@ fn ClientList() -> Element {
 }
 
 #[component]
-fn ClientAdd() -> Element {
+fn New() -> Element {
     let mut first_name = use_signal(String::new);
     let mut last_name = use_signal(String::new);
     let mut description = use_signal(String::new);
 
-    let submit_client = move |_: FormEvent| {
+    let submit_client = move |_| {
         // Write the client
         CLIENTS.write().push(Client {
             first_name: first_name(),
@@ -74,7 +85,7 @@ fn ClientAdd() -> Element {
         });
 
         // And then navigate back to the client list
-        dioxus::router::router().push(Route::ClientList);
+        router().push(Route::List);
     };
 
     rsx! {
@@ -87,7 +98,7 @@ fn ClientAdd() -> Element {
                         id: "first_name",
                         r#type: "text",
                         placeholder: "First Name…",
-                        required: "",
+                        required: true,
                         value: "{first_name}",
                         oninput: move |e| first_name.set(e.value()),
 
@@ -99,19 +110,19 @@ fn ClientAdd() -> Element {
                 }
 
                 div { class: "pure-control-group",
-                    label { "for": "last_name", "Last Name" }
+                    label { r#for: "last_name", "Last Name" }
                     input {
                         id: "last_name",
                         r#type: "text",
                         placeholder: "Last Name…",
-                        required: "",
+                        required: true,
                         value: "{last_name}",
                         oninput: move |e| last_name.set(e.value())
                     }
                 }
 
                 div { class: "pure-control-group",
-                    label { "for": "description", "Description" }
+                    label { r#for: "description", "Description" }
                     textarea {
                         id: "description",
                         placeholder: "Description…",
@@ -122,7 +133,7 @@ fn ClientAdd() -> Element {
 
                 div { class: "pure-controls",
                     button { r#type: "submit", class: "pure-button pure-button-primary", "Save" }
-                    Link { to: Route::ClientList, class: "pure-button pure-button-primary red", "Cancel" }
+                    Link { to: Route::List, class: "pure-button pure-button-primary red", "Cancel" }
                 }
             }
         }
@@ -137,10 +148,10 @@ fn Settings() -> Element {
             class: "pure-button pure-button-primary red",
             onclick: move |_| {
                 CLIENTS.write().clear();
-                dioxus::router::router().push(Route::ClientList);
+                dioxus::router::router().push(Route::List);
             },
             "Remove all Clients"
         }
-        Link { to: Route::ClientList, class: "pure-button", "Go back" }
+        Link { to: Route::List, class: "pure-button", "Go back" }
     }
 }

+ 8 - 1
examples/custom_assets.rs

@@ -1,3 +1,10 @@
+//! A simple example on how to use assets loading from the filesystem.
+//!
+//! If the feature "collect-assets" is enabled, the assets will be collected via the dioxus CLI and embedded into the
+//! final bundle. This lets you do various useful things like minify, compress, and optimize your assets.
+//!
+//! We can still use assets without the CLI middleware, but generally larger apps will benefit from it.
+
 use dioxus::prelude::*;
 
 #[cfg(not(feature = "collect-assets"))]
@@ -14,7 +21,7 @@ fn main() {
 fn app() -> Element {
     rsx! {
         div {
-            p { "This should show an image:" }
+            h1 { "This should show an image:" }
             img { src: ASSET_PATH.to_string() }
         }
     }

+ 4 - 10
examples/custom_html.rs

@@ -1,28 +1,22 @@
 //! This example shows how to use a custom index.html and custom <HEAD> extensions
 //! to add things like stylesheets, scripts, and third-party JS libraries.
 
-use dioxus::desktop::Config;
 use dioxus::prelude::*;
 
 fn main() {
     LaunchBuilder::desktop()
         .with_cfg(
-            Config::new().with_custom_head("<style>body { background-color: red; }</style>".into()),
-        )
-        .launch(app);
-
-    LaunchBuilder::desktop()
-        .with_cfg(
-            Config::new().with_custom_index(
+            dioxus::desktop::Config::new().with_custom_index(
                 r#"
 <!DOCTYPE html>
 <html>
   <head>
     <title>Dioxus app</title>
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <style>body { background-color: blue; }</style>
+    <style>body { background-color: olive; }</style>
   </head>
   <body>
+    <h1>External HTML</h1>
     <div id="main"></div>
   </body>
 </html>
@@ -35,6 +29,6 @@ fn main() {
 
 fn app() -> Element {
     rsx! {
-        div { h1 { "hello world!" } }
+        h1 { "Custom HTML!" }
     }
 }

+ 5 - 2
examples/disabled.rs

@@ -1,3 +1,7 @@
+//! A simple demonstration of how to set attributes on buttons to disable them.
+//!
+//! This example also showcases the shorthand syntax for attributes, and how signals themselves implement IntoAttribute
+
 use dioxus::prelude::*;
 
 fn main() {
@@ -8,13 +12,12 @@ fn app() -> Element {
     let mut disabled = use_signal(|| false);
 
     rsx! {
-        div {
+        div { style: "text-align: center; margin: 20px; display: flex; flex-direction: column; align-items: center;",
             button { onclick: move |_| disabled.toggle(),
                 "click to "
                 if disabled() { "enable" } else { "disable" }
                 " the lower button"
             }
-
             button { disabled, "lower button" }
         }
     }

+ 31 - 13
examples/dog_app.rs

@@ -1,3 +1,12 @@
+//! This example demonstrates a simple app that fetches a list of dog breeds and displays a random dog.
+//!
+//! The app uses the `use_signal` and `use_resource` hooks to manage state and fetch data from the Dog API.
+//! `use_resource` is basically an async version of use_memo - it will track dependencies between .await points
+//! and then restart the future if any of the dependencies change.
+//!
+//! You should generally throttle requests to an API - either client side or server side. This example doesn't do that
+//! since it's unlikely the user will rapidly cause new fetches, but it's something to keep in mind.
+
 use dioxus::prelude::*;
 use std::collections::HashMap;
 
@@ -6,8 +15,18 @@ fn main() {
 }
 
 fn app() -> Element {
+    // Breed is a signal that will be updated when the user clicks a breed in the list
+    // `deerhound` is just a default that we know will exist. We could also use a `None` instead
     let mut breed = use_signal(|| "deerhound".to_string());
+
+    // Fetch the list of breeds from the Dog API
+    // Since there are no dependencies, this will never restart
     let breed_list = use_resource(move || async move {
+        #[derive(Debug, Clone, PartialEq, serde::Deserialize)]
+        struct ListBreeds {
+            message: HashMap<String, Vec<String>>,
+        }
+
         let list = reqwest::get("https://dog.ceo/api/breeds/list/all")
             .await
             .unwrap()
@@ -19,7 +38,7 @@ fn app() -> Element {
         };
 
         rsx! {
-            for cur_breed in breeds.message.keys().take(10).cloned() {
+            for cur_breed in breeds.message.keys().take(20).cloned() {
                 li { key: "{cur_breed}",
                     button { onclick: move |_| breed.set(cur_breed.clone()),
                         "{cur_breed}"
@@ -29,22 +48,31 @@ fn app() -> Element {
         }
     });
 
+    // We can use early returns in dioxus!
+    // Traditional signal-based libraries can't do this since the scope is by default non-reactive
     let Some(breed_list) = breed_list() else {
         return rsx! { "loading breeds..." };
     };
 
     rsx! {
+        style { {include_str!("./assets/dog_app.css")} }
         h1 { "Select a dog breed!" }
         div { height: "500px", display: "flex",
-            ul { flex: "50%", {breed_list} }
-            div { flex: "50%", BreedPic { breed } }
+            ul { width: "100px", {breed_list} }
+            div { flex: 1, BreedPic { breed } }
         }
     }
 }
 
 #[component]
 fn BreedPic(breed: Signal<String>) -> Element {
+    // This resource will restart whenever the breed changes
     let mut fut = use_resource(move || async move {
+        #[derive(serde::Deserialize, Debug)]
+        struct DogApi {
+            message: String,
+        }
+
         reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
             .await
             .unwrap()
@@ -61,13 +89,3 @@ fn BreedPic(breed: Signal<String>) -> Element {
         None => rsx! { "loading image..." },
     }
 }
-
-#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
-struct ListBreeds {
-    message: HashMap<String, Vec<String>>,
-}
-
-#[derive(serde::Deserialize, Debug)]
-struct DogApi {
-    message: String,
-}

+ 9 - 3
examples/dynamic_asset.rs

@@ -1,3 +1,9 @@
+//! This example shows how to load in custom assets with the use_asset_handler hook.
+//!
+//! This hook is currently only available on desktop and allows you to intercept any request made by the webview
+//! and respond with your own data. You could use this to load in custom videos, streams, stylesheets, images,
+//! or any asset that isn't known at compile time.
+
 use dioxus::desktop::{use_asset_handler, wry::http::Response};
 use dioxus::prelude::*;
 
@@ -16,8 +22,8 @@ fn app() -> Element {
     });
 
     rsx! {
-        div {
-            img { src: "/logos/logo.png" }
-        }
+        style { {include_str!("./assets/custom_assets.css")} }
+        h1 { "Dynamic Assets" }
+        img { src: "/logos/logo.png" }
     }
 }

+ 23 - 6
examples/error_handle.rs

@@ -1,3 +1,10 @@
+//! This example showcases how to use the ErrorBoundary component to handle errors in your app.
+//!
+//! The ErrorBoundary component is a special component that can be used to catch panics and other errors that occur.
+//! By default, Dioxus will catch panics during rendering, async, and handlers, and bubble them up to the nearest
+//! error boundary. If no error boundary is present, it will be caught by the root error boundary and the app will
+//! render the error message as just a string.
+
 use dioxus::{dioxus_core::CapturedError, prelude::*};
 
 fn main() {
@@ -7,7 +14,10 @@ fn main() {
 fn app() -> Element {
     rsx! {
         ErrorBoundary {
-            handle_error: |error: CapturedError| rsx! {"Found error {error}"},
+            handle_error: |error: CapturedError| rsx! {
+                h1 { "An error occurred" }
+                pre { "{error:#?}" }
+            },
             DemoC { x: 1 }
         }
     }
@@ -15,11 +25,18 @@ fn app() -> Element {
 
 #[component]
 fn DemoC(x: i32) -> Element {
-    let result = Err("Error");
-
-    result.throw()?;
-
     rsx! {
-        h1 { "{x}" }
+        h1 { "Error handler demo" }
+        button {
+            onclick: move |_| {
+                // Create an error
+                let result: Result<Element, &str> = Err("Error");
+
+                // And then call `throw` on it. The `throw` method is given by the `Throw` trait which is automatically
+                // imported via the prelude.
+                _ = result.throw();
+            },
+            "Click to throw an error"
+        }
     }
 }

+ 19 - 1
examples/eval.rs

@@ -1,3 +1,8 @@
+//! This example shows how to use the `eval` function to run JavaScript code in the webview.
+//!
+//! Eval will only work with renderers that support javascript - so currently only the web and desktop/mobile renderers
+//! that use a webview. Native renderers will throw "unsupported" errors when calling `eval`.
+
 use dioxus::prelude::*;
 
 fn main() {
@@ -5,20 +10,33 @@ fn main() {
 }
 
 fn app() -> Element {
+    // Create a future that will resolve once the javascript has been succesffully executed.
     let future = use_resource(move || async move {
+        // Wait a little bit just to give the appearance of a loading screen
+        tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
+
+        // The `eval` is available in the prelude - and simply takes a block of JS.
+        // Dioxus' eval is interesting since it allows sending messages to and from the JS code using the `await dioxus.recv()`
+        // builtin function. This allows you to create a two-way communication channel between Rust and JS.
         let mut eval = eval(
             r#"
                 dioxus.send("Hi from JS!");
                 let msg = await dioxus.recv();
                 console.log(msg);
-                return "hello world";
+                return "hi from JS!";
             "#,
         )
         .unwrap();
 
+        // Send a message to the JS code.
         eval.send("Hi from Rust!".into()).unwrap();
+
+        // Our line on the JS side will log the message and then return "hello world".
         let res = eval.recv().await.unwrap();
+
+        // This will print "Hi from JS!" and "Hi from Rust!".
         println!("{:?}", eval.await);
+
         res
     });
 

+ 21 - 20
examples/file_explorer.rs

@@ -1,12 +1,9 @@
 //! Example: File Explorer
-//! -------------------------
 //!
 //! This is a fun little desktop application that lets you explore the file system.
 //!
 //! This example is interesting because it's mixing filesystem operations and GUI, which is typically hard for UI to do.
-//!
-//! It also uses `use_ref` to maintain a model, rather than `use_state`. That way,
-//! we dont need to clutter our code with `read` commands.
+//! We store the state entirely in a single signal, making the explorer logic fairly easy to reason about.
 
 use dioxus::desktop::{Config, WindowBuilder};
 use dioxus::prelude::*;
@@ -37,25 +34,25 @@ fn app() -> Element {
             }
             style { "{_STYLE}" }
             main {
-                {files.read().path_names.iter().enumerate().map(|(dir_id, path)| {
-                    let path_end = path.split('/').last().unwrap_or(path.as_str());
-                    rsx! (
-                        div {
-                            class: "folder",
-                            key: "{path}",
-                            i { class: "material-icons",
-                                onclick: move |_| files.write().enter_dir(dir_id),
-                                if path_end.contains('.') {
-                                    "description"
-                                } else {
-                                    "folder"
+                for (dir_id, path) in files.read().path_names.iter().enumerate() {
+                    {
+                        let path_end = path.split('/').last().unwrap_or(path.as_str());
+                        rsx! {
+                            div { class: "folder", key: "{path}",
+                                i { class: "material-icons",
+                                    onclick: move |_| files.write().enter_dir(dir_id),
+                                    if path_end.contains('.') {
+                                        "description"
+                                    } else {
+                                        "folder"
+                                    }
+                                    p { class: "cooltip", "0 folders / 0 files" }
                                 }
-                                p { class: "cooltip", "0 folders / 0 files" }
+                                h1 { "{path_end}" }
                             }
-                            h1 { "{path_end}" }
                         }
-                    )
-                })},
+                    }
+                }
                 if let Some(err) = files.read().err.as_ref() {
                     div {
                         code { "{err}" }
@@ -67,6 +64,10 @@ fn app() -> Element {
     }
 }
 
+/// A simple little struct to hold the file explorer state
+///
+/// We don't use any fancy signals or memoization here - Dioxus is so fast that even a file explorer can be done with a
+/// single signal.
 struct Files {
     path_stack: Vec<String>,
     path_names: Vec<String>,

+ 22 - 12
examples/file_upload.rs

@@ -1,13 +1,17 @@
-#![allow(non_snake_case)]
+//! This example shows how to use the `file` methods on FormEvent and DragEvent to handle file uploads and drops.
+//!
+//! Dioxus intercepts these events and provides a Rusty interface to the file data. Since we want this interface to
+//! be crossplatform,
+
 use dioxus::html::HasFileData;
 use dioxus::prelude::*;
 use tokio::time::sleep;
 
 fn main() {
-    launch(App);
+    launch(app);
 }
 
-fn App() -> Element {
+fn app() -> Element {
     let mut enable_directory_upload = use_signal(|| false);
     let mut files_uploaded = use_signal(|| Vec::new() as Vec<String>);
 
@@ -31,12 +35,16 @@ fn App() -> Element {
     };
 
     rsx! {
+        style { {include_str!("./assets/file_upload.css")} }
+
+        input {
+            r#type: "checkbox",
+            id: "directory-upload",
+            checked: enable_directory_upload,
+            oninput: move |evt| enable_directory_upload.set(evt.checked()),
+        },
         label {
-            input {
-                r#type: "checkbox",
-                checked: enable_directory_upload,
-                oninput: move |evt| enable_directory_upload.set(evt.checked()),
-            },
+            r#for: "directory-upload",
             "Enable directory upload"
         }
 
@@ -47,16 +55,18 @@ fn App() -> Element {
             directory: enable_directory_upload,
             onchange: upload_files,
         }
+
         div {
-            width: "100px",
-            height: "100px",
-            border: "1px solid black",
+            // cheating with a little bit of JS...
+            "ondragover": "this.style.backgroundColor='#88FF88';",
+            "ondragleave": "this.style.backgroundColor='#FFFFFF';",
+
+            id: "drop-zone",
             prevent_default: "ondrop dragover dragenter",
             ondrop: handle_file_drop,
             ondragover: move |event| event.stop_propagation(),
             "Drop files here"
         }
-
         ul {
             for file in files_uploaded.read().iter() {
                 li { "{file}" }

+ 0 - 17
examples/filedragdrop.rs

@@ -1,17 +0,0 @@
-use dioxus::desktop::Config;
-use dioxus::prelude::*;
-
-fn main() {
-    LaunchBuilder::desktop()
-        .with_cfg(Config::new().with_file_drop_handler(|_w, e| {
-            println!("{e:?}");
-            true
-        }))
-        .launch(app)
-}
-
-fn app() -> Element {
-    rsx!(
-        div { h1 { "drag a file here and check your console" } }
-    )
-}

+ 31 - 19
examples/flat_router.rs

@@ -1,8 +1,18 @@
+//! This example shows how to use the `Router` component to create a simple navigation system.
+//! The more complex router example uses all of the router features, while this simple exmaple showcases
+//! just the `Layout` and `Route` features.
+//!
+//! Layouts let you wrap chunks of your app with a component. This is useful for things like a footers, heeaders, etc.
+//! Routes are enum variants with that match the name of a component in scope. This way you can create a new route
+//! in your app simply by adding the variant to the enum and creating a new component with the same name. You can
+//! override this of course.
+
 use dioxus::prelude::*;
 
 fn main() {
     launch(|| {
         rsx! {
+            style { {include_str!("./assets/flat_router.css")} }
             Router::<Route> {}
         }
     })
@@ -11,7 +21,7 @@ fn main() {
 #[derive(Routable, Clone)]
 #[rustfmt::skip]
 enum Route {
-    #[layout(Footer)]
+    #[layout(Footer)] // wrap the entire app in a footer
         #[route("/")]
         Home {},
 
@@ -28,45 +38,47 @@ enum Route {
 #[component]
 fn Footer() -> Element {
     rsx! {
-        Outlet::<Route> {}
-        p { "----" }
         nav {
-            style { {STYLE} }
             Link { to: Route::Home {}, class: "nav-btn", "Home" }
             Link { to: Route::Games {}, class: "nav-btn", "Games" }
             Link { to: Route::Play {}, class: "nav-btn", "Play" }
             Link { to: Route::Settings {}, class: "nav-btn", "Settings" }
         }
+        div { id: "content",
+            Outlet::<Route> {}
+        }
     }
 }
 
 #[component]
 fn Home() -> Element {
-    rsx!("Home")
+    rsx!(
+        h1 { "Home" }
+        p { "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." }
+    )
 }
 
 #[component]
 fn Games() -> Element {
-    rsx!("Games")
+    rsx!(
+        h1 { "Games" }
+        // Dummy text that talks about video games
+        p { "Lorem games are sit amet  Sed do eiusmod tempor et dolore magna aliqua." }
+    )
 }
 
 #[component]
 fn Play() -> Element {
-    rsx!("Play")
+    rsx!(
+        h1 { "Play" }
+        p { "Always play with your full heart adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." }
+    )
 }
 
 #[component]
 fn Settings() -> Element {
-    rsx!("Settings")
+    rsx!(
+        h1 { "Settings" }
+        p { "Settings are consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." }
+    )
 }
-
-const STYLE: &str = r#"
-    nav {
-        display: flex;
-        justify-content: space-around;
-    }
-    .nav-btn {
-        text-decoration: none;
-        color: black;
-    }
-"#;

+ 1 - 1
examples/form.rs

@@ -1,7 +1,7 @@
 //! Forms
 //!
 //! Dioxus forms deviate slightly from html, automatically returning all named inputs
-//! in the "values" field
+//! in the "values" field.
 
 use dioxus::prelude::*;
 

+ 38 - 0
examples/future.rs

@@ -0,0 +1,38 @@
+//! A simple example that shows how to use the use_future hook to run a background task.
+//!
+//! use_future assumes your future will never complete - it won't return a value.
+//! If you want to return a value, use use_resource instead.
+
+use dioxus::prelude::*;
+use std::time::Duration;
+
+fn main() {
+    launch_desktop(app);
+}
+
+fn app() -> Element {
+    let mut count = use_signal(|| 0);
+
+    // use_future will run the future
+    use_future(move || async move {
+        loop {
+            tokio::time::sleep(Duration::from_millis(200)).await;
+            count += 1;
+        }
+    });
+
+    // We can also spawn futures from effects, handlers, or other futures
+    use_effect(move || {
+        spawn(async move {
+            tokio::time::sleep(Duration::from_secs(5)).await;
+            count.set(100);
+        });
+    });
+
+    rsx! {
+        div {
+            h1 { "Current count: {count}" }
+            button { onclick: move |_| count.set(0), "Reset the count" }
+        }
+    }
+}

+ 5 - 1
examples/generic_component.rs

@@ -1,6 +1,10 @@
-use std::fmt::Display;
+//! This example demonstrates how to create a generic component in Dioxus.
+//!
+//! Generic components can be useful when you want to create a component that renders differently depending on the type
+//! of data it receives. In this particular example, we're just using a type that implements `Display` and `PartialEq`,
 
 use dioxus::prelude::*;
+use std::fmt::Display;
 
 fn main() {
     launch_desktop(app);

+ 41 - 3
examples/global.rs

@@ -1,6 +1,9 @@
-//! Example: README.md showcase
+//! Example: Global signals and memos
 //!
-//! The example from the README.md.
+//! This example demonstrates how to use global signals and memos to share state across your app.
+//! Global signals are simply signals that live on the root of your app and are accessible from anywhere. To access a
+//! global signal, simply use its methods like a regular signal. Calls to `read` and `write` will be forwarded to the
+//! signal at the root of your app using the `static`'s address.
 
 use dioxus::prelude::*;
 
@@ -13,8 +16,43 @@ static DOUBLED_COUNT: GlobalMemo<i32> = Signal::global_memo(|| COUNT() * 2);
 
 fn app() -> Element {
     rsx! {
-        h1 { "{COUNT} x 2 = {DOUBLED_COUNT}" }
+        style { {include_str!("./assets/counter.css")} }
+        Increment {}
+        Decrement {}
+        Reset {}
+        Display {}
+    }
+}
+
+#[component]
+fn Increment() -> Element {
+    rsx! {
         button { onclick: move |_| *COUNT.write() += 1, "Up high!" }
+    }
+}
+
+#[component]
+fn Decrement() -> Element {
+    rsx! {
         button { onclick: move |_| *COUNT.write() -= 1, "Down low!" }
     }
 }
+
+#[component]
+fn Display() -> Element {
+    rsx! {
+        p { "Count: ", "{COUNT}" }
+        p { "Doubled: ", "{DOUBLED_COUNT}" }
+    }
+}
+
+#[component]
+fn Reset() -> Element {
+    // Not all write methods are availale on global signals since `write` requires a mutable reference. In these cases,
+    // We can simply pull out the actual signal using the signal() method.
+    let mut as_signal = use_hook(|| COUNT.signal());
+
+    rsx! {
+        button { onclick: move |_| as_signal.set(0), "Reset" }
+    }
+}

+ 11 - 0
examples/hello_world.rs

@@ -1,3 +1,14 @@
+//! The simplest example of a Dioxus app.
+//!
+//! In this example we:
+//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.)
+//! - define a main function that calls the launch function with our app function
+//! - define an app function that returns a div element with the text "Hello, world!"
+//!
+//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function
+//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web
+//! app, and if the `desktop` feature is enabled, it will launch a desktop app.
+
 use dioxus::prelude::*;
 
 fn main() {

+ 0 - 162
examples/inputs.rs

@@ -1,162 +0,0 @@
-//! This example roughly shows how events are serialized into Rust from JavaScript.
-//!
-//! There is some conversion happening when input types are checkbox/radio/select/textarea etc.
-
-use dioxus::prelude::*;
-
-fn main() {
-    launch_desktop(app);
-}
-
-const FIELDS: &[(&str, &str)] = &[
-    ("button", "Click me!"),
-    ("checkbox", "CHECKBOX"),
-    ("color", ""),
-    ("date", ""),
-    ("datetime-local", ""),
-    ("email", ""),
-    ("file", ""),
-    ("image", ""),
-    ("number", ""),
-    ("password", ""),
-    ("radio", ""),
-    ("range", ""),
-    ("reset", ""),
-    ("search", ""),
-    ("submit", ""),
-    ("tel", ""),
-    ("text", ""),
-    ("time", ""),
-    ("url", ""),
-    // less supported things
-    ("hidden", ""),
-    ("month", ""), // degrades to text most of the time, but works properly as "value'"
-    ("week", ""),  // degrades to text most of the time
-];
-
-fn app() -> Element {
-    rsx! {
-        div { margin_left: "30px",
-            {select_example()},
-            div {
-                // handling inputs on divs will catch all input events below
-                // so the value of our input event will be either huey, dewey, louie, or true/false (because of the checkboxe)
-                // be mindful in grouping inputs together, as they will all be handled by the same event handler
-                oninput: move |evt| println!("{evt:?}"),
-                div {
-                    input {
-                        id: "huey",
-                        r#type: "radio",
-                        value: "huey",
-                        checked: true,
-                        name: "drone",
-                    }
-                    label {
-                        r#for: "huey",
-                        "Huey"
-                    }
-                }
-                div {
-                    input {
-                        id: "dewey",
-                        r#type: "radio",
-                        value: "dewey",
-                        name: "drone",
-                    }
-                    label { r#for: "dewey", "Dewey" }
-                }
-                div {
-                    input {
-                        id: "louie",
-                        value: "louie",
-                        r#type: "radio",
-                        name: "drone",
-                    }
-                    label {
-                        r#for: "louie",
-                        "Louie"
-                    }
-                }
-                div {
-                    input {
-                        id: "groovy",
-                        value: "groovy",
-                        r#type: "checkbox",
-                        name: "drone",
-                    }
-                    label {
-                        r#for: "groovy",
-                        "groovy"
-                    }
-                }
-            }
-
-            // elements with driven values will preventdefault automatically.
-            // you can disable this override with preventdefault: false
-            div {
-                input {
-                    id: "pdf",
-                    value: "pdf",
-                    name: "pdf",
-                    r#type: "checkbox",
-                    oninput: move |evt| {
-                        println!("{evt:?}");
-                    },
-                }
-                label {
-                    r#for: "pdf",
-                    "pdf"
-                }
-            }
-
-            for (field, value) in FIELDS.iter() {
-                div {
-                    input {
-                        id: "{field}",
-                        name: "{field}",
-                        r#type: "{field}",
-                        value: "{value}",
-                        oninput: move |evt: FormEvent| {
-                            println!("{evt:?}");
-                        },
-                    }
-                    label {
-                        r#for: "{field}",
-                        "{field} element"
-                    }
-                    br {}
-                }
-            }
-        }
-    }
-}
-
-fn select_example() -> Element {
-    rsx! {
-        div {
-            select {
-                id: "selection",
-                name: "selection",
-                multiple: true,
-                oninput: move |evt| println!("{evt:?}"),
-                option {
-                    value: "Option 1",
-                    label: "Option 1",
-                }
-                option {
-                    value: "Option 2",
-                    label: "Option 2",
-                    selected: true,
-                },
-                option {
-                    value: "Option 3",
-                    label: "Option 3",
-                }
-            }
-            label {
-                r#for: "selection",
-                "select element"
-            }
-        }
-    }
-}

+ 40 - 22
examples/link.rs

@@ -1,24 +1,21 @@
+//! How to use links in Dioxus
+//!
+//! The `router` crate gives us a `Link` component which is a much more powerful version of the standard HTML link.
+//! However, you can use the traditional `<a>` tag if you want to build your own `Link` component.
+//!
+//! The `Link` component integrates with the Router and is smart enough to detect if the link is internal or external.
+//! It also allows taking any `Route` as a target, making your links typesafe
+
 use dioxus::prelude::*;
 
 fn main() {
-    launch_desktop(App);
+    launch_desktop(app);
 }
 
-#[component]
-fn App() -> Element {
+fn app() -> Element {
     rsx! (
-        div {
-            p { a { href: "http://dioxuslabs.com/", "Default link - links outside of your app" } }
-            p {
-                a {
-                    href: "http://dioxuslabs.com/",
-                    prevent_default: "onclick",
-                    onclick: |_| println!("Hello Dioxus"),
-                    "Custom event link - links inside of your app"
-                }
-            }
-        }
-        div { Router::<Route> {} }
+        style { {include_str!("./assets/links.css")} }
+        Router::<Route> {}
     )
 }
 
@@ -28,6 +25,10 @@ enum Route {
     #[layout(Header)]
         #[route("/")]
         Home {},
+
+        #[route("/default-links")]
+        DefaultLinks {},
+
         #[route("/settings")]
         Settings {},
 }
@@ -36,13 +37,10 @@ enum Route {
 fn Header() -> Element {
     rsx! {
         h1 { "Your app here" }
-        ul {
-            li {
-                Link { to: Route::Home {}, "home" }
-            }
-            li {
-                Link { to: Route::Settings {}, "settings" }
-            }
+        nav { id: "nav",
+            Link { to: Route::Home {}, "home" }
+            Link { to: Route::DefaultLinks {}, "default links" }
+            Link { to: Route::Settings {}, "settings" }
         }
         Outlet::<Route> {}
     }
@@ -57,3 +55,23 @@ fn Home() -> Element {
 fn Settings() -> Element {
     rsx!( h1 { "Settings" } )
 }
+
+#[component]
+fn DefaultLinks() -> Element {
+    rsx! {
+        // Just some default links
+        div { id: "external-links",
+            // This link will open in a webbrowser
+            a { href: "http://dioxuslabs.com/", "Default link - links outside of your app" }
+
+            // This link will do nothing - we're preventing the default behavior
+            // It will just log "Hello Dioxus" to the console
+            a {
+                href: "http://dioxuslabs.com/",
+                prevent_default: "onclick",
+                onclick: |_| println!("Hello Dioxus"),
+                "Custom event link - links inside of your app"
+            }
+        }
+    }
+}

+ 7 - 2
examples/login_form.rs

@@ -1,5 +1,10 @@
-//! This example demonstrates the following:
-//! Futures in a callback, Router, and Forms
+//! Implementing a login form
+//!
+//! This example demonstrates how to implement a login form using Dioxus desktop. Since forms typically navigate the
+//! page on submit, we need to intercept the onsubmit event and send a request to a server. On the web, we could
+//! just leave the submit action` as is, but on desktop, we need to handle the form submission ourselves.
+//!
+//! Todo: actually spin up a server and run the login flow. Login is way more complex than a form override :)
 
 use dioxus::prelude::*;
 

+ 5 - 0
examples/memo_chain.rs

@@ -1,3 +1,8 @@
+//! This example shows how you can chain memos together to create a tree of memoized values.
+//!
+//! Memos will also pause when their parent component pauses, so if you have a memo that depends on a signal, and the
+//! signal pauses, the memo will pause too.
+
 use dioxus::prelude::*;
 
 fn main() {

+ 6 - 0
examples/multiwindow.rs

@@ -1,3 +1,9 @@
+//! Multiwindow example
+//!
+//! This exmaple shows how to implement a simple multiwindow application using dioxus.
+//! This works by spawning a new window when the user clicks a button. We have to build a new virtualdom which has its
+//! own context, root elements, etc.
+
 use dioxus::prelude::*;
 
 fn main() {

+ 12 - 4
examples/optional_props.rs

@@ -1,8 +1,7 @@
-#![allow(non_snake_case)]
-
-//! Example: README.md showcase
+//! Optional props
 //!
-//! The example from the README.md.
+//! This example demonstrates how to use optional props in your components. The `Button` component has several props,
+//! and we use a variety of attributes to set them.
 
 use dioxus::prelude::*;
 
@@ -12,19 +11,27 @@ fn main() {
 
 fn app() -> Element {
     rsx! {
+        // We can set some of the props, and the rest will be filled with their default values
+        // By default `c` can take a `None` value, but `d` is required to wrap a `Some` value
         Button {
             a: "asd".to_string(),
+            // b can be omitted, and it will be filled with its default value
             c: "asd".to_string(),
             d: Some("asd".to_string()),
             e: Some("asd".to_string()),
         }
+
         Button {
             a: "asd".to_string(),
             b: "asd".to_string(),
+
+            // We can omit the `Some` on `c` since Dioxus automatically transforms Option<T> into optional
             c: "asd".to_string(),
             d: Some("asd".to_string()),
             e: "asd".to_string(),
         }
+
+        // `b` and `e` are ommitted
         Button {
             a: "asd".to_string(),
             c: "asd".to_string(),
@@ -51,6 +58,7 @@ struct ButtonProps {
 
 type SthElse<T> = Option<T>;
 
+#[allow(non_snake_case)]
 fn Button(props: ButtonProps) -> Element {
     rsx! {
         button {

+ 27 - 12
examples/overlay.rs

@@ -1,4 +1,13 @@
-use dioxus::desktop::{tao::dpi::PhysicalPosition, LogicalSize, WindowBuilder};
+//! This example demonstrates how to create an overlay window with dioxus.
+//!
+//! Basically, we just create a new window with a transparent background and no decorations, size it to the screen, and
+//! then we can draw whatever we want on it. In this case, we're drawing a simple overlay with a draggable header.
+//!
+//! We also add a global shortcut to toggle the overlay on and off, so you could build a raycast-type app with this.
+
+use dioxus::desktop::{
+    tao::dpi::PhysicalPosition, use_global_shortcut, LogicalSize, WindowBuilder,
+};
 use dioxus::prelude::*;
 
 fn main() {
@@ -6,21 +15,27 @@ fn main() {
 }
 
 fn app() -> Element {
-    rsx! {
-        div {
-            width: "100%",
-            height: "100%",
-            background_color: "red",
-            border: "1px solid black",
+    let mut show_overlay = use_signal(|| true);
 
+    _ = use_global_shortcut("cmd+g", move || show_overlay.toggle());
+
+    rsx! {
+        if show_overlay() {
             div {
                 width: "100%",
-                height: "10px",
-                background_color: "black",
-                onmousedown: move |_| dioxus::desktop::window().drag(),
-            }
+                height: "100%",
+                background_color: "red",
+                border: "1px solid black",
+
+                div {
+                    width: "100%",
+                    height: "10px",
+                    background_color: "black",
+                    onmousedown: move |_| dioxus::desktop::window().drag(),
+                }
 
-            "This is an overlay!"
+                "This is an overlay!"
+            }
         }
     }
 }

+ 6 - 8
examples/compose.rs → examples/popup.rs

@@ -1,9 +1,8 @@
 //! This example shows how to create a popup window and send data back to the parent window.
-
-use std::rc::Rc;
+//! Currently Dioxus doesn't support nested renderers, hence the need to create popups as separate windows.
 
 use dioxus::prelude::*;
-use futures_util::StreamExt;
+use std::rc::Rc;
 
 fn main() {
     launch_desktop(app);
@@ -14,6 +13,7 @@ fn app() -> Element {
 
     // Wait for responses to the compose channel, and then push them to the emails_sent signal.
     let handle = use_coroutine(|mut rx: UnboundedReceiver<String>| async move {
+        use futures_util::StreamExt;
         while let Some(message) = rx.next().await {
             emails_sent.write().push(message);
         }
@@ -22,7 +22,7 @@ fn app() -> Element {
     let open_compose_window = move |_evt: MouseEvent| {
         let tx = handle.tx();
         dioxus::desktop::window().new_window(
-            VirtualDom::new_with_props(compose, Rc::new(move |s| tx.unbounded_send(s).unwrap())),
+            VirtualDom::new_with_props(popup, Rc::new(move |s| tx.unbounded_send(s).unwrap())),
             Default::default(),
         );
     };
@@ -41,21 +41,19 @@ fn app() -> Element {
     }
 }
 
-fn compose(send: Rc<dyn Fn(String)>) -> Element {
+fn popup(send: Rc<dyn Fn(String)>) -> Element {
     let mut user_input = use_signal(String::new);
 
     rsx! {
         div {
             h1 { "Compose a new email" }
-
             button {
                 onclick: move |_| {
                     send(user_input.cloned());
                     dioxus::desktop::window().close();
                 },
-                "Click to send"
+                "Send"
             }
-
             input { oninput: move |e| user_input.set(e.value()), value: "{user_input}" }
         }
     }

+ 7 - 1
examples/read_size.rs

@@ -1,4 +1,9 @@
-#![allow(clippy::await_holding_refcell_ref)]
+//! Read the size of elements using the MountedData struct.
+//!
+//! Whenever an Element is finally mounted to the Dom, its data is avaiable to be read.
+//! These fields can typically only be read asynchronously, since various renderers need to release the main thread to
+//! perform layout and painting.
+
 use std::rc::Rc;
 
 use dioxus::{html::geometry::euclid::Rect, prelude::*};
@@ -33,6 +38,7 @@ fn app() -> Element {
     let read_dims = move |_| async move {
         let read = div_element.read();
         let client_rect = read.as_ref().map(|el| el.get_client_rect());
+
         if let Some(client_rect) = client_rect {
             if let Ok(rect) = client_rect.await {
                 dimensions.set(rect);

+ 7 - 0
examples/readme.rs

@@ -1,3 +1,10 @@
+//! The example from the readme!
+//!
+//! This example demonstrates how to create a simple counter app with dioxus. The `Signal` type wraps inner values,
+//! making them `Copy`, allowing them to be freely used in closures and and async functions. `Signal` also provides
+//! helper methods like AddAssign, SubAssign, toggle, etc, to make it easy to update the value without running
+//! into lock issues.
+
 use dioxus::prelude::*;
 
 fn main() {

+ 9 - 5
examples/reducer.rs

@@ -15,12 +15,16 @@ fn app() -> Element {
     let mut state = use_signal(|| PlayerState { is_playing: false });
 
     rsx!(
-        div {
-            h1 {"Select an option"}
-            h3 { "The radio is... ", {state.read().is_playing()}, "!" }
-            button { onclick: move |_| state.write().reduce(PlayerAction::Pause), "Pause" }
-            button { onclick: move |_| state.write().reduce(PlayerAction::Play), "Play" }
+        style { {include_str!("./assets/radio.css")} }
+        h1 {"Select an option"}
+
+        // Add some cute animations if the radio is playing!
+        div { class: if state.read().is_playing { "bounce" },
+            "The radio is... ", {state.read().is_playing()}, "!"
         }
+
+        button { id: "play", onclick: move |_| state.write().reduce(PlayerAction::Pause), "Pause" }
+        button { id: "pause", onclick: move |_| state.write().reduce(PlayerAction::Play), "Play" }
     )
 }
 

+ 53 - 31
examples/router.rs

@@ -1,32 +1,59 @@
+//! An advanced usage of the router with nested routes and redirects.
+//!
+//! Dioxus implements an enum-based router, which allows you to define your routes in a type-safe way.
+//! However, since we need to bake quite a bit of logic into the enum, we have to add some extra syntax.
+//!
+//! Note that you don't need to use advanced features like nest, redirect, etc, since these can all be implemented
+//! manually, but they are provided as a convenience.
+
 use dioxus::prelude::*;
 
 fn main() {
     launch_desktop(|| {
         rsx! {
+            style { {include_str!("./assets/router.css")} }
             Router::<Route> {}
         }
     });
 }
 
+// Turn off rustfmt since we're doing layouts and routes in the same enum
 #[derive(Routable, Clone, Debug, PartialEq)]
 #[rustfmt::skip]
 enum Route {
+    // Wrap Home in a Navbar Layout
     #[layout(NavBar)]
+        // The default route is always "/" unless otherwise specified
         #[route("/")]
         Home {},
+
+        // Wrap the next routes in a layout and a nest
         #[nest("/blog")]
-            #[layout(Blog)]
-                #[route("/")]
-                BlogList {},
-                #[route("/:name")]
-                BlogPost { name: String },
-            #[end_layout]
+        #[layout(Blog)]
+            // At "/blog", we want to show a list of blog posts
+            #[route("/")]
+            BlogList {},
+
+            // At "/blog/:name", we want to show a specific blog post, using the name slug
+            #[route("/:name")]
+            BlogPost { name: String },
+
+        // We need to end the blog layout and nest
+        // Note we don't need either - we could've just done `/blog/` and `/blog/:name` without nesting,
+        // but it's a bit cleaner this way
+        #[end_layout]
         #[end_nest]
+
+    // And the regular page layout
     #[end_layout]
+
+    // Add some redirects for the `/myblog` route
     #[nest("/myblog")]
         #[redirect("/", || Route::BlogList {})]
         #[redirect("/:name", |name: String| Route::BlogPost { name })]
     #[end_nest]
+
+    // Finally, we need to handle the 404 page
     #[route("/:..route")]
     PageNotFound {
         route: Vec<String>,
@@ -36,15 +63,9 @@ enum Route {
 #[component]
 fn NavBar() -> Element {
     rsx! {
-        nav {
-            ul {
-                li {
-                    Link { to: Route::Home {}, "Home" }
-                }
-                li {
-                    Link { to: Route::BlogList {}, "Blog" }
-                }
-            }
+        nav { id: "navbar",
+            Link { to: Route::Home {}, "Home" }
+            Link { to: Route::BlogList {}, "Blog" }
         }
         Outlet::<Route> {}
     }
@@ -67,30 +88,31 @@ fn Blog() -> Element {
 fn BlogList() -> Element {
     rsx! {
         h2 { "Choose a post" }
-        ul {
-            li {
-                Link {
-                    to: Route::BlogPost {
-                        name: "Blog post 1".into(),
-                    },
-                    "Read the first blog post"
-                }
+        div { id: "blog-list",
+            Link { to: Route::BlogPost { name: "Blog post 1".into() },
+                "Read the first blog post"
             }
-            li {
-                Link {
-                    to: Route::BlogPost {
-                        name: "Blog post 2".into(),
-                    },
-                    "Read the second blog post"
-                }
+            Link { to: Route::BlogPost { name: "Blog post 2".into() },
+                "Read the second blog post"
             }
         }
     }
 }
 
+// We can use the `name` slug to show a specific blog post
+// In theory we could read from the filesystem or a database here
 #[component]
 fn BlogPost(name: String) -> Element {
-    rsx! { h2 { "Blog Post: {name}" } }
+    let contents = match name.as_str() {
+        "Blog post 1" => "This is the first blog post. It's not very interesting.",
+        "Blog post 2" => "This is the second blog post. It's not very interesting either.",
+        _ => "This blog post doesn't exist.",
+    };
+
+    rsx! {
+        h2 { "{name}" }
+        p { "{contents}" }
+    }
 }
 
 #[component]

+ 261 - 258
examples/rsx_usage.rs

@@ -39,263 +39,266 @@
 //! - Allow top-level fragments
 
 fn main() {
-    todo!()
-    //launch_desktop(App);
+    launch(app)
 }
 
-// use core::{fmt, str::FromStr};
-// use std::fmt::Display;
-
-// use baller::Baller;
-// use dioxus::prelude::*;
-
-// #[component]
-// fn App() -> Element {
-//     let formatting = "formatting!";
-//     let formatting_tuple = ("a", "b");
-//     let lazy_fmt = format_args!("lazily formatted text");
-//     let asd = 123;
-//     rsx! {
-//         div {
-//             // Elements
-//             div {}
-//             h1 {"Some text"}
-//             h1 {"Some text with {formatting}"}
-//             h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"}
-//             h1 {"Formatting without interpolation " {formatting_tuple.0} "and" {formatting_tuple.1} }
-//             h2 {
-//                 "Multiple"
-//                 "Text"
-//                 "Blocks"
-//                 "Use comments as separators in html"
-//             }
-//             div {
-//                 h1 {"multiple"}
-//                 h2 {"nested"}
-//                 h3 {"elements"}
-//             }
-//             div {
-//                 class: "my special div",
-//                 h1 {"Headers and attributes!"}
-//             }
-//             div {
-//                 // pass simple rust expressions in
-//                 class: lazy_fmt,
-//                 id: format_args!("attributes can be passed lazily with std::fmt::Arguments"),
-//                 class: "asd",
-//                 class: "{asd}",
-//                 // if statements can be used to conditionally render attributes
-//                 class: if formatting.contains("form") { "{asd}" },
-//                 div {
-//                     class: {
-//                         const WORD: &str = "expressions";
-//                         format_args!("Arguments can be passed in through curly braces for complex {WORD}")
-//                     }
-//                 }
-//             }
-
-//             // Expressions can be used in element position too:
-//             {rsx!(p { "More templating!" })},
-
-//             // Iterators
-//             {(0..10).map(|i| rsx!(li { "{i}" }))},
-
-//             // Iterators within expressions
-//             {
-//                 let data = std::collections::HashMap::<&'static str, &'static str>::new();
-//                 // Iterators *should* have keys when you can provide them.
-//                 // Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
-//                 // Using an "ID" associated with your data is a good idea.
-//                 data.into_iter().map(|(k, v)| rsx!(li { key: "{k}", "{v}" }))
-//             }
-
-//             // Matching
-//             match true {
-//                 true => rsx!( h1 {"Top text"}),
-//                 false => rsx!( h1 {"Bottom text"})
-//             }
-
-//             // Conditional rendering
-//             // Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
-//             // You can convert a bool condition to rsx! with .then and .or
-//             {true.then(|| rsx!(div {}))},
-
-//             // Alternatively, you can use the "if" syntax - but both branches must be resolve to Element
-//             if false {
-//                 h1 {"Top text"}
-//             } else {
-//                 h1 {"Bottom text"}
-//             }
-
-//             // Using optionals for diverging branches
-//             // Note that since this is wrapped in curlies, it's interpreted as an expression
-//             {if true {
-//                 Some(rsx!(h1 {"Top text"}))
-//             } else {
-//                 None
-//             }}
-
-//             // returning "None" without a diverging branch is a bit noisy... but rare in practice
-//             {None as Option<()>},
-
-//             // can also just use empty fragments
-//             Fragment {}
-
-//             // Fragments let you insert groups of nodes without a parent.
-//             // This lets you make components that insert elements as siblings without a container.
-//             div {"A"}
-//             Fragment {
-//                 div {"B"}
-//                 div {"C"}
-//                 Fragment {
-//                     "D"
-//                     Fragment {
-//                         "E"
-//                         "F"
-//                     }
-//                 }
-//             }
-
-//             // Components
-//             // Can accept any paths
-//             // Notice how you still get syntax highlighting and IDE support :)
-//             Baller {}
-//             baller::Baller {}
-//             crate::baller::Baller {}
-
-//             // Can take properties
-//             Taller { a: "asd" }
-
-//             // Can take optional properties
-//             Taller { a: "asd" }
-
-//             // Can pass in props directly as an expression
-//             {
-//                 let props = TallerProps {a: "hello", children: None };
-//                 rsx!(Taller { ..props })
-//             }
-
-//             // Spreading can also be overridden manually
-//             Taller {
-//                 ..TallerProps { a: "ballin!", children: None },
-//                 a: "not ballin!"
-//             }
-
-//             // Can take children too!
-//             Taller { a: "asd", div {"hello world!"} }
-
-//             // This component's props are defined *inline* with the `inline_props` macro
-//             WithInline { text: "using functionc all syntax" }
-
-//             // Components can be generic too
-//             // This component takes i32 type to give you typed input
-//             TypedInput::<i32> {}
-
-//             // Type inference can be used too
-//             TypedInput { initial: 10.0 }
-
-//             // geneircs with the `inline_props` macro
-//             Label { text: "hello geneirc world!" }
-//             Label { text: 99.9 }
-
-//             // Lowercase components work too, as long as they are access using a path
-//             baller::lowercase_component {}
-
-//             // For in-scope lowercase components, use the `self` keyword
-//             self::lowercase_helper {}
-
-//             // helper functions
-//             // Anything that implements IntoVnode can be dropped directly into Rsx
-//             {helper("hello world!")}
-
-//             // Strings can be supplied directly
-//             {String::from("Hello world!")}
-
-//             // So can format_args
-//             {format_args!("Hello {}!", "world")}
-
-//             // Or we can shell out to a helper function
-//             {format_dollars(10, 50)}
-//         }
-//     }
-// }
-
-// fn format_dollars(dollars: u32, cents: u32) -> String {
-//     format!("${dollars}.{cents:02}")
-// }
-
-// fn helper<'a>(cx: &'a ScopeState, text: &'a str) -> Element {
-//     rsx! {
-//         p { "{text}" }
-//     }
-// }
-
-// // no_case_check disables PascalCase checking if you *really* want a snake_case component.
-// // This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
-// // something like Clippy.
-// #[component(no_case_check)]
-// fn lowercase_helper() -> Element {
-//     rsx! {
-//         "asd"
-//     }
-// }
-
-// mod baller {
-//     use super::*;
-
-//     #[component]
-//     /// This component totally balls
-//     pub fn Baller() -> Element {
-//         todo!()
-//     }
-
-//     // no_case_check disables PascalCase checking if you *really* want a snake_case component.
-//     // This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
-//     // something like Clippy.
-//     #[component(no_case_check)]
-//     pub fn lowercase_component() -> Element {
-//         rsx! { "look ma, no uppercase" }
-//     }
-// }
-
-// /// Documention for this component is visible within the rsx macro
-// #[component]
-// pub fn Taller(
-//     /// Fields are documented and accessible in rsx!
-//     a: &'static str,
-//     children: Element,
-// ) -> Element {
-//     rsx! { {&children} }
-// }
-
-// #[derive(Props, PartialEq, Eq)]
-// pub struct TypedInputProps<T> {
-//     #[props(optional, default)]
-//     initial: Option<T>,
-// }
-
-// #[allow(non_snake_case)]
-// pub fn TypedInput<T>(_: Scope<TypedInputProps<T>>) -> Element
-// where
-//     T: FromStr + fmt::Display,
-//     <T as FromStr>::Err: std::fmt::Display,
-// {
-//     todo!()
-// }
-
-// #[component]
-// fn WithInline(cx: Scope<'a>, text: &'a str) -> Element {
-//     rsx! {
-//         p { "{text}" }
-//     }
-// }
-
-// #[component]
-// fn Label<T: Clone + PartialEq>(text: T) -> Element
-// where
-//     T: Display,
-// {
-//     rsx! {
-//         p { "{text}" }
-//     }
-// }
+use core::{fmt, str::FromStr};
+use std::fmt::Display;
+
+use baller::Baller;
+use dioxus::prelude::*;
+
+fn app() -> Element {
+    let formatting = "formatting!";
+    let formatting_tuple = ("a", "b");
+    let lazy_fmt = format_args!("lazily formatted text");
+    let asd = 123;
+
+    rsx! {
+        div {
+            // Elements
+            div {}
+            h1 {"Some text"}
+            h1 {"Some text with {formatting}"}
+            h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"}
+            h1 {"Formatting without interpolation " {formatting_tuple.0} "and" {formatting_tuple.1} }
+            h2 {
+                "Multiple"
+                "Text"
+                "Blocks"
+                "Use comments as separators in html"
+            }
+            div {
+                h1 {"multiple"}
+                h2 {"nested"}
+                h3 {"elements"}
+            }
+            div {
+                class: "my special div",
+                h1 {"Headers and attributes!"}
+            }
+            div {
+                // pass simple rust expressions in
+                class: lazy_fmt,
+                id: format_args!("attributes can be passed lazily with std::fmt::Arguments"),
+                class: "asd",
+                class: "{asd}",
+                // if statements can be used to conditionally render attributes
+                class: if formatting.contains("form") { "{asd}" },
+                div {
+                    class: {
+                        const WORD: &str = "expressions";
+                        format_args!("Arguments can be passed in through curly braces for complex {WORD}")
+                    }
+                }
+            }
+
+            // Expressions can be used in element position too:
+            {rsx!(p { "More templating!" })},
+
+            // Iterators
+            {(0..10).map(|i| rsx!(li { "{i}" }))},
+
+            // Iterators within expressions
+            {
+                let data = std::collections::HashMap::<&'static str, &'static str>::new();
+                // Iterators *should* have keys when you can provide them.
+                // Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
+                // Using an "ID" associated with your data is a good idea.
+                data.into_iter().map(|(k, v)| rsx!(li { key: "{k}", "{v}" }))
+            }
+
+            // Matching
+            match true {
+                true => rsx!( h1 {"Top text"}),
+                false => rsx!( h1 {"Bottom text"})
+            }
+
+            // Conditional rendering
+            // Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
+            // You can convert a bool condition to rsx! with .then and .or
+            {true.then(|| rsx!(div {}))},
+
+            // Alternatively, you can use the "if" syntax - but both branches must be resolve to Element
+            if false {
+                h1 {"Top text"}
+            } else {
+                h1 {"Bottom text"}
+            }
+
+            // Using optionals for diverging branches
+            // Note that since this is wrapped in curlies, it's interpreted as an expression
+            {if true {
+                Some(rsx!(h1 {"Top text"}))
+            } else {
+                None
+            }}
+
+            // returning "None" without a diverging branch is a bit noisy... but rare in practice
+            {None as Option<()>},
+
+            // can also just use empty fragments
+            Fragment {}
+
+            // Fragments let you insert groups of nodes without a parent.
+            // This lets you make components that insert elements as siblings without a container.
+            div {"A"}
+            Fragment {
+                div {"B"}
+                div {"C"}
+                Fragment {
+                    "D"
+                    Fragment {
+                        "E"
+                        "F"
+                    }
+                }
+            }
+
+            // Components
+            // Can accept any paths
+            // Notice how you still get syntax highlighting and IDE support :)
+            Baller {}
+            baller::Baller {}
+            crate::baller::Baller {}
+
+            // Can take properties
+            Taller { a: "asd" }
+
+            // Can take optional properties
+            Taller { a: "asd" }
+
+            // Can pass in props directly as an expression
+            {
+                let props = TallerProps {a: "hello", children: None };
+                rsx!(Taller { ..props })
+            }
+
+            // Spreading can also be overridden manually
+            Taller {
+                ..TallerProps { a: "ballin!", children: None },
+                a: "not ballin!"
+            }
+
+            // Can take children too!
+            Taller { a: "asd", div {"hello world!"} }
+
+            // This component's props are defined *inline* with the `inline_props` macro
+            WithInline { text: "using functionc all syntax" }
+
+            // Components can be generic too
+            // This component takes i32 type to give you typed input
+            TypedInput::<i32> {}
+
+            // Type inference can be used too
+            TypedInput { initial: 10.0 }
+
+            // geneircs with the `inline_props` macro
+            Label { text: "hello geneirc world!" }
+            Label { text: 99.9 }
+
+            // Lowercase components work too, as long as they are access using a path
+            baller::lowercase_component {}
+
+            // For in-scope lowercase components, use the `self` keyword
+            self::lowercase_helper {}
+
+            // helper functions
+            // Anything that implements IntoVnode can be dropped directly into Rsx
+            {helper("hello world!")}
+
+            // Strings can be supplied directly
+            {String::from("Hello world!")}
+
+            // So can format_args
+            {format_args!("Hello {}!", "world")}
+
+            // Or we can shell out to a helper function
+            {format_dollars(10, 50)}
+        }
+    }
+}
+
+fn format_dollars(dollars: u32, cents: u32) -> String {
+    format!("${dollars}.{cents:02}")
+}
+
+fn helper(text: &str) -> Element {
+    rsx! {
+        p { "{text}" }
+    }
+}
+
+// no_case_check disables PascalCase checking if you *really* want a snake_case component.
+// This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
+// something like Clippy.
+#[component(no_case_check)]
+fn lowercase_helper() -> Element {
+    rsx! {
+        "asd"
+    }
+}
+
+mod baller {
+    use super::*;
+
+    #[component]
+    /// This component totally balls
+    pub fn Baller() -> Element {
+        rsx! { "ballin'" }
+    }
+
+    // no_case_check disables PascalCase checking if you *really* want a snake_case component.
+    // This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
+    // something like Clippy.
+    #[component(no_case_check)]
+    pub fn lowercase_component() -> Element {
+        rsx! { "look ma, no uppercase" }
+    }
+}
+
+/// Documention for this component is visible within the rsx macro
+#[component]
+pub fn Taller(
+    /// Fields are documented and accessible in rsx!
+    a: &'static str,
+    children: Element,
+) -> Element {
+    rsx! { {&children} }
+}
+
+#[derive(Props, Clone, PartialEq, Eq)]
+pub struct TypedInputProps<T: 'static + Clone + PartialEq> {
+    #[props(optional, default)]
+    initial: Option<T>,
+}
+
+#[allow(non_snake_case)]
+pub fn TypedInput<T>(props: TypedInputProps<T>) -> Element
+where
+    T: FromStr + fmt::Display + PartialEq + Clone + 'static,
+    <T as FromStr>::Err: std::fmt::Display,
+{
+    if let Some(props) = props.initial {
+        return rsx! { "{props}" };
+    }
+
+    None
+}
+
+#[component]
+fn WithInline(text: String) -> Element {
+    rsx! {
+        p { "{text}" }
+    }
+}
+
+#[component]
+fn Label<T: Clone + PartialEq + 'static>(text: T) -> Element
+where
+    T: Display,
+{
+    rsx! {
+        p { "{text}" }
+    }
+}

+ 7 - 0
examples/scroll_to_top.rs

@@ -1,3 +1,10 @@
+//! Scroll elements using their MountedData
+//!
+//! Dioxus exposes a few helpful APIs around elements (mimicking the DOM APIs) to allow you to interact with elements
+//! across the renderers. This includes scrolling, reading dimensions, and more.
+//!
+//! In this example we demonstrate how to scroll to the top of the page using the `scroll_to` method on the `MountedData`
+
 use dioxus::prelude::*;
 
 fn main() {

+ 7 - 0
examples/shortcut.rs

@@ -1,3 +1,10 @@
+//! Add global shortcuts to your app while a component is active
+//!
+//! This demo shows how to add a global shortcut to your app that toggles a signal. You could use this to implement
+//! a raycast-type app, or to add a global shortcut to your app that toggles a component on and off.
+//!
+//! These are *global* shortcuts, so they will work even if your app is not in focus.
+
 use dioxus::desktop::use_global_shortcut;
 use dioxus::prelude::*;
 

+ 2 - 0
examples/shorthand.rs

@@ -1,3 +1,5 @@
+//! Dioxus supports shorthand syntax for creating elements and components.
+
 use dioxus::prelude::*;
 
 fn main() {

+ 8 - 0
examples/signals.rs

@@ -1,3 +1,11 @@
+//! A simple example demonstrating how to use signals to modify state from several different places.
+//!
+//! This simlpe example implements a counter that can be incremented, decremented, and paused. It also demonstrates
+//! that background tasks in use_futures can modify the value as well.
+//!
+//! Most signals implement Into<ReadOnlySignal<T>>, making ReadOnlySignal a good default type when building new
+//! library components that don't need to modify their values.
+
 use dioxus::prelude::*;
 use std::time::Duration;
 

+ 4 - 0
examples/simple_list.rs

@@ -1,3 +1,7 @@
+//! A few ways of mapping elements into rsx! syntax
+//!
+//! Rsx allows anything that's an iterator where the output type implements Into<Element>, so you can use any of the following:
+
 use dioxus::prelude::*;
 
 fn main() {

+ 21 - 10
examples/simple_router.rs

@@ -1,20 +1,32 @@
-#![allow(non_snake_case)]
+//! A simple example of a router with a few routes and a nav bar.
 
 use dioxus::prelude::*;
 
+fn main() {
+    // Launch the router, using our `Route` component as the generic type
+    // This will automatically boot the app to "/" unless otherwise specified
+    launch(|| rsx! { Router::<Route> {} });
+}
+
+/// By default, the Routable derive will use the name of the variant as the route
+/// You can also specify a specific component by adding the Component name to the `#[route]` attribute
+#[rustfmt::skip]
 #[derive(Routable, Clone, PartialEq)]
 enum Route {
+    // Wrap the app in a Nav layout
     #[layout(Nav)]
-    #[route("/")]
-    Homepage {},
+        #[route("/")]
+        Homepage {},
 
-    #[route("/blog/:id")]
-    Blog { id: String },
+        #[route("/blog/:id")]
+        Blog { id: String },
 }
 
 #[component]
 fn Homepage() -> Element {
-    rsx! { h1 { "Welcome home" } }
+    rsx! {
+        h1 { "Welcome home" }
+    }
 }
 
 #[component]
@@ -25,6 +37,9 @@ fn Blog(id: String) -> Element {
     }
 }
 
+/// A simple nav bar that links to the homepage and blog pages
+///
+/// The `Route` enum gives up typesafe routes, allowing us to rename routes and serialize them automatically
 #[component]
 fn Nav() -> Element {
     rsx! {
@@ -52,7 +67,3 @@ fn Nav() -> Element {
         div { Outlet::<Route> {} }
     }
 }
-
-fn main() {
-    launch_desktop(|| rsx! { Router::<Route> {} });
-}

+ 5 - 0
examples/spread.rs

@@ -1,3 +1,8 @@
+//! This example demonstrates how to use the spread operator to pass attributes to child components.
+//!
+//! This lets components like the `Link` allow the user to extend the attributes of the underlying `a` tag.
+//! These attributes are bundled into a `Vec<Attribute>` which can be spread into the child component using the `..` operator.
+
 use dioxus::prelude::*;
 
 fn main() {

+ 3 - 0
examples/ssr.rs

@@ -1,6 +1,9 @@
 //! Example: SSR
 //!
 //! This example shows how we can render the Dioxus Virtualdom using SSR.
+//! Dioxus' SSR is quite comprehensive and can generate a number of utility markers for things like hydration.
+//!
+//! You can also render without any markers to get a clean HTML output.
 
 use dioxus::prelude::*;
 

+ 0 - 36
examples/stale_memo.rs

@@ -1,36 +0,0 @@
-use dioxus::prelude::*;
-
-fn main() {
-    launch_desktop(app);
-}
-
-fn app() -> Element {
-    let mut state = use_signal(|| 0);
-    let mut depth = use_signal(|| 1_usize);
-
-    if depth() == 5 {
-        return rsx! {
-            div { "Max depth reached" }
-            button { onclick: move |_| depth -= 1, "Remove depth" }
-        };
-    }
-
-    let items = use_memo(move || (0..depth()).map(|f| f as _).collect::<Vec<isize>>());
-
-    rsx! {
-        button { onclick: move |_| state += 1, "Increment" }
-        button { onclick: move |_| depth += 1, "Add depth" }
-        button {
-            onclick: move |_| async move {
-                depth += 1;
-                tokio::time::sleep(std::time::Duration::from_millis(100)).await;
-                dbg!(items.read());
-                // if depth() is 5, this will be the old since the memo hasn't been re-computed
-                // use_memos are only re-computed when the signals they capture change
-                // *and* they are used in the current render
-                // If the use_memo isn't used, it can't be re-computed!
-            },
-            "Add depth with sleep"
-        }
-    }
-}

+ 5 - 0
examples/streams.rs

@@ -1,3 +1,5 @@
+//! Handle async streams using use_future and awaiting the next value.
+
 use dioxus::prelude::*;
 use futures_util::{future, stream, Stream, StreamExt};
 use std::time::Duration;
@@ -10,8 +12,11 @@ fn app() -> Element {
     let mut count = use_signal(|| 10);
 
     use_future(move || async move {
+        // Create the stream.
+        // This could be a network request, a file read, or any other async operation.
         let mut stream = some_stream();
 
+        // Await the next value from the stream.
         while let Some(second) = stream.next().await {
             count.set(second);
         }

+ 1 - 2
examples/suspense.rs

@@ -1,5 +1,3 @@
-#![allow(non_snake_case)]
-
 //! Suspense in Dioxus
 //!
 //! Currently, `rsx!` does not accept futures as values. To achieve the functionality
@@ -49,6 +47,7 @@ fn app() -> Element {
 /// This component will re-render when the future has finished
 /// Suspense is achieved my moving the future into only the component that
 /// actually renders the data.
+#[component]
 fn Doggo() -> Element {
     let mut fut = use_resource(move || async move {
         #[derive(serde::Deserialize)]

+ 7 - 1
examples/svg.rs

@@ -1,4 +1,10 @@
-// Thanks to @japsu and their project https://github.com/japsu/jatsi for the example!
+//! Thanks to @japsu and their project https://github.com/japsu/jatsi for the example!
+//!
+//! This example shows how to create a simple dice rolling app using SVG and Dioxus.
+//! The `svg` element and its children have a custom namespace, and are attached using different methods than regular
+//! HTML elements. Any element can specify a custom namespace by using the `namespace` meta attribute.
+//!
+//! If you `go-to-definition` on the `svg` element, you'll see its custom namespace.
 
 use dioxus::prelude::*;
 use rand::{thread_rng, Rng};

+ 0 - 28
examples/tasks.rs

@@ -1,28 +0,0 @@
-//! Example: README.md showcase
-//!
-//! The example from the README.md.
-
-use dioxus::prelude::*;
-use std::time::Duration;
-
-fn main() {
-    launch_desktop(app);
-}
-
-fn app() -> Element {
-    let mut count = use_signal(|| 0);
-
-    use_future(move || async move {
-        loop {
-            tokio::time::sleep(Duration::from_millis(1000)).await;
-            count += 1;
-        }
-    });
-
-    rsx! {
-        div {
-            h1 { "Current count: {count}" }
-            button { onclick: move |_| count.set(0), "Reset the count" }
-        }
-    }
-}

+ 0 - 21
examples/textarea.rs

@@ -1,21 +0,0 @@
-// How to use textareas
-
-use dioxus::prelude::*;
-
-fn main() {
-    launch_desktop(app);
-}
-
-fn app() -> Element {
-    let mut model = use_signal(|| String::from("asd"));
-
-    rsx! {
-        textarea {
-            class: "border",
-            rows: "10",
-            cols: "80",
-            value: "{model}",
-            oninput: move |e| model.set(e.value().clone()),
-        }
-    }
-}

+ 45 - 23
examples/todomvc.rs

@@ -1,4 +1,5 @@
-#![allow(non_snake_case)]
+//! The typical TodoMVC app, implemented in Dioxus.
+
 use dioxus::prelude::*;
 use dioxus_elements::input_data::keyboard_types::Key;
 use std::collections::HashMap;
@@ -21,15 +22,21 @@ struct TodoItem {
     contents: String,
 }
 
-const STYLE: &str = include_str!("./assets/todomvc.css");
-
 fn app() -> Element {
+    // We store the todos in a HashMap in a Signal.
+    // Each key is the id of the todo, and the value is the todo itself.
     let mut todos = use_signal(HashMap::<u32, TodoItem>::new);
+
     let filter = use_signal(|| FilterState::All);
 
+    // We use a simple memoized signal to calculate the number of active todos.
+    // Whenever the todos change, the active_todo_count will be recalculated.
     let active_todo_count =
         use_memo(move || todos.read().values().filter(|item| !item.checked).count());
 
+    // We use a memoized signal to filter the todos based on the current filter state.
+    // Whenever the todos or filter change, the filtered_todos will be recalculated.
+    // Note that we're only storing the IDs of the todos, not the todos themselves.
     let filtered_todos = use_memo(move || {
         let mut filtered_todos = todos
             .read()
@@ -47,6 +54,8 @@ fn app() -> Element {
         filtered_todos
     });
 
+    // Toggle all the todos to the opposite of the current state.
+    // If all todos are checked, uncheck them all. If any are unchecked, check them all.
     let toggle_all = move |_| {
         let check = active_todo_count() != 0;
         for (_, item) in todos.write().iter_mut() {
@@ -55,8 +64,8 @@ fn app() -> Element {
     };
 
     rsx! {
+        style { {include_str!("./assets/todomvc.css")} }
         section { class: "todoapp",
-            style { {STYLE} }
             TodoHeader { todos }
             section { class: "main",
                 if !todos.read().is_empty() {
@@ -69,17 +78,29 @@ fn app() -> Element {
                     }
                     label { r#for: "toggle-all" }
                 }
+
+                // Render the todos using the filtered_todos signal
+                // We pass the ID into the TodoEntry component so it can access the todo from the todos signal.
+                // Since we store the todos in a signal too, we also need to send down the todo list
                 ul { class: "todo-list",
                     for id in filtered_todos() {
                         TodoEntry { key: "{id}", id, todos }
                     }
                 }
+
+                // We only show the footer if there are todos.
                 if !todos.read().is_empty() {
                     ListFooter { active_todo_count, todos, filter }
                 }
             }
         }
-        PageFooter {}
+
+        // A simple info footer
+        footer { class: "info",
+            p { "Double-click to edit a todo" }
+            p { "Created by " a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" } }
+            p { "Part of " a { href: "http://todomvc.com", "TodoMVC" } }
+        }
     }
 }
 
@@ -117,21 +138,34 @@ fn TodoHeader(mut todos: Signal<HashMap<u32, TodoItem>>) -> Element {
     }
 }
 
+/// A single todo entry
+/// This takes the ID of the todo and the todos signal as props
+/// We can use these together to memoize the todo contents and checked state
 #[component]
 fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
     let mut is_editing = use_signal(|| false);
+
+    // To avoid re-rendering this component when the todo list changes, we isolate our reads to memos
+    // This way, the component will only re-render when the contents of the todo change, or when the editing state changes.
+    // This does involve taking a local clone of the todo contents, but it allows us to prevent this component from re-rendering
     let checked = use_memo(move || todos.read().get(&id).unwrap().checked);
     let contents = use_memo(move || todos.read().get(&id).unwrap().contents.clone());
 
     rsx! {
-        li { class: if checked() { "completed" }, class: if is_editing() { "editing" },
+        li {
+            // Dioxus lets you use if statements in rsx to conditionally render attributes
+            // These will get merged into a single class attribute
+            class: if checked() { "completed" },
+            class: if is_editing() { "editing" },
+
+            // Some basic controls for the todo
             div { class: "view",
                 input {
                     class: "toggle",
                     r#type: "checkbox",
                     id: "cbg-{id}",
                     checked: "{checked}",
-                    oninput: move |evt| todos.write().get_mut(&id).unwrap().checked = evt.value().parse().unwrap(),
+                    oninput: move |evt| todos.write().get_mut(&id).unwrap().checked = evt.checked(),
                 }
                 label {
                     r#for: "cbg-{id}",
@@ -145,6 +179,8 @@ fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
                     prevent_default: "onclick"
                 }
             }
+
+            // Only render the actual input if we're editing
             if is_editing() {
                 input {
                     class: "edit",
@@ -170,6 +206,8 @@ fn ListFooter(
     active_todo_count: ReadOnlySignal<usize>,
     mut filter: Signal<FilterState>,
 ) -> Element {
+    // We use a memoized signal to calculate whether we should show the "Clear completed" button.
+    // This will recompute whenever the todos change, and if the value is true, the button will be shown.
     let show_clear_completed = use_memo(move || todos.read().values().any(|todo| todo.checked));
 
     rsx! {
@@ -211,19 +249,3 @@ fn ListFooter(
         }
     }
 }
-
-fn PageFooter() -> Element {
-    rsx! {
-        footer { class: "info",
-            p { "Double-click to edit a todo" }
-            p {
-                "Created by "
-                a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }
-            }
-            p {
-                "Part of "
-                a { href: "http://todomvc.com", "TodoMVC" }
-            }
-        }
-    }
-}

+ 27 - 18
examples/video_stream.rs

@@ -1,35 +1,26 @@
+//! Using `wry`'s http module, we can stream a video file from the local file system.
+//!
+//! You could load in any file type, but this example uses a video file.
+
 use dioxus::desktop::wry::http;
 use dioxus::desktop::wry::http::Response;
 use dioxus::desktop::{use_asset_handler, AssetRequest};
 use dioxus::prelude::*;
 use http::{header::*, response::Builder as ResponseBuilder, status::StatusCode};
 use std::{io::SeekFrom, path::PathBuf};
-use tokio::io::AsyncReadExt;
-use tokio::io::AsyncSeekExt;
-use tokio::io::AsyncWriteExt;
+use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
 
 const VIDEO_PATH: &str = "./examples/assets/test_video.mp4";
 
 fn main() {
-    let video_file = PathBuf::from(VIDEO_PATH);
-    if !video_file.exists() {
-        tokio::runtime::Runtime::new()
-            .unwrap()
-            .block_on(async move {
-                println!("Downloading video file...");
-                let video_url =
-                    "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
-                let mut response = reqwest::get(video_url).await.unwrap();
-                let mut file = tokio::fs::File::create(&video_file).await.unwrap();
-                while let Some(chunk) = response.chunk().await.unwrap() {
-                    file.write_all(&chunk).await.unwrap();
-                }
-            });
-    }
+    // For the sake of this example, we will download the video file if it doesn't exist
+    ensure_video_is_loaded();
+
     launch_desktop(app);
 }
 
 fn app() -> Element {
+    // Any request to /videos will be handled by this handler
     use_asset_handler("videos", move |request, responder| {
         // Using dioxus::spawn works, but is slower than a dedicated thread
         tokio::task::spawn(async move {
@@ -186,3 +177,21 @@ async fn get_stream_response(
 
     http_response.map_err(Into::into)
 }
+
+fn ensure_video_is_loaded() {
+    let video_file = PathBuf::from(VIDEO_PATH);
+    if !video_file.exists() {
+        tokio::runtime::Runtime::new()
+            .unwrap()
+            .block_on(async move {
+                println!("Downloading video file...");
+                let video_url =
+                    "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
+                let mut response = reqwest::get(video_url).await.unwrap();
+                let mut file = tokio::fs::File::create(&video_file).await.unwrap();
+                while let Some(chunk) = response.chunk().await.unwrap() {
+                    file.write_all(&chunk).await.unwrap();
+                }
+            });
+    }
+}

+ 20 - 1
examples/web_component.rs

@@ -1,3 +1,9 @@
+//! Dioxus allows webcomponents to be created with a simple syntax.
+//!
+//! Read more about webcomponents [here](https://developer.mozilla.org/en-US/docs/Web/Web_Components)
+//!
+//! We typically suggest wrapping webcomponents in a strongly typed interface using a component.
+
 use dioxus::prelude::*;
 
 fn main() {
@@ -6,8 +12,21 @@ fn main() {
 
 fn app() -> Element {
     rsx! {
+        div {
+            h1 { "Web Components" }
+            CoolWebComponet { my_prop: "Hello, world!".to_string() }
+        }
+    }
+}
+
+/// A web-component wrapped with a strongly typed interface using a component
+#[component]
+fn CoolWebComponet(my_prop: String) -> Element {
+    rsx! {
+        // rsx! takes a webcomponent as long as its tag name is separated with dashes
         web-component {
-            "my-prop": "5%",
+            // Since web-components don't have built-in attributes, the attribute names must be passed as a string
+            "my-prop": my_prop,
         }
     }
 }

+ 86 - 44
examples/window_event.rs

@@ -1,3 +1,14 @@
+//! This example demonstrates how to handle window events and change window properties.
+//!
+//! We're able to do things like:
+//! - implement window dragging
+//! - toggle fullscreen
+//! - toggle always on top
+//! - toggle window decorations
+//! - change the window title
+//!
+//! The entire featuresuite of wry and tao is available to you
+
 use dioxus::desktop::{window, Config, WindowBuilder};
 use dioxus::prelude::*;
 
@@ -14,29 +25,40 @@ fn main() {
 }
 
 fn app() -> Element {
-    let mut fullscreen = use_signal(|| false);
-    let mut always_on_top = use_signal(|| false);
-    let mut decorations = use_signal(|| false);
-
     rsx!(
-        link {
-            href: "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css",
-            rel: "stylesheet"
+        link { href: "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel: "stylesheet" }
+        Header {}
+        div { class: "container mx-auto",
+            div { class: "grid grid-cols-5",
+                SetOnTop {}
+                SetDecorations {}
+                SetTitle {}
+            }
         }
-        header {
-            class: "text-gray-400 bg-gray-900 body-font",
-            onmousedown: move |_| window().drag(),
+    )
+}
+
+#[component]
+fn Header() -> Element {
+    let mut fullscreen = use_signal(|| false);
+
+    rsx! {
+        header { class: "text-gray-400 bg-gray-900 body-font", onmousedown: move |_| window().drag(),
             div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
                 a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
                     span { class: "ml-3 text-xl", "Dioxus" }
                 }
                 nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" }
+
+                // Set the window to minimized
                 button {
                     class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
                     onmousedown: |evt| evt.stop_propagation(),
                     onclick: move |_| window().set_minimized(true),
                     "Minimize"
                 }
+
+                // Toggle fullscreen
                 button {
                     class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
                     onmousedown: |evt| evt.stop_propagation(),
@@ -47,6 +69,9 @@ fn app() -> Element {
                     },
                     "Fullscreen"
                 }
+
+                // Close the window
+                // If the window is the last window open, the app will close, if you configured the close behavior to do so
                 button {
                     class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
                     onmousedown: |evt| evt.stop_propagation(),
@@ -55,40 +80,57 @@ fn app() -> Element {
                 }
             }
         }
-        br {}
-        div { class: "container mx-auto",
-            div { class: "grid grid-cols-5",
-                div {
-                    button {
-                        class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
-                        onmousedown: |evt| evt.stop_propagation(),
-                        onclick: move |_| {
-                            window().set_always_on_top(!always_on_top());
-                            always_on_top.toggle();
-                        },
-                        "Always On Top"
-                    }
-                }
-                div {
-                    button {
-                        class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
-                        onmousedown: |evt| evt.stop_propagation(),
-                        onclick: move |_| {
-                            window().set_decorations(!decorations());
-                            decorations.toggle();
-                        },
-                        "Set Decorations"
-                    }
-                }
-                div {
-                    button {
-                        class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
-                        onmousedown: |evt| evt.stop_propagation(),
-                        onclick: move |_| window().set_title("Dioxus Application"),
-                        "Change Title"
-                    }
-                }
+    }
+}
+
+#[component]
+fn SetOnTop() -> Element {
+    let mut always_on_top = use_signal(|| false);
+
+    rsx! {
+        div {
+            button {
+                class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
+                onmousedown: |evt| evt.stop_propagation(),
+                onclick: move |_| {
+                    window().set_always_on_top(!always_on_top());
+                    always_on_top.toggle();
+                },
+                "Always On Top"
             }
         }
-    )
+    }
+}
+
+#[component]
+fn SetDecorations() -> Element {
+    let mut decorations = use_signal(|| false);
+
+    rsx! {
+        div {
+            button {
+                class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
+                onmousedown: |evt| evt.stop_propagation(),
+                onclick: move |_| {
+                    window().set_decorations(!decorations());
+                    decorations.toggle();
+                },
+                "Set Decorations"
+            }
+        }
+    }
+}
+
+#[component]
+fn SetTitle() -> Element {
+    rsx! {
+        div {
+            button {
+                class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
+                onmousedown: |evt| evt.stop_propagation(),
+                onclick: move |_| window().set_title("Dioxus Application"),
+                "Change Title"
+            }
+        }
+    }
 }

+ 7 - 0
examples/window_focus.rs

@@ -1,3 +1,10 @@
+//! Listen for window focus events using a wry event handler
+//!
+//! This example shows how to use the use_wry_event_handler hook to listen for window focus events.
+//! We can intercept any Wry event, but in this case we're only interested in the WindowEvent::Focused event.
+//!
+//! This lets you do things like backgrounding tasks, pausing animations, or changing the UI when the window is focused or not.
+
 use dioxus::desktop::tao::event::Event as WryEvent;
 use dioxus::desktop::tao::event::WindowEvent;
 use dioxus::desktop::use_wry_event_handler;

+ 6 - 0
examples/window_zoom.rs

@@ -1,3 +1,7 @@
+//! Adjust the zoom of a desktop app
+//!
+//! This example shows how to adjust the zoom of a desktop app using the webview.zoom method.
+
 use dioxus::prelude::*;
 
 fn main() {
@@ -8,6 +12,8 @@ fn app() -> Element {
     let mut level = use_signal(|| 1.0);
 
     rsx! {
+        h1 { "Zoom level: {level}" }
+        p { "Change the zoom level of the webview by typing a number in the input below."}
         input {
             r#type: "number",
             value: "{level}",

+ 9 - 2
packages/desktop/src/webview.rs

@@ -37,11 +37,16 @@ impl WebviewInstance {
         dom: VirtualDom,
         shared: Rc<SharedContext>,
     ) -> WebviewInstance {
-        let window = cfg.window.clone().build(&shared.target).unwrap();
+        let mut window = cfg.window.clone();
+
+        // tao makes small windows for some reason, make them bigger
+        if cfg.window.window.inner_size.is_none() {
+            window = window.with_inner_size(tao::dpi::LogicalSize::new(800.0, 600.0));
+        }
 
         // We assume that if the icon is None in cfg, then the user just didnt set it
         if cfg.window.window.window_icon.is_none() {
-            window.set_window_icon(Some(
+            window = window.with_window_icon(Some(
                 tao::window::Icon::from_rgba(
                     include_bytes!("./assets/default_icon.bin").to_vec(),
                     460,
@@ -51,6 +56,8 @@ impl WebviewInstance {
             ));
         }
 
+        let window = window.build(&shared.target).unwrap();
+
         let mut web_context = WebContext::new(cfg.data_dir.clone());
         let edit_queue = EditQueue::default();
         let asset_handlers = AssetHandlerRegistry::new(dom.runtime());

+ 8 - 0
packages/html/src/events/form.rs

@@ -90,6 +90,14 @@ impl FormData {
         self.inner.value()
     }
 
+    /// Get the value of the form event as a parsed type
+    pub fn parsed<T>(&self) -> Result<T, T::Err>
+    where
+        T: std::str::FromStr,
+    {
+        self.value().parse()
+    }
+
     /// Try to parse the value as a boolean
     ///
     /// Returns false if the value is not a boolean, or if it is false!

+ 1 - 1
packages/mobile/Cargo.toml

@@ -10,7 +10,7 @@ keywords = ["dom", "ui", "gui", "react"]
 license = "MIT OR Apache-2.0"
 
 [dependencies]
-dioxus-desktop = { workspace = true, default-features = false, features = ["tokio_runtime"] }
+dioxus-desktop = { workspace = true, features = ["tokio_runtime"] }
 
 [lib]
 doctest = false

+ 11 - 0
packages/router/src/components/link.rs

@@ -99,6 +99,10 @@ pub struct LinkProps {
     /// The onclick event handler.
     pub onclick: Option<EventHandler<MouseEvent>>,
 
+    /// The onmounted event handler.
+    /// Fired when the <a> element is mounted.
+    pub onmounted: Option<EventHandler<MountedEvent>>,
+
     #[props(default)]
     /// Whether the default behavior should be executed if an `onclick` handler is provided.
     ///
@@ -269,10 +273,17 @@ pub fn Link(props: LinkProps) -> Element {
         }
     };
 
+    let onmounted = move |event| {
+        if let Some(handler) = props.onmounted.clone() {
+            handler.call(event);
+        }
+    };
+
     rsx! {
         a {
             onclick: action,
             href,
+            onmounted: onmounted,
             prevent_default,
             class,
             rel,