diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict index efbfd1aa3..476f28f10 100644 --- a/.vscode/spellright.dict +++ b/.vscode/spellright.dict @@ -46,3 +46,5 @@ impl destructured linting lodash +crates.io +utilties diff --git a/docs/assets/01-setup-helloworld.png b/docs/assets/01-setup-helloworld.png new file mode 100644 index 000000000..9da58b932 Binary files /dev/null and b/docs/assets/01-setup-helloworld.png differ diff --git a/docs/main-concepts/00-index.md b/docs/main-concepts/00-index.md new file mode 100644 index 000000000..b4d938cae --- /dev/null +++ b/docs/main-concepts/00-index.md @@ -0,0 +1,3 @@ +# Getting Started + +This page is an overview of the Dioxus documentation. diff --git a/docs/main-concepts/01-setup.md b/docs/main-concepts/01-setup.md new file mode 100644 index 000000000..861d5a73f --- /dev/null +++ b/docs/main-concepts/01-setup.md @@ -0,0 +1,57 @@ +# Overview + +Dioxus aims to provide a fast, friendly, and portable toolkit for building user interfaces with Rust. + +This guide assumes you'll be building a SPA (single page application) for the web. The process for building desktop apps, server-rendered apps, static sites, and mobile apps, is more-or-less the same. You can check out the [Platform Specific Guides](../platforms/00-index.md) for more information on setting up Dioxus for any of the various targets you are building for. + +# Setting up Dioxus + +Dioxus requires a few main things to get up and running: + +- The [Rust compiler](https://www.rust-lang.org) and associated build tooling + +- An editor of your choice, with the [Rust-Analyzer LSP plugin](https://rust-analyzer.github.io) + +Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide appropriate syntax highlighting, code navigation, folding, and more. + +We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. It'll be our one-stop-shop for anything related to building and sharing our Dioxus Apps. To install the CLI, you'll need cargo (should be automatically installed with Rust): + +``` +$ cargo install dioxus-cli +``` + +You can update the dioxus-cli at any time with: + +``` +$ cargo install --force dioxus-cli +``` + +If your version of the CLI is out of date, it'll remind you to update whenever a new version is uploaded to Rust's package manager [crates.io](https://crates.io). We use a dedicated 1st-party CLI to save you from having to run potentially untrusted code every time you add a crate to your project - as is standard in the NPM ecosystem. You can vet the source of the Dioxus-CLI yourself at its [GitHub repo](https://github.com/jkelleyrtp/dioxus/tree/master/packages/cli). + +If you want to keep your traditional `npm install XXX` workflow for adding packages, you might want to install `cargo-edit` and a few other fun `cargo` extensions: + +- [cargo edit](https://github.com/killercup/cargo-edit) for adding dependencies from the CLI +- [cargo-expand](https://github.com/dtolnay/cargo-expand) for expanding macro calls +- `cargo tree` - an integrated cargo command that lets you inspect your dependency tree + +That's it! We won't need to touch NPM/WebPack/Babel/Parcel, etc. + +# Important tools + +With Rust, things like benchmarking, testing, and documentation are included in the language. We strongly recommend going through the official Rust book _completely_. However, our hope is that a Dioxus app can serve as a great first project. With Dioxus you'll learn about: + +- Error handling +- Structs, Functions, Enums +- Closures +- Macros + +We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need knowledge on async, lifetimes, and smart pointers until you really start building complex Dioxus apps. + +We strongly encourage exploring the guides for more information on how to work with the integrated tooling: + +- [Testing](Testing.md) +- [Documentation](Documentation.md) +- [Benchmarking](Benchmarking.md) +- [Building](Building.md) +- [Modules](Modules.md) +- [Crates](Crates.md) diff --git a/docs/main-concepts/02-hello.md b/docs/main-concepts/02-hello.md new file mode 100644 index 000000000..a2e9deed5 --- /dev/null +++ b/docs/main-concepts/02-hello.md @@ -0,0 +1,142 @@ +# Hello World + +Let's put together a simple "hello world" to get acquainted with Dioxus. The Dioxus-CLI has an equivalent to "create-react-app" built-in, but setting up Dioxus apps is simple enough to not need additional tooling. + +First, let's start a new project. Rust has the concept of executables and libraries. Executables have a `main.rs` and libraries have `lib.rs`. A project may have both. Our `hello world` will be an executable - we expect our app to launch when we run it! Cargo provides this for us: + +```shell +$ cargo new --bin hello-dioxus +``` + +Now, we can `cd` into our project and poke around: + +```shell +$ cd hello-dioxus +$ tree +. +├── Cargo.toml +├── .git +├── .gitignore +└── src + └── main.rs +``` + +We are greeted with a pre-initialized git repository, our code folder (`src`) and our project file (`Cargo.toml`). + +Our `src` folder holds our code. Our `main.rs` file holds our `fn main` which will be executed when our app is ran. + +```shell +$ more src/main.rs +``` + +```rust +fn main() { + println!("Hello, world!"); +} +``` + +Right now, whenever our app is launched, "Hello world" will be echoed to the terminal. + +```shell +$ cargo run + Compiling hello-dioxus v0.1.0 + Finished dev [unoptimized + debuginfo] target(s) in 0.41s + Running `target/debug/hello-dioxus` +Hello, world! +``` + +Our `Cargo.toml` file holds our dependencies and project flags. + +```shell +$ cat Cargo.toml +``` + +```toml +[package] +name = "hello-dioxus" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +``` + +To use the Dioxus library, we'll want to add the most recent version of `Dioxus` to our crate. If you have `cargo edit` installed, simply call: + +```shell +$ cargo add dioxus --features web +``` + +It's very important to add `dioxus` with the `web` feature for this example. The `dioxus` crate is a batteries-include crate that combines a bunch of utility crates together, ensuring compatibility of the most important parts of the ecosystem. Under the hood, the `dioxus` crate configures various renderers, hooks, debug tooling, and more. The `web` feature ensures the we only depend on the smallest set of required crates to compile. + +If you plan to develop extensions for the `Dioxus` ecosystem, please use `dioxus-core` crate. The `dioxus` crate re-exports `dioxus-core` alongside other utilties. As a result, `dioxus-core` is much more stable than the batteries-included `dioxus` crate. + +Now, let's edit our `main.rs` file: + +```rust +use diouxs::prelude::*; + +fn main() { + dioxus::web::start(App) +} + +fn App(ctx: Context<()>) -> VNode { + ctx.render(rsx! { + div { "Hello, world!" } + }) +} +``` + +Let's dissect our example a bit. + +This bit of code imports everything from the the `prelude` module. This brings into scope the right traits, types, and macros needed for working with Dioxus. + +```rust +use diouxs::prelude::*; +``` + +This bit of code starts the WASM renderer as a future (JS Promise) and then awaits it. This is very similar to the `ReactDOM.render()` method you use for a React app. We pass in the `App` function as a our app. + +```rust +fn main() { + dioxus::web::start(App) +} +``` + +Finally, our app. Every component in Dioxus is a function that takes in a `Context` object and returns a `VNode`. + +```rust +fn App(ctx: Context<()>) -> VNode { + ctx.render(rsx! { + div { "Hello, world!" } + }) +}; +``` + +In React, you'll save data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. In Dioxus, you are given an explicit `Context` object to control how the component renders and stores data. + +Next, we're greeted with the `rsx!` macro. This lets us add a custom DSL for declaratively building the structure of our app. The semantics of this macro are similar to that of JSX and HTML, though with a familiar Rust-y interface. The `html!` macro is also available for writing components with a JSX/HTML syntax. + +The `rsx!` macro is lazy: it does not immediately produce elements or allocates, but rather builds a closure which can be rendered with `ctx.render`. + +Now, let's launch our app in a development server: + +```shell +$ dioxus develop +``` + +Huzzah! We have a simple app. + +![Hello world](../assets/01-setup-helloworld.png) + +If we wanted to golf a bit, we can shrink our hello-world even smaller: + +```rust +fn main() { + dioxus::web::start(|ctx| ctx.render(diouxs::rsx!( div { "Hello, World!" } )) +} +``` + +Anyways, let's move on to something a bit more complex. diff --git a/docs/main-concepts/03-rsx.md b/docs/main-concepts/03-rsx.md new file mode 100644 index 000000000..c82e0802e --- /dev/null +++ b/docs/main-concepts/03-rsx.md @@ -0,0 +1,156 @@ +# VNode Macros + +Dioxus comes preloaded with two macros for creating VNodes. + +## html! macro + +The html! macro supports a limited subset of the html standard. This macro will happily accept a copy-paste from something like tailwind builder. However, writing HTML by hand is a bit tedious - IDE tools for Rust don't support linting/autocomplete/syntax highlighting. RSX is much more natural for Rust programs and _does_ integrate well with Rust IDE tools. + +There is also limited support for dynamic handlers, but it will function similarly to JSX. + +You'll want to write RSX where you can, and in a future release we'll have a tool that automatically converts HTML to RSX. + +```rust +#[fc] +fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> VNode { + ctx.render(html! { +
+

"Hello, {name}!"

+

"Status: {pending}!"

+

"Count {count}!"

+
+ }) +} +``` + +## rsx! macro + +The rsx! macro is a VNode builder macro designed especially for Rust programs. Writing these should feel very natural, much like assembling a struct. VSCode also supports these with code folding, bracket-tabbing, bracket highlighting, and section selecting. + +The Dioxus VSCode extension will eventually provide a macro to convert a selection of html! template and turn it into rsx!, so you'll never need to transcribe templates by hand. + +It's also a bit easier on the eyes 🙂. + +```rust +#[fc] +fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> VNode { + ctx.render(rsx! { + div { + p {"Hello, {name}!"} + p {"Status: {pending}!"} + p {"Count {count}!"} + } + }) +} + +``` + +Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work: + +- `name: value` sets the property on this element. +- `"text"` adds a new text element +- `tag {}` adds a new child element +- `CustomTag {}` adds a new child component +- `{expr}` pastes the `expr` tokens literally. They must be IntoCtx to work properly + +Commas are entirely optional, but might be useful to delineate between elements and attributes. + +The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. + +```rust +static Example: FC<()> = |ctx| { + + let text = "example"; + + let g = async { + wait(10).await; + "hello" + }; + + let user_name = use_read_async(ctx, USERNAME); + let title = ctx.suspend(user_name, |user_name| rsx!{ h1 { "Welcome back, {user_name}" } }); + + + ctx.render(rsx!{ + div { + h1 { "Example" }, + + {title} + + // fstring interpolation + "{text}" + + p { + // Attributes + tag: "type", + + // Anything that implements display can be an attribute + abc: 123, + enabled: true, + + // attributes also supports interpolation + // `class` is not a restricted keyword unlike JS and ClassName + class: "big small wide short {text}", + + // bool-based classnames + classes: [("big", true), ("small", false)] + + // Bool-based props + // *must* be in the tuple form, cannot enter as a variable + tag: ("type", false) + + tag: {"these tokens are placed directly"} + + // Children + a { "abcder" }, + + // Children with attributes + h2 { "hello", class: "abc-123" }, + + // Child components + CustomComponent { a: 123, b: 456, key: "1" }, + + // Child components with paths + crate::components::CustomComponent { a: 123, b: 456, key: "1" }, + + // Iterators + { 0..3.map(|i| rsx!( h1 {"{:i}"} )) }, + + // More rsx!, or even html! + { rsx! { div { } } }, + { html! {
} }, + + // Matching + // Requires rendering the nodes first. + // rsx! is lazy, and the underlying closures cannot have the same type + // Rendering produces the VNode type + {match rand::gen_range::(1..3) { + 1 => rsx!(in ctx, h1 { "big" }) + 2 => rsx!(in ctx, h2 { "medium" }) + _ => rsx!(in ctx, h3 { "small" }) + }} + + // Optionals + {true.and_then(|f| rsx!{ h1 {"Conditional Rendering"} })} + + // Bool options + {(rsx!{ h1 {"Conditional Rendering"}, true)} + + // Child nodes + // Returns &[VNode] + {ctx.children()} + + // Duplicating nodes + // Clones the nodes by reference, so they are literally identical + {{ + let node = rsx!(in ctx, h1{ "TopNode" }); + (0..10).map(|_| node.clone()) + }} + + // Any expression that is `IntoVNode` + {expr} + } + } + }) +} +``` diff --git a/docs/main-concepts/testing.md b/docs/main-concepts/testing.md new file mode 100644 index 000000000..42f9af1d8 --- /dev/null +++ b/docs/main-concepts/testing.md @@ -0,0 +1,29 @@ +## Testing + +To test your Rust code, you can annotate any function with the `#[test]` block. In VSCode with RA, this will provide a lens to click and run the test. + +```rust +#[test] +fn component_runs() { + assert!(true) +} +``` + +This will test your Rust code _without_ going through the browser. This is ideal for squashing logic bugs and ensuring components render appropriately when the browsers's DOM is not needed. If you need to run tests in the browser, you can annotate your blocks with the `#[dioxus::test]` block. + +```rust +#[dioxus::test] +fn runs_in_browser() { + // ... +} +``` + +Then, when you run + +``` +$ dioxus test --chrome +``` + +Dioxus will build and test your code using the Chrome browser as a harness. + +There's a lot more to testing if you dive in, so check out the [Testing]() guide for more information diff --git a/docs/guides/00-index.md b/docs/platforms/00-index.md similarity index 98% rename from docs/guides/00-index.md rename to docs/platforms/00-index.md index 22548e951..551aa3523 100644 --- a/docs/guides/00-index.md +++ b/docs/platforms/00-index.md @@ -1,4 +1,7 @@ +# Welcome to Dioxus! + ## Running Examples + We use the dedicated `dioxus-cli` to build and test dioxus web-apps. This can run examples, tests, build web workers, launch development servers, bundle, and more. It's general purpose, but currently very tailored to Dioxus for liveview and bundling. If you've not used it before, `cargo install --path pacakages/dioxus-cli` will get it installed. This CLI tool should feel like using `cargo` but with 1st party support for assets, bundling, and other important dioxus-specific features. Alternatively, `trunk` works but can't run examples. diff --git a/docs/guides/01-ssr.md b/docs/platforms/01-ssr.md similarity index 100% rename from docs/guides/01-ssr.md rename to docs/platforms/01-ssr.md diff --git a/docs/guides/02-wasm.md b/docs/platforms/02-wasm.md similarity index 100% rename from docs/guides/02-wasm.md rename to docs/platforms/02-wasm.md diff --git a/docs/guides/03-desktop.md b/docs/platforms/03-desktop.md similarity index 100% rename from docs/guides/03-desktop.md rename to docs/platforms/03-desktop.md diff --git a/docs/guides/04-concurrency.md b/docs/platforms/04-concurrency.md similarity index 100% rename from docs/guides/04-concurrency.md rename to docs/platforms/04-concurrency.md diff --git a/docs/guides/05-liveview.md b/docs/platforms/05-liveview.md similarity index 100% rename from docs/guides/05-liveview.md rename to docs/platforms/05-liveview.md diff --git a/docs/guides/06-components.md b/docs/platforms/06-components.md similarity index 100% rename from docs/guides/06-components.md rename to docs/platforms/06-components.md diff --git a/docs/rocketsrchrome_files/cloud-0.png b/docs/rocketsrchrome_files/cloud-0.png deleted file mode 100644 index 9a3377af2..000000000 Binary files a/docs/rocketsrchrome_files/cloud-0.png and /dev/null differ diff --git a/docs/rocketsrchrome_files/cloud-1.png b/docs/rocketsrchrome_files/cloud-1.png deleted file mode 100644 index efe778e38..000000000 Binary files a/docs/rocketsrchrome_files/cloud-1.png and /dev/null differ diff --git a/docs/rocketsrchrome_files/cloud-2.png b/docs/rocketsrchrome_files/cloud-2.png deleted file mode 100644 index 2a7eef37b..000000000 Binary files a/docs/rocketsrchrome_files/cloud-2.png and /dev/null differ diff --git a/docs/rocketsrchrome_files/config-icon.svg b/docs/rocketsrchrome_files/config-icon.svg deleted file mode 100644 index 845f5932a..000000000 --- a/docs/rocketsrchrome_files/config-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/cookies-icon.svg b/docs/rocketsrchrome_files/cookies-icon.svg deleted file mode 100644 index 0ba4c3593..000000000 --- a/docs/rocketsrchrome_files/cookies-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/css b/docs/rocketsrchrome_files/css deleted file mode 100644 index a0fdfcc25..000000000 --- a/docs/rocketsrchrome_files/css +++ /dev/null @@ -1,32 +0,0 @@ -/* latin-ext */ -@font-face { - font-family: 'Proza Libre'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/prozalibre/v5/LYjGdGHgj0k1DIQRyUEyyEomdNr1Wpyyzzc.woff2) format('woff2'); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Proza Libre'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/prozalibre/v5/LYjGdGHgj0k1DIQRyUEyyEoodNr1Wpyy.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -/* latin-ext */ -@font-face { - font-family: 'Proza Libre'; - font-style: normal; - font-weight: 500; - src: url(https://fonts.gstatic.com/s/prozalibre/v5/LYjbdGHgj0k1DIQRyUEyyELbV8_WeJGY9T6Mh1k.woff2) format('woff2'); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Proza Libre'; - font-style: normal; - font-weight: 500; - src: url(https://fonts.gstatic.com/s/prozalibre/v5/LYjbdGHgj0k1DIQRyUEyyELbV8_YeJGY9T6M.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} diff --git a/docs/rocketsrchrome_files/helmet.svg b/docs/rocketsrchrome_files/helmet.svg deleted file mode 100644 index 480458326..000000000 --- a/docs/rocketsrchrome_files/helmet.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/logo-small.svg b/docs/rocketsrchrome_files/logo-small.svg deleted file mode 100644 index b2e134820..000000000 --- a/docs/rocketsrchrome_files/logo-small.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/robot-free.svg b/docs/rocketsrchrome_files/robot-free.svg deleted file mode 100644 index 275e2e5dd..000000000 --- a/docs/rocketsrchrome_files/robot-free.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/ship-icon.svg b/docs/rocketsrchrome_files/ship-icon.svg deleted file mode 100644 index 581322d8c..000000000 --- a/docs/rocketsrchrome_files/ship-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/streams-icon.svg b/docs/rocketsrchrome_files/streams-icon.svg deleted file mode 100644 index c65454527..000000000 --- a/docs/rocketsrchrome_files/streams-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/style.css b/docs/rocketsrchrome_files/style.css deleted file mode 100644 index 4415ba380..000000000 --- a/docs/rocketsrchrome_files/style.css +++ /dev/null @@ -1,2033 +0,0 @@ -@keyframes octocat-wave { - 0%, - to { - transform: rotate(0); - } - 20%, - 60% { - transform: rotate(-25deg); - } - 40%, - 80% { - transform: rotate(10deg); - } -} -html { - line-height: 1.15; - -ms-text-size-adjust: 100%; - -webkit-text-size-adjust: 100%; -} -.features .col.tight .button, -.features .col.tight button, -body { - margin: 0; -} -article, -aside, -details, -figcaption, -figure, -footer, -header, -header nav.version-select:hover options, -main, -menu, -nav, -section { - display: block; -} -h1 { - margin: 0.67em 0; - font-size: 3em; -} -figure { - margin: 1em 40px; -} -hr { - box-sizing: content-box; - overflow: visible; - border: 0; - height: 1px; - background-color: rgba(255, 255, 255, 0.25); -} -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} -a { - background-color: transparent; - -webkit-text-decoration-skip: objects; - color: #fff; - text-decoration: none; -} -a:active, -a:hover { - outline-width: 0; -} -abbr[title] { - border-bottom: none; - text-decoration: underline dotted; -} -b, -strong { - font-weight: bolder; -} -dfn { - font-style: italic; -} -mark { - background-color: #ff0; - color: #000; -} -small { - font-size: 80%; -} -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} -sub { - bottom: -0.25em; -} -sup { - top: -0.5em; -} -audio, -canvas, -video { - display: inline-block; -} -audio:not([controls]) { - display: none; - height: 0; -} -img { - border-style: none; -} -svg:not(:root) { - overflow: hidden; -} -button { - line-height: 1.15; -} -input, -optgroup, -select, -textarea { - font-family: sans-serif; - line-height: 1.15; - margin: 0; -} -optgroup { - font-size: 100%; -} -button, -input { - overflow: visible; -} -button, -select { - text-transform: none; -} -[type="reset"], -[type="submit"], -button, -html [type="button"] { - -webkit-appearance: button; -} -[type="button"]::-moz-focus-inner, -[type="reset"]::-moz-focus-inner, -[type="submit"]::-moz-focus-inner, -button::-moz-focus-inner { - border-style: none; - padding: 0; -} -[type="button"]:-moz-focusring, -[type="reset"]:-moz-focusring, -[type="submit"]:-moz-focusring, -button:-moz-focusring { - outline: 1px dotted ButtonText; -} -fieldset { - border: 1px solid silver; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} -legend { - color: inherit; - display: table; - max-width: 100%; - white-space: normal; -} -progress { - display: inline-block; - vertical-align: baseline; -} -textarea { - overflow: auto; -} -[type="checkbox"], -[type="radio"], -legend { - box-sizing: border-box; - padding: 0; -} -[type="number"]::-webkit-inner-spin-button, -[type="number"]::-webkit-outer-spin-button { - height: auto; -} -[type="search"] { - -webkit-appearance: textfield; - outline-offset: -2px; -} -[type="search"]::-webkit-search-cancel-button, -[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} -::-webkit-file-upload-button { - -webkit-appearance: button; - font: inherit; -} -summary { - display: list-item; -} -[hidden], -template { - display: none; -} -body, -html { - -webkit-font-smoothing: antialiased; - background-color: black; - /* background-color: #d33847; */ -} -html { - color: #fff; -} -a.button, -body, -button, -html { - font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; -} -body { - font-size: 16px; -} -body, -input, -select, -textarea { - color: #fff; -} -.code { - font-family: Consolas, "Liberation Mono", Menlo, Monaco, Lucida Console, - Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, - monospace, serif; -} -.container { - margin: 0 20px; - display: inherit; - position: relative; - width: calc(100% - 40px); -} -@media (min-width: 1310px) { - .container { - margin: auto; - width: 1270px; - } -} -h2 { - font-size: 2.5em; -} -h3 { - font-size: 1.6em; -} -h4 { - font-size: 1.2em; -} -.addon, -input, -select, -textarea { - outline: 0; - font-size: 14px; -} -ul.columns { - list-style-position: inside; - columns: 1; -} -@media (min-width: 650px) { - ul.columns { - columns: 2; - } -} -@media (min-width: 800px) { - ul.columns { - columns: 3; - } -} -a.button, -button { - font-size: 1.125em; - background: #999; - border-radius: 5px; - border: 0; - cursor: pointer; - display: inline-block; - padding: 8px 34px; - background: #fff; - color: #f03b4d; - font-weight: 500; - box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.15); - margin: 15px; - text-shadow: none; -} -a.button.small, -button.small { - font-size: 0.875em; - padding: 8px 26px; -} -a.button:hover, -button:hover { - background: #ffd1d6; - background: linear-gradient(#fff, #ffd1d6); - border: 0; - outline: 0; -} -a.button:active, -a.button:focus, -button:active, -button:focus { - background: #ffd1d6; - background: linear-gradient(#ffd1d6, #fff); - border: 0; - outline: 0; -} -a.button.inverted, -button.inverted { - background: 0 0; - color: #fff; - padding: 6px 30px; - border: 2px solid #fff; - font-weight: 500; -} -a.button.inverted.small, -button.inverted.small { - padding: 6px 24px; -} -a.button.inverted:hover, -button.inverted:hover { - background-color: rgba(255, 255, 255, 0.15); -} -a.button.inverted:active, -a.button.inverted:focus, -button.inverted:active, -button.inverted:focus { - background-color: rgba(0, 0, 0, 0.25); -} -a.button.inverted.blue, -button.inverted.blue { - border-color: #5daced; -} -a.button.inverted.purple, -button.inverted.purple { - border-color: #df76ff; -} -a.button.inverted.red, -button.inverted.red { - border-color: #ff6170; -} -a.button.inverted.yellow, -button.inverted.yellow { - border-color: #fff776; -} -a.button.inverted.orange, -button.inverted.orange { - border-color: #ff853e; -} -a.button.inverted.green, -button.inverted.green { - border-color: #15dc98; -} -.table-container { - overflow-x: auto; -} -table:not(.code) { - border-collapse: collapse; - border-spacing: 0; - font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; - text-shadow: none; - box-shadow: 0 2px 2px rgba(0, 0, 0, 0.25); - border-bottom: 1px solid #111; - border-top: 1px solid #111; -} -table:not(.code) thead { - background-color: #131313; - text-align: left; - border-bottom: 1px solid #111; -} -table:not(.code) td, -table:not(.code) th { - border-left: 1px solid #111; - padding: 0.5em 1.25em; -} -table:not(.code) td:last-child, -table:not(.code) th:last-child { - border-right: 1px solid #111; -} -table:not(.code) tbody > tr { - background: #303133; -} -table:not(.code) tbody > tr:nth-child(2n) { - background: #202020; -} -.grid { - display: flex; -} -@media (max-width: 870px) { - .grid { - display: block; - } -} -.col { - box-sizing: border-box; - flex: 1; -} -.col.center { - align-self: center; -} -.hl, -.hl-dark { - color: #abb2bf; -} -.hl .c, -.hl-dark .c { - color: #5c6370; - font-style: italic; -} -.hl .err, -.hl-dark .err { - color: #960050; - background-color: #1e0010; -} -.hl .k, -.hl-dark .k { - color: #c678dd; -} -.hl .l, -.hl-dark .l { - color: #98c379; -} -.hl .n, -.hl .o, -.hl .p, -.hl-dark .n, -.hl-dark .o, -.hl-dark .p { - color: #abb2bf; -} -.hl .cm, -.hl-dark .cm { - color: #5c6370; - font-style: italic; -} -.hl .cp, -.hl-dark .cp { - color: #61afef; -} -.hl .c1, -.hl .cs, -.hl-dark .c1, -.hl-dark .cs { - color: #5c6370; - font-style: italic; -} -.hl .ge, -.hl-dark .ge { - font-style: italic; -} -.hl .gs, -.hl-dark .gs { - font-weight: 700; -} -.hl .kc, -.hl .kd, -.hl .kn, -.hl-dark .kc, -.hl-dark .kd, -.hl-dark .kn { - color: #c678dd; -} -.hl .kp, -.hl-dark .kp { - color: #d19a66; -} -.hl .kr, -.hl-dark .kr { - color: #c678dd; -} -.hl .kt, -.hl-dark .kt { - color: #e06c75; -} -.hl .ld, -.hl-dark .ld { - color: #98c379; -} -.hl .m, -.hl-dark .m { - color: #d19a66; -} -.hl .s, -.hl-dark .s { - color: #98c379; -} -.hl .na, -.hl-dark .na { - color: #d19a66; -} -.hl .nb, -.hl .nc, -.hl .nd, -.hl .ne, -.hl .ni, -.hl .no, -.hl-dark .nb, -.hl-dark .nc, -.hl-dark .nd, -.hl-dark .ne, -.hl-dark .ni, -.hl-dark .no { - color: #e5c07b; -} -.hl .nf, -.hl-dark .nf { - color: #61afef; -} -.hl .nl, -.hl-dark .nl { - color: #e5c07b; -} -.hl .nn, -.hl .nx, -.hl-dark .nn, -.hl-dark .nx { - color: #abb2bf; -} -.hl .py, -.hl-dark .py { - color: #e5c07b; -} -.hl .nt, -.hl-dark .nt { - color: #e06c75; -} -.hl .nv, -.hl-dark .nv { - color: #e5c07b; -} -.hl .ow, -.hl-dark .ow { - font-weight: 700; -} -.hl .w, -.hl-dark .w { - color: #f8f8f2; -} -.hl .mf, -.hl .mh, -.hl .mi, -.hl .mo, -.hl-dark .mf, -.hl-dark .mh, -.hl-dark .mi, -.hl-dark .mo { - color: #d19a66; -} -.hl .s2, -.hl .sb, -.hl .sc, -.hl .sd, -.hl .se, -.hl .sh, -.hl .si, -.hl .sx, -.hl-dark .s2, -.hl-dark .sb, -.hl-dark .sc, -.hl-dark .sd, -.hl-dark .se, -.hl-dark .sh, -.hl-dark .si, -.hl-dark .sx { - color: #98c379; -} -.hl .sr, -.hl-dark .sr { - color: #56b6c2; -} -.hl .s1, -.hl-dark .s1 { - color: #98c379; -} -.hl .ss, -.hl-dark .ss { - color: #56b6c2; -} -.hl .bp, -.hl .vc, -.hl .vg, -.hl-dark .bp, -.hl-dark .vc, -.hl-dark .vg { - color: #e5c07b; -} -.hl .vi, -.hl-dark .vi { - color: #e06c75; -} -.hl .il, -.hl-dark .il { - color: #d19a66; -} -.hl .gu, -.hl-dark .gu { - color: #75715e; -} -.hl .gd, -.hl-dark .gd { - color: #f92672; -} -.hl .gi, -.hl-dark .gi { - color: #a6e22e; -} -.hl-light { - color: #383a42; -} -.hl-light .c { - color: #a0a1a7; - font-style: italic; -} -.hl-light .err { - color: #960050; - background-color: #1e0010; -} -.hl-light .k { - color: #a626a4; -} -.hl-light .l { - color: #50a14f; -} -.hl-light .n, -.hl-light .o, -.hl-light .p { - color: #383a42; -} -.hl-light .cm { - color: #a0a1a7; - font-style: italic; -} -.hl-light .cp { - color: #4078f2; -} -.hl-light .c1, -.hl-light .cs { - color: #a0a1a7; - font-style: italic; -} -.hl-light .ge { - font-style: italic; -} -.hl-light .gs { - font-weight: 700; -} -.hl-light .kc, -.hl-light .kd, -.hl-light .kn { - color: #a626a4; -} -.hl-light .kp { - color: #986801; -} -.hl-light .kr { - color: #a626a4; -} -.hl-light .kt { - color: #e45649; -} -.hl-light .ld { - color: #50a14f; -} -.hl-light .m { - color: #986801; -} -.hl-light .s { - color: #50a14f; -} -.hl-light .na { - color: #986801; -} -.hl-light .nb, -.hl-light .nc, -.hl-light .nd, -.hl-light .ne, -.hl-light .ni, -.hl-light .no { - color: #c18401; -} -.hl-light .nf { - color: #4078f2; -} -.hl-light .nl { - color: #c18401; -} -.hl-light .nn, -.hl-light .nx { - color: #383a42; -} -.hl-light .py { - color: #c18401; -} -.hl-light .nt { - color: #e45649; -} -.hl-light .nv { - color: #c18401; -} -.hl-light .ow { - font-weight: 700; -} -.hl-light .w { - color: #f8f8f2; -} -.hl-light .mf, -.hl-light .mh, -.hl-light .mi, -.hl-light .mo { - color: #986801; -} -.hl-light .s2, -.hl-light .sb, -.hl-light .sc, -.hl-light .sd, -.hl-light .se, -.hl-light .sh, -.hl-light .si, -.hl-light .sx { - color: #50a14f; -} -.hl-light .sr { - color: #0184bc; -} -.hl-light .s1 { - color: #50a14f; -} -.hl-light .ss { - color: #0184bc; -} -.hl-light .bp, -.hl-light .vc, -.hl-light .vg { - color: #c18401; -} -.hl-light .vi { - color: #e45649; -} -.hl-light .il { - color: #986801; -} -.hl-light .gu { - color: #75715e; -} -.hl-light .gd { - color: #f92672; -} -.hl-light .gi { - color: #a6e22e; -} -header { - background-color: #d33847; - position: relative; - overflow: hidden; -} -header :not(.cloud) { - position: relative; - z-index: 10; -} -header h1, -header h2, -header h3, -header h4 { - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; - text-shadow: 0 2px 0 rgba(0, 0, 0, 0.25); -} -header h1 { - font-size: 42px; - margin: 30px 0 0; - font-weight: 600; -} -header h1 a:active, -header h1 a:focus, -header h1 a:hover { - color: #ffbec4; - border: 0; -} -header h4 { - font-size: 16px; - line-height: 18px; - font-weight: 500; - margin: 0; - padding: 10px 0 70px; - color: #ffdada; -} -header .banner { - margin: 30px 0 50px; -} -header .banner h1, -header .banner h2, -header .banner h3, -header .banner h4 { - font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; -} -header .banner h1 { - font-size: 36px; - font-weight: 500; - margin: 17px 0; -} -header .banner h2 { - font-size: 25px; - font-weight: 300; - line-height: 36px; - margin: 10px auto 15px; - max-width: 580px; -} -.cloud { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: block; - -webkit-backface-visibility: hidden; - -moz-backface-visibility: hidden; - -ms-backface-visibility: hidden; - backface-visibility: hidden; - -webkit-perspective: 1000; - -moz-perspective: 1000; - -ms-perspective: 1000; - perspective: 1000; - transform: translateZ(0); -} -.cloud.back { - z-index: 1; - opacity: 0.03; -} -.cloud.front { - z-index: 2; - opacity: 0.06; -} -.features img { - margin-bottom: 15px; -} -.features h2 { - font-size: 36px; - color: #fff; - text-shadow: 0 2px 0 rgba(0, 0, 0, 0.15); - font-weight: 500; - line-height: 36px; - margin-top: 0; - margin-bottom: 10px; -} -.features figcaption, -.features h2, -.features h3, -.features h4 { - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; -} -.features h3 { - font-size: 20px; - line-height: 20px; - text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); - margin: 0 auto; - font-weight: 500; -} -.features h3.blue { - color: #5daced; -} -.features h3.purple { - color: #df76ff; -} -.features h3.red { - color: #ff6170; -} -.features h3.yellow { - color: #fff776; -} -.features h3.orange { - color: #ff853e; -} -.features h3.green { - color: #15dc98; -} -.features figcaption, -.features h4 { - font-weight: 400; -} -.features h4 { - margin-top: 0; - margin-bottom: 35px; - font-size: 18px; - color: #a2a2a2; - line-height: 24px; -} -.features figcaption { - color: #ffd5d8; - font-size: 15px; - line-height: 25px; - max-width: 220px; - margin: 10px auto 0; -} -.features figcaption.wide { - margin-bottom: 15px; - max-width: 300px; - font-weight: 300; - color: #fff; - padding: 0 10px; -} -.features .col { - border-right: 1px solid rgba(255, 255, 255, 0.25); - margin: 40px 0; -} -@media (max-width: 870px) { - .features .col { - border-right: none; - } -} -.features .col.tight:last-child, -.features .col:last-child { - border-right: none; -} -@media (max-width: 870px) { - .features .col:last-child { - margin-bottom: 40px; - } -} -.features .col.tight { - padding: 35px 0; - margin: 10px 0; - border-right: 1px solid rgba(255, 255, 255, 0.1); -} -@media (max-width: 870px) { - .features .col.tight { - border-right: none; - } -} -@media (max-width: 870px) { - .features .col.tight:last-child { - margin-bottom: 0; - } -} -.features hr { - background-color: rgba(255, 255, 255, 0.1); - margin: 30px 0; -} -a:hover { - border-bottom: 1px dashed #fff; -} -a:active, -a:focus { - border-bottom: 1px solid #fff; - outline: 0; -} -#pagination ul, -.centered, -.circle-container h4 { - text-align: center; -} -.pull-right { - float: right; -} -.text-right { - text-align: right; -} -table { - width: 100%; -} -table.code { - display: inline-block; - color: #a5abb8; - font-size: 14px; - text-shadow: none; - padding: 0; - border-spacing: 0; - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.5); - border-radius: 5px; - width: 100%; - overflow-x: auto; -} -table.code * { - font-family: Consolas, "Liberation Mono", Menlo, Monaco, Lucida Console, - Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, - monospace, serif; - line-height: 18px; - margin: 0; - -webkit-font-smoothing: subpixel-antialiased; -} -table.code td.linenos { - background-color: #3a3a3a; - padding: 15px 10px 15px 18px; - color: #666; - text-align: right; - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; - border: 1px solid #4b4b4b; - border-right: 1px solid #1b1d22; -} -table.code td.code { - padding-left: 15px; - background: #323232; - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; - border: 1px solid #4b4b4b; - border-left: none; - width: 100%; -} -footer, -section { - text-shadow: 0 2px 0 rgba(0, 0, 0, 0.25); - background-color: #272727; -} -section { - padding: 70px 0; -} -footer.tight, -section.tight { - padding: 0; -} -footer.dark, -section.dark { - background-color: #1d1d1d; -} -footer h3, -section h3 { - font-size: 36px; - font-weight: 500; - margin: 20px 0 0; - line-height: 48px; -} -footer .col.left-main, -section .col.left-main { - line-height: 22px; - align-self: center; -} -footer .col.left-main h3, -footer .col.left-main p, -section .col.left-main h3, -section .col.left-main p { - padding: 0 20px; -} -footer .col.right-main, -section .col.right-main { - overflow-x: auto; - align-self: center; -} -@media (max-width: 870px) { - footer h3, - section h3 { - margin: -10px 0 0; - text-align: center; - } - footer span.callout, - section span.callout { - display: block; - text-align: center; - } - footer .col.left-main, - section .col.left-main { - padding-bottom: 10px; - } -} -@media (min-width: 870px) { - footer:nth-child(even) .col.left-main, - section:nth-child(even) .col.left-main { - padding-left: 20px; - } - footer:nth-child(even) .col.right-main, - section:nth-child(even) .col.right-main { - padding-right: 20px; - order: -1; - } - footer:nth-child(odd) .col.left-main, - section:nth-child(odd) .col.left-main { - padding-right: 20px; - } - footer:nth-child(odd) .col.right-main, - section:nth-child(odd) .col.right-main { - padding-left: 20px; - } -} -footer:nth-child(odd), -section:nth-child(odd) { - background-color: #d33847; -} -footer:nth-child(odd) table.code, -section:nth-child(odd) table.code { - border-color: #a0a0a0; - color: #222; -} -footer:nth-child(odd) table.code td.linenos, -section:nth-child(odd) table.code td.linenos { - background-color: #efefef; - border: 1px solid #a0a0a0; - border-right: 1px solid #d5d5d5; - color: #a9a9a9; -} -footer:nth-child(odd) table.code td.code, -section:nth-child(odd) table.code td.code { - background: #fff; - border: 1px solid #a0a0a0; - border-left: none; -} -footer #sidebar, -section #sidebar { - background-color: #1d1d1d; - width: calc(280px + (100% - 1310px) / 2); - min-width: 280px; -} -@media (max-width: 870px) { - footer #sidebar, - section #sidebar { - display: none; - } -} -footer #search-results, -footer article, -section #search-results, -section article { - width: calc(100% - 280px); - max-width: 1060px; - padding: 40px; -} -@media (max-width: 870px) { - footer #search-results, - footer article, - section #search-results, - section article { - width: 100%; - padding: 35px 30px; - } -} -footer nav, -section nav { - padding: 40px 35px; - box-sizing: border-box; - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; - font-weight: 500; - position: relative; - min-width: 250px; - float: right; - display: inline-block; - overflow: auto; -} -footer nav .affix, -section nav .affix { - position: fixed; - top: 40px; - overflow: auto; - height: 100%; -} -footer nav .affix-bottom, -section nav .affix-bottom { - position: relative; - bottom: 40px; - overflow: auto; - height: 100%; -} -footer nav li, -section nav li { - list-style: none; -} -footer nav .toc ul, -section nav .toc ul { - padding: 10px 0 0; -} -footer nav .toc ul:first-child, -section nav .toc ul:first-child { - padding: 0; - margin: 0; -} -footer nav .toc ul.small, -section nav .toc ul.small { - padding: 0; -} -footer nav .toc ul.small a, -section nav .toc ul.small a { - font-size: 14px; -} -footer nav .toc > ul > li, -section nav .toc > ul > li { - font-size: 20px; - line-height: 20px; - margin: 20px 0; -} -footer nav .toc > ul > li:first-child, -section nav .toc > ul > li:first-child { - margin: 0; -} -footer nav .toc > ul > li ul li, -section nav .toc > ul > li ul li { - border-left: 1px solid #424242; - padding: 10px 0 10px 17px; - font-size: 14px; - line-height: 14px; -} -footer nav .toc > ul > li ul li a, -section nav .toc > ul > li ul li a { - color: #c5c5c5; -} -article.news a > h2, -footer nav .toc > ul > li ul li a.active, -footer nav .toc > ul > li ul li a:hover, -section nav .toc > ul > li ul li a.active, -section nav .toc > ul > li ul li a:hover { - color: #fff; -} -footer nav .toc > ul > li ul li a:active, -footer nav .toc > ul > li ul li a:focus, -section nav .toc > ul > li ul li a:active, -section nav .toc > ul > li ul li a:focus { - color: #444; -} -footer nav a:hover, -section nav a:hover { - border: 0; - text-decoration: underline; - color: #fff; -} -footer nav a:active, -footer nav a:focus, -section nav a:active, -section nav a:focus { - border: 0; - color: #444; -} -footer nav a.active, -section nav a.active { - font-weight: 700; -} -#search-results, -article, -main { - box-sizing: border-box; - padding: 40px 10px; - font-size: 16px; - font-family: Helvetica, Arial, "Lucida Grande", sans-serif; - line-height: 24px; -} -#search-results p, -article p, -main p { - margin: 15px 0; -} -#search-results h2, -#search-results h3, -#search-results h4, -#search-results h5, -article h2, -article h3, -article h4, -article h5, -main h2, -main h3, -main h4, -main h5 { - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; - font-weight: 600; - text-align: left; -} -#search-results h2 + p, -#search-results h3 + p, -#search-results h4 + p, -#search-results h5 + p, -article h2 + p, -article h3 + p, -article h4 + p, -article h5 + p, -main h2 + p, -main h3 + p, -main h4 + p, -main h5 + p { - margin-top: 0; -} -#search-results h2:first-child, -#search-results h3:first-child, -#search-results h4:first-child, -#search-results h5:first-child, -article h2:first-child, -article h3:first-child, -article h4:first-child, -article h5:first-child, -main h2:first-child, -main h3:first-child, -main h4:first-child, -main h5:first-child { - padding-top: 0; - margin-top: 0; -} -#pagination li, -#search-results h2:hover > a, -#search-results h3:hover > a, -#search-results h4:hover > a, -#search-results h5:hover > a, -article h2:hover > a, -article h3:hover > a, -article h4:hover > a, -article h5:hover > a, -main h2:hover > a, -main h3:hover > a, -main h4:hover > a, -main h5:hover > a { - display: inline; -} -#search-results h2, -article h2, -main h2 { - padding: 20px 0 10px; - margin: 15px 0; - font-size: 30px; - line-height: 32px; -} -#search-results h3, -article h3, -main h3 { - padding: 10px 0; - margin: 20px 0 10px; - font-size: 22px; - line-height: 24px; -} -#search-results h4, -#search-results h5, -article h4, -article h5, -main h4, -main h5 { - padding: 10px 0; - margin: 10px 0 5px; -} -#search-results h4, -article h4, -main h4 { - font-size: 18px; - line-height: 20px; -} -#search-results h5, -article h5, -article.news h5, -main h5 { - font-size: 16px; - line-height: 20px; -} -#search-results a.headerlink, -article a.headerlink, -main a.headerlink { - color: #5e5e5e; - font-size: 0.8em; - line-height: 1em; - margin-left: 5px; - border-bottom: none; -} -@media (min-width: 870px) { - #search-results a.headerlink, - article a.headerlink, - main a.headerlink { - display: none; - } -} -#search-results a.headerlink:active, -#search-results a.headerlink:focus, -#search-results a.headerlink:hover, -article a.headerlink:active, -article a.headerlink:focus, -article a.headerlink:hover, -main a.headerlink:active, -main a.headerlink:focus, -main a.headerlink:hover { - border: 0; -} -#search-results a.headerlink:hover, -article a.headerlink:hover, -main a.headerlink:hover { - color: #ccc; -} -#search-results a.headerlink:active, -#search-results a.headerlink:focus, -article a.headerlink:active, -article a.headerlink:focus, -main a.headerlink:active, -main a.headerlink:focus { - color: #fff; -} -#search-results blockquote, -article blockquote, -main blockquote { - background-color: rgba(255, 255, 255, 0.05); - margin: 0; - padding: 5px 20px 5px 30px; - border-left: 10px solid rgba(255, 255, 255, 0.1); - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.5); - text-shadow: 0 2px 0 rgba(0, 0, 0, 0.25); -} -article.news { - font-size: 18px; - font-family: Helvetica, Arial, "Lucida Grande", sans-serif; - line-height: 30px; - max-width: 900px; - margin: 0 auto; - overflow-x: auto; -} -@media (max-width: 870px) { - article.news { - padding: 10px 5px; - } -} -article.news p:not(:nth-child(2)) { - margin-left: auto; - margin-right: auto; -} -article.news p.metadata { - margin: -10px 0 20px; - font-size: 16px; - color: #bbb; -} -article.news blockquote p:not(:last-child) { - color: #bbb; -} -article.news a > h2:hover { - text-decoration: none; -} -#tabs-panel .panel, -#tabs-panel input, -article.news h2 a, -article.news h2:hover > a { - display: none; -} -article.news h2 { - margin-top: 20px; - font-size: 30px; - line-height: 42px; - padding: 0; -} -article.news h3 { - font-size: 22px; - line-height: 26px; -} -article.news h4 { - font-size: 18px; - line-height: 22px; -} -#search-results a, -article a { - font-weight: 700; - color: #8ab4ff; -} -#search-results a:hover, -article a:hover { - color: inherit; - border-bottom: none; - text-decoration: underline; -} -#search-results a:hover code, -article a:hover code { - text-decoration: underline; -} -#search-results a:active, -#search-results a:focus, -article a:active, -article a:focus { - border-bottom: none; - text-decoration: underline; -} -#search-results h2, -article h2 { - border-bottom: 1px solid rgba(255, 255, 255, 0.15); -} -#search-results h3, -article h3 { - border-bottom: 1px solid rgba(255, 255, 255, 0.07); -} -main h2, -main h3 { - margin-bottom: 0; -} -main h2:not(:first-child) { - margin-top: 30px; -} -code, -footer { - text-shadow: none; -} -code { - display: inline-block; - background: rgba(0, 0, 0, 0.4); - font-size: 0.87em; - border-radius: 5px; - font-family: Consolas, "Liberation Mono", Menlo, Monaco, Lucida Console, - Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, - monospace, serif; - -webkit-font-smoothing: subpixel-antialiased; - padding: 1px 4px; - line-height: 1.4em; -} -span.orange, -span.purple { - color: #f68500; - font-weight: 400; - font-size: 0.95em; -} -span.purple { - color: #9a64c0; -} -span.callout { - display: inline-block; - background: #fff; - color: #000; - padding: 2px 15px; - font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; - font-size: 14px; - font-weight: 700; - border-radius: 5px; - border: 1px solid #000; - box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5); - margin: 0; - text-shadow: none; -} -footer { - padding: 40px 0 30px; - line-height: 32px; -} -footer, -footer a { - font-size: 14px; - color: #ffb9b9; -} -footer h2 { - font-size: 36px; - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; -} -footer h4 { - font-size: 18px; -} -#tabs-panel { - border: 1px solid #3d3d3d; - text-shadow: none; - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.5); -} -#tabs-panel p:first-child { - margin-top: 0; -} -#tabs-panel p:last-child { - margin-bottom: 0; -} -#tabs-panel .tab { - display: block; - padding: 17px 20px 17px 45px; - font: 500 18px "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; - cursor: pointer; - color: #464646; - background-color: #212121; - border-right: 1px solid #3d3d3d; - position: relative; - margin-right: -1px; - box-shadow: inset -1px 1px 3px rgba(0, 0, 0, 0.25); -} -#tabs-panel .tab img { - margin: auto 0; - position: absolute; - left: 17px; - bottom: 0; - right: 0; - top: 0; - opacity: 0.15; - pointer-events: none; -} -#tabs-panel .tab:not(:first-child) { - border-top: 1px solid #2f2f2f; -} -#tabs-panel .tab:hover { - background-color: #272727; - color: #aaa; -} -#tabs-panel .tab:hover img { - opacity: 0.5; -} -#tabs-panel .tab:focus { - background-color: #111; -} -#tabs-panel .tab:active { - background-color: #2c2c2c; -} -#tabs-panel .tabs { - flex: 1 1 300px; - background-color: #212121; - min-width: 220px; -} -#tabs-panel .panels { - background-color: #323232; - width: 100%; -} -#tabs-panel .panel { - padding: 15px 25px; - font: 16px Helvetica, Arial, "Lucida Grande", sans-serif; - color: #e0e0e0; - line-height: 26px; -} -#tabs-panel #dynamic-params-tab img { - top: -1px; -} -#tabs-panel #handling-data-tab img { - left: 18px; - top: -1px; -} -#tabs-panel #request-guards-tab img { - left: 19px; - top: -3px; -} -#tabs-panel #responders-tab img { - top: -3px; -} -#tabs-panel #launching-tab img { - left: 21px; - top: -3px; -} -#tabs-panel #dynamic-params:checked ~ .tabs #dynamic-params-tab, -#tabs-panel #handling-data:checked ~ .tabs #handling-data-tab, -#tabs-panel #launching:checked ~ .tabs #launching-tab, -#tabs-panel #request-guards:checked ~ .tabs #request-guards-tab, -#tabs-panel #responders:checked ~ .tabs #responders-tab, -#tabs-panel #routing:checked ~ .tabs #routing-tab { - text-shadow: 0 2px 0 rgba(0, 0, 0, 0.25); - background-color: #323232; - cursor: default; - color: #fff; - border-right: none; - box-shadow: none; -} -#tabs-panel #dynamic-params:checked ~ .tabs #dynamic-params-tab img, -#tabs-panel #handling-data:checked ~ .tabs #handling-data-tab img, -#tabs-panel #launching:checked ~ .tabs #launching-tab img, -#tabs-panel #request-guards:checked ~ .tabs #request-guards-tab img, -#tabs-panel #responders:checked ~ .tabs #responders-tab img, -#tabs-panel #routing:checked ~ .tabs #routing-tab img { - opacity: 1; -} -#tabs-panel #dynamic-params:checked ~ .panels #dynamic-params-panel, -#tabs-panel #handling-data:checked ~ .panels #handling-data-panel, -#tabs-panel #launching:checked ~ .panels #launching-panel, -#tabs-panel #request-guards:checked ~ .panels #request-guards-panel, -#tabs-panel #responders:checked ~ .panels #responders-panel, -#tabs-panel #routing:checked ~ .panels #routing-panel { - display: block; -} -#logo span, -#toggle { - display: none; -} -header .container { - padding-top: 35px; - z-index: 10; -} -header nav.version-select { - position: relative; - display: inline-block; - width: 70px; - height: 35px; - line-height: 35px; - text-align: center; - font-size: 15px; - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; - font-weight: 700; - z-index: 20; -} -header nav.version-select i { - border: solid #fff; - border-width: 0 3px 3px 0; - display: inline-block; - padding: 3px; - transform: rotate(45deg); - margin-left: 3px; - top: -2px; -} -header nav.version-select options { - display: none; - position: absolute; - box-shadow: 0 2px 0 rgba(0, 0, 0, 0.25); - left: -1px; - top: 35px; - z-index: 30; -} -header nav.version-select options a, -header nav.version-select options a:hover { - border: 1px solid rgba(255, 255, 255, 0.8); -} -header nav.version-select options a:hover:not(:first-child), -header nav.version-select options a:not(:first-child) { - border-top: none; -} -header nav.version-select options a { - color: #fff; - font-size: 15px; - font-weight: 700; - display: block; - background-color: rgba(255, 255, 255, 0.1); - width: 70px; - height: 35px; -} -header nav.version-select options a:hover { - background-color: rgba(255, 255, 255, 0.8); - color: #d33847; -} -@media (max-width: 870px) { - header nav.version-select options a { - background-color: #d44756; - } - header nav.version-select options a:hover { - background-color: #dcdcdc; - } -} -header .hamburger, -header nav.main { - float: right; -} -.circle-container .circle + h4, -header nav a { - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; -} -header nav a { - font-size: 18px; - font-weight: 500; -} -a#logo:active, -a#logo:hover { - border: 0; - text-decoration: none; -} -@media (min-width: 870px) { - header nav.main { - margin-top: 20px; - } - header nav.main a { - margin-left: 35px; - } - #logo { - height: 60px; - width: 180px; - background-repeat: no-repeat; - display: inline-block; - vertical-align: middle; - } -} -@media (max-width: 870px) { - @-webkit-keyframes bugfix { - 0%, - to { - padding: 0; - } - } - body { - -webkit-animation: bugfix infinite 1s; - } - #logo { - height: 30px; - width: 140px; - background-repeat: no-repeat; - display: inline-block; - vertical-align: middle; - } - header .banner { - margin-top: 20px; - } - header .banner h1 { - margin-top: 0; - } - header nav.main { - display: block; - color: #000; - width: 100%; - background-color: rgba(0, 0, 0, 0.2); - max-height: 0; - transition: max-height 0.25s ease-in-out; - margin: 20px 0 0; - overflow: hidden; - float: none; - } - header nav.main a { - display: block; - margin: 0; - padding: 15px 35px; - } - header nav.main a:first-child { - padding-top: 30px; - } - header nav.main a:last-child { - padding-bottom: 30px; - } - header nav.main a:hover { - border: 0; - background-color: rgba(0, 0, 0, 0.2); - } - header nav.main a:active, - header nav.main a:focus { - border: 0; - background-color: rgba(0, 0, 0, 0.4); - } - .hamburger, - .hamburger span { - display: block; - transform: rotate(0deg); - } - .hamburger { - float: right; - cursor: pointer; - user-select: none; - background: rgba(0, 0, 0, 0.2); - width: 38px; - height: 30px; - position: relative; - } - .hamburger span { - position: absolute; - height: 14%; - width: 60%; - background: #fff; - border-radius: 9px; - opacity: 1; - left: 20%; - transition: 0.25s ease-in-out; - } - .hamburger span:nth-child(1) { - top: 20%; - } - #toggle:checked ~ .hamburger span:nth-child(1) { - top: 43%; - transform: rotate(135deg); - } - .hamburger span:nth-child(2) { - top: 43%; - } - #toggle:checked ~ .hamburger span:nth-child(2) { - opacity: 0; - left: -60px; - } - .hamburger span:nth-child(3) { - top: 66%; - } - #toggle:checked ~ .hamburger span:nth-child(3) { - top: 43%; - transform: rotate(-135deg); - } - #toggle:checked ~ nav { - max-height: 300px; - } - .mobile-hide { - display: none; - } -} -.circle-container { - flex: 0 0 180px; -} -.circle-container .circle { - border-radius: 50%; - text-shadow: none; - width: 60px; - height: 60px; - font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; - font-size: 42px; - font-weight: 700; - color: #1e1e1e; - text-align: center; - line-height: 60px; - margin: 0 auto; -} -.circle-container .circle.blue { - background: #5daced; -} -.circle-container .circle.purple { - background: #da74f9; -} -.circle-container .circle.red { - background: #ff6170; -} -.circle-container .circle + h4 { - margin: 12px 0 0; -} -.work-step { - border-top: 1px solid #464646; - padding: 25px 0; -} -.blocky-code table.code td.linenos { - border-radius: 0; - border: 1px solid #414141; - border-right: none; - background-color: #323232; - padding-left: 20px; -} -.blocky-code table.code td.code { - border-radius: 0; - border: 1px solid #414141; - border-left: none; - background-color: #323232; - padding-left: 10px; -} -.lighter-code table.code { - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.25); -} -.lighter-code table.code td.linenos { - background-color: #434343; - border: 1px solid #494949; - border-right: none; - color: #8e8e8e; - padding: 12px; -} -.lighter-code table.code td.code { - border: 1px solid #494949; - border-left: none; - background-color: #393939; -} -@media (max-width: 870px) { - .github-corner { - display: none; - } -} -.github-corner:hover .octo-arm { - animation: octocat-wave 560ms ease-in-out; -} -input#search-input { - max-width: 180px; - color: #fff; - font-weight: 600; - border: 0; - margin-bottom: 25px; - box-sizing: border-box; - padding: 10px 15px 10px 35px; - background-color: #000; - background-size: 15px 15px; - background-repeat: no-repeat; - background-position: 10px 11px; -} -#hits { - color: inherit; -} -#hits a { - font-size: 22px; -} -#hits span { - color: #ccc; - font-weight: 400; - font-size: 14px; -} -#hits .hit { - margin-bottom: 30px; -} -#hits hr { - margin: 5px 0; -} -#hits p { - font-weight: 400; - margin: 5px 0 0; -} -#hits em { - background-color: rgba(117, 97, 0, 0.24); - color: #ffc900; - font-style: normal; - font-weight: 700; -} -#pagination .ais-pagination--item__disabled { - display: none; -} -#hits p, -#pagination .ais-pagination--item__active a { - color: #fff; -} -#pagination li a { - padding: 6px; - background: rgba(0, 0, 0, 0.5); - border: 1px solid #000; - margin: 0 5px; -} -#pagination li a:hover { - background: rgba(0, 0, 0, 0.2); - border-bottom: 1px solid #000; -} -.cancel-search { - display: inline-block; - width: 20px; - height: 20px; - cursor: pointer; - margin-bottom: -4px; - opacity: 0.6; - margin-right: 4px; -} -.cancel-search:hover { - opacity: 1; -} -.admonition { - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.5); - margin: 15px 0; - padding: 0 20px; - border-radius: 5px; - overflow: auto; - position: relative; -} -.admonition .title { - display: block; - margin: 0 -20px; - padding: 10px 20px 6px 43px; - background-repeat: no-repeat; - background-position: 20px 55%; - background-size: 17px; -} -@media (max-width: 870px) { - .admonition .title { - padding-left: 55px; - background-position: 20px 50%; - } -} -.admonition.note { - border-left: 8px solid #448aff; - background-color: rgba(68, 138, 255, 0.05); -} -.admonition.note .title { - background-color: rgba(68, 138, 255, 0.25); - border-bottom: 0.1rem solid rgba(68, 138, 255, 0.1); - background-position: 20px 50%; -} -.admonition.tip { - border-left: 8px solid #00bfa5; - background-color: rgba(0, 191, 165, 0.05); -} -.admonition.tip .title { - background-color: rgba(0, 191, 165, 0.25); - border-bottom: 0.1rem solid rgba(0, 191, 165, 0.1); -} -.admonition.warning { - border-left: 8px solid #ff9100; - background-color: rgba(255, 145, 0, 0.05); -} -.admonition.warning .title { - background-color: rgba(255, 145, 0, 0.25); - border-bottom: 0.1rem solid rgba(255, 145, 0, 0.1); -} diff --git a/docs/rocketsrchrome_files/sun.svg b/docs/rocketsrchrome_files/sun.svg deleted file mode 100644 index 435993c5e..000000000 --- a/docs/rocketsrchrome_files/sun.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/telescope.svg b/docs/rocketsrchrome_files/telescope.svg deleted file mode 100644 index 62e24291c..000000000 --- a/docs/rocketsrchrome_files/telescope.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/templating-icon.svg b/docs/rocketsrchrome_files/templating-icon.svg deleted file mode 100644 index 29049bc2c..000000000 --- a/docs/rocketsrchrome_files/templating-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/testing-icon.svg b/docs/rocketsrchrome_files/testing-icon.svg deleted file mode 100644 index 0a4e9acec..000000000 --- a/docs/rocketsrchrome_files/testing-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/core-macro/src/rsx/ambiguous.rs b/packages/core-macro/src/rsx/ambiguous.rs index 15a30aa06..82b95d2b5 100644 --- a/packages/core-macro/src/rsx/ambiguous.rs +++ b/packages/core-macro/src/rsx/ambiguous.rs @@ -50,9 +50,14 @@ impl Parse for AmbiguousElement { false => { let first_char = name_str.chars().next().unwrap(); if first_char.is_ascii_uppercase() { - input - .parse::() - .map(|c| AmbiguousElement::Component(c)) + match name_str.as_str() { + "Fragment" => input + .parse::() + .map(|c| AmbiguousElement::Fragment(c)), + _ => input + .parse::() + .map(|c| AmbiguousElement::Component(c)), + } } else { let name = input.parse::().unwrap(); Err(Error::new( diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index ceb4bef05..3d10aede0 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/core-macro/src/rsx/component.rs @@ -116,26 +116,16 @@ impl ToTokens for Component { #( .add_child(#childs) )* .finish() }; - let name_str = name.segments.last().unwrap().ident.to_string(); - match name_str.as_str() { - "Fragment" => tokens.append_all(quote! { - dioxus::builder::vfragment( - __ctx, - #key_token, - #children - ) - }), - _ => tokens.append_all(quote! { - dioxus::builder::virtual_child( - __ctx, - #name, - #builder, - #key_token, - #children - ) - }), - } - // } + + tokens.append_all(quote! { + dioxus::builder::virtual_child( + __ctx, + #name, + #builder, + #key_token, + #children + ) + }) } } diff --git a/packages/core-macro/src/rsx/fragment.rs b/packages/core-macro/src/rsx/fragment.rs index 7a5d12014..840b14797 100644 --- a/packages/core-macro/src/rsx/fragment.rs +++ b/packages/core-macro/src/rsx/fragment.rs @@ -8,6 +8,10 @@ //! - [ ] Children //! - [ ] Keys +use syn::parse::ParseBuffer; + +use super::AmbiguousElement; + use { proc_macro::TokenStream, proc_macro2::{Span, TokenStream as TokenStream2}, @@ -19,15 +23,44 @@ use { }, }; -pub struct Fragment {} +pub struct Fragment { + children: Vec, +} impl Parse for Fragment { fn parse(input: ParseStream) -> Result { - todo!() + input.parse::()?; + + let children = Vec::new(); + + // parse the guts + let content: ParseBuffer; + syn::braced!(content in input); + while !content.is_empty() { + content.parse::()?; + + if content.peek(Token![,]) { + let _ = content.parse::(); + } + } + Ok(Self { children }) } } impl ToTokens for Fragment { fn to_tokens(&self, tokens: &mut TokenStream2) { - todo!() + let childs = &self.children; + let children = quote! { + ChildrenList::new(__ctx) + #( .add_child(#childs) )* + .finish() + }; + tokens.append_all(quote! { + // #key_token, + dioxus::builder::vfragment( + __ctx, + None, + #children + ) + }) } } diff --git a/packages/core-macro/src/rsx/mod.rs b/packages/core-macro/src/rsx/mod.rs index a96796c1c..6eb6d90db 100644 --- a/packages/core-macro/src/rsx/mod.rs +++ b/packages/core-macro/src/rsx/mod.rs @@ -26,7 +26,7 @@ pub use node::*; use crate::util::is_valid_tag; use proc_macro2::TokenStream as TokenStream2; -use quote::{quote, ToTokens}; +use quote::{quote, ToTokens, TokenStreamExt}; use syn::{ parse::{Parse, ParseStream}, Error, Ident, LitStr, Result, Token, @@ -34,7 +34,7 @@ use syn::{ pub struct RsxRender { custom_context: Option, - root: AmbiguousElement, + roots: Vec, } impl Parse for RsxRender { @@ -61,16 +61,13 @@ impl Parse for RsxRender { None }; - let root = { input.parse::() }?; - if !input.is_empty() { - return Err(Error::new( - input.span(), - "Currently only one element is allowed per component. Try wrapping your list of components in a `Fragment` tag", - )); + let mut roots = Vec::new(); + while !input.is_empty() { + roots.push(input.parse::()?); } Ok(Self { - root, + roots, custom_context, }) } @@ -78,27 +75,41 @@ impl Parse for RsxRender { impl ToTokens for RsxRender { fn to_tokens(&self, out_tokens: &mut TokenStream2) { - let inner = &self.root; - let output = match &self.custom_context { - // The `in ctx` pattern allows directly rendering - Some(ident) => { - quote! { - #ident.render(dioxus::prelude::LazyNodes::new(move |__ctx|{ - let bump = &__ctx.bump(); - #inner - })) - } - } - // Otherwise we just build the LazyNode wrapper - None => { - quote! { - dioxus::prelude::LazyNodes::new(move |__ctx|{ - let bump = &__ctx.bump(); - #inner - }) - } + let inner = if self.roots.len() == 1 { + let inner = &self.roots[0]; + quote! {#inner} + } else { + let childs = &self.roots; + let children = quote! { + ChildrenList::new(__ctx) + #( .add_child(#childs) )* + .finish() + }; + quote! { + // #key_token, + dioxus::builder::vfragment( + __ctx, + None, + #children + ) } }; - output.to_tokens(out_tokens) + + match &self.custom_context { + // The `in ctx` pattern allows directly rendering + Some(ident) => out_tokens.append_all(quote! { + #ident.render(dioxus::prelude::LazyNodes::new(move |__ctx|{ + let bump = &__ctx.bump(); + #inner + })) + }), + // Otherwise we just build the LazyNode wrapper + None => out_tokens.append_all(quote! { + dioxus::prelude::LazyNodes::new(move |__ctx|{ + let bump = &__ctx.bump(); + #inner + }) + }), + }; } } diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index 328889715..e8ad5243f 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -39,6 +39,7 @@ fn app(ctx: Context) -> VNode { .item_handler(Callback(set_val.clone())) .build(), None, + &[], )); } root.finish() diff --git a/packages/core/examples/nested.rs b/packages/core/examples/nested.rs index 6da3fae8c..0a4e278cf 100644 --- a/packages/core/examples/nested.rs +++ b/packages/core/examples/nested.rs @@ -19,6 +19,7 @@ static Header: FC<()> = |ctx| { Bottom, (), None, + &[], )))) .finish() })) diff --git a/packages/web/examples/fragment.rs b/packages/web/examples/fragment.rs index dd3bf7e86..1f3e63049 100644 --- a/packages/web/examples/fragment.rs +++ b/packages/web/examples/fragment.rs @@ -14,14 +14,12 @@ fn main() { static App: FC<()> = |ctx| { ctx.render(rsx! { - h2 { "abc 1" } - h2 { "abc 1" } - h2 { "abc 1" } - h2 { "abc 1" } - h2 { "abc 1" } h2 { "abc 1" } div { "hello world!" } + Fragment { + h2 { "abc 2"} + } }) }; diff --git a/packages/web/examples/helloworld.rs b/packages/web/examples/helloworld.rs new file mode 100644 index 000000000..480adace4 --- /dev/null +++ b/packages/web/examples/helloworld.rs @@ -0,0 +1,11 @@ +use dioxus_web::prelude::*; + +fn main() { + wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App)) +} + +fn App(ctx: Context<()>) -> VNode { + ctx.render(rsx! { + div { "Hello, world!" } + }) +} diff --git a/packages/web/examples/todomvcsingle.rs b/packages/web/examples/todomvcsingle.rs index a2f6eacbe..8320cd198 100644 --- a/packages/web/examples/todomvcsingle.rs +++ b/packages/web/examples/todomvcsingle.rs @@ -149,11 +149,11 @@ pub fn FilterToggles(ctx: Context<()>) -> VNode { .iter() .map(|(name, path, filter)| { rsx!( - li { - class: "{name}" - a { "{name}", + li { class: "{name}" + a { href: "{path}", onclick: move |_| reducer.set_filter(&filter), + "{name}", } } ) @@ -168,7 +168,7 @@ pub fn FilterToggles(ctx: Context<()>) -> VNode { footer { span { strong {"{items_left}"} - span {"{item_text} left"} + span { "{item_text} left" } } ul { class: "filters" @@ -180,8 +180,7 @@ pub fn FilterToggles(ctx: Context<()>) -> VNode { pub fn Footer(ctx: Context<()>) -> VNode { rsx! { in ctx, - footer { - class: "info" + footer { class: "info" p {"Double-click to edit a todo"} p { "Created by "