dioxus/examples/rsx_usage.rs
Leonard c866ae602b
Add system for creating component attributes + new #[component] attribute (#1448)
* Add `#[component]` attribute + system for creating component attributes + other stuff

* Delete inlineprops.rs

* Update inline_props.rs

* Cargo fmt

* Fix clippy warnings and paths in props/mods.rs

* Include where clause in `#[inline_props]` output

* Allow Clippy type complexity in `LinkProps`

* Allow the type complexity lint for the entire link.rs file

* Remove snake_case -> PascalCase converter, but rather enforce PascalCase

Also:
- Put the second function inside the main one instead of besides it.
- Simplify

* Simplify type check lints so they don't return false positives

They will not always work, but they won't return any false positives, like for aliases. This is likely going to be replaced by a more polished Clippy-backed linting system.

* Fix #583

* Cargo fmt

* Add docs for `deserialize()` and remove useless comment

* Add `#[component]` to prelude

* Merge branch 'master' of https://github.com/tigerros/dioxus

* #[inline_props] is no more. Except in the docs folder, but that's going to be removed

* Remove docs folder

* Remove docs from workspace

* Resolve `DeserializerOutput` conversation
2023-09-15 09:13:36 -05:00

301 lines
9 KiB
Rust

//! A tour of the rsx! macro
//! ------------------------
//!
//! This example serves as an informal quick reference of all the things that the rsx! macro can do.
//!
//! A full in-depth reference guide is available at: https://www.notion.so/rsx-macro-basics-ef6e367dec124f4784e736d91b0d0b19
//!
//! ### Elements
//! - Create any element from its tag
//! - Accept compile-safe attributes for each tag
//! - Display documentation for elements
//! - Arguments instead of String
//! - Text
//! - Inline Styles
//!
//! ## General Concepts
//! - Iterators
//! - Keys
//! - Match statements
//! - Conditional Rendering
//!
//! ### Events
//! - Handle events with the "onXYZ" syntax
//! - Closures can capture their environment with the 'a lifetime
//!
//!
//! ### Components
//! - Components can be made by specifying the name
//! - Components can be referenced by path
//! - Components may have optional parameters
//! - Components may have their properties specified by spread syntax
//! - Components may accept child nodes
//! - Components that accept "onXYZ" get those closures bump allocated
//!
//! ### Fragments
//! - Allow fragments using the built-in `Fragment` component
//! - Accept a list of vnodes as children for a Fragment component
//! - Allow keyed fragments in iterators
//! - Allow top-level fragments
fn main() {
dioxus_desktop::launch(App);
}
use core::{fmt, str::FromStr};
use std::fmt::Display;
use baller::Baller;
use dioxus::prelude::*;
#[component]
fn App(cx: Scope) -> Element {
let formatting = "formatting!";
let formatting_tuple = ("a", "b");
let lazy_fmt = format_args!("lazily formatted text");
cx.render(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"),
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 {
rsx!(h1 {"Top text"})
} else {
rsx!(h1 {"Bottom text"})
}
// Using optionals for diverging branches
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: cx.render(rsx!(()))};
rsx!(Taller { ..props })
}
// Spreading can also be overridden manually
Taller {
..TallerProps { a: "ballin!", children: cx.render(rsx!(()) )},
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(cx, "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<'a> {
cx.render(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(cx: Scope) -> Element {
cx.render(rsx! {
"asd"
})
}
mod baller {
use super::*;
#[derive(Props, PartialEq, Eq)]
pub struct BallerProps {}
#[component]
/// This component totally balls
pub fn Baller(_cx: Scope<BallerProps>) -> 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(cx: Scope) -> Element {
cx.render(rsx! { "look ma, no uppercase" })
}
}
#[derive(Props)]
pub struct TallerProps<'a> {
/// Fields are documented and accessible in rsx!
a: &'static str,
children: Element<'a>,
}
/// Documention for this component is visible within the rsx macro
#[component]
pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element {
cx.render(rsx! {
&cx.props.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<'a>(cx: Scope<'a>, text: &'a str) -> Element {
cx.render(rsx! {
p { "{text}" }
})
}
#[component]
fn Label<T>(cx: Scope, text: T) -> Element
where
T: Display,
{
cx.render(rsx! {
p { "{text}" }
})
}