aboutsummaryrefslogtreecommitdiff
path: root/test/examples
diff options
context:
space:
mode:
Diffstat (limited to 'test/examples')
-rw-r--r--test/examples/counter.gleam56
-rw-r--r--test/examples/counter.html17
-rw-r--r--test/examples/index.html21
-rw-r--r--test/examples/input.gleam123
-rw-r--r--test/examples/input.html54
-rw-r--r--test/examples/nested.gleam57
-rw-r--r--test/examples/nested.html17
7 files changed, 345 insertions, 0 deletions
diff --git a/test/examples/counter.gleam b/test/examples/counter.gleam
new file mode 100644
index 0000000..0bff35b
--- /dev/null
+++ b/test/examples/counter.gleam
@@ -0,0 +1,56 @@
+// IMPORTS ---------------------------------------------------------------------
+
+import gleam/int
+import lustre
+import lustre/element.{Element, t}
+import lustre/html.{button, div, p}
+import lustre/event
+
+// MAIN ------------------------------------------------------------------------
+
+pub fn main() {
+ // A `simple` lustre application doesn't produce `Cmd`s. These are best to
+ // start with if you're just getting started with lustre or you know you don't
+ // need the runtime to manage any side effects.
+ let app = lustre.simple(init, update, render)
+ let assert Ok(_) = lustre.start(app, "[data-lustre-app]")
+}
+
+// MODEL -----------------------------------------------------------------------
+
+pub type Model =
+ Int
+
+pub fn init() -> Model {
+ 0
+}
+
+// UPDATE ----------------------------------------------------------------------
+
+pub opaque type Msg {
+ Incr
+ Decr
+ Reset
+}
+
+pub fn update(model: Model, msg: Msg) -> Model {
+ case msg {
+ Incr -> model + 1
+ Decr -> model - 1
+ Reset -> 0
+ }
+}
+
+// VIEW ------------------------------------------------------------------------
+
+pub fn render(model: Model) -> Element(Msg) {
+ div(
+ [],
+ [
+ button([event.on_click(Incr)], [t("+")]),
+ button([event.on_click(Decr)], [t("-")]),
+ button([event.on_click(Reset)], [t("Reset")]),
+ p([], [t(int.to_string(model))]),
+ ],
+ )
+}
diff --git a/test/examples/counter.html b/test/examples/counter.html
new file mode 100644
index 0000000..2b120fd
--- /dev/null
+++ b/test/examples/counter.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>lustre | counter</title>
+
+ <script type="module">
+ import { main } from "../../build/dev/javascript/lustre/examples/counter.mjs";
+
+ document.addEventListener("DOMContentLoaded", main);
+ </script>
+ </head>
+ <body>
+ <div data-lustre-app></div>
+ </body>
+</html>
diff --git a/test/examples/index.html b/test/examples/index.html
new file mode 100644
index 0000000..1dda01b
--- /dev/null
+++ b/test/examples/index.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>lustre | examples</title>
+ </head>
+ <body>
+ <menu>
+ <li>
+ <a href="input.html">input</a>
+ </li>
+ <li>
+ <a href="counter.html">counter</a>
+ </li>
+ <li>
+ <a href="nested.html">nested</a>
+ </li>
+ </menu>
+ </body>
+</html>
diff --git a/test/examples/input.gleam b/test/examples/input.gleam
new file mode 100644
index 0000000..9916000
--- /dev/null
+++ b/test/examples/input.gleam
@@ -0,0 +1,123 @@
+// IMPORTS ---------------------------------------------------------------------
+
+import gleam/dynamic
+import gleam/string
+import lustre
+import lustre/attribute.{attribute}
+import lustre/element.{Element, t}
+import lustre/event
+import lustre/html.{div, input, label, pre}
+
+// MAIN ------------------------------------------------------------------------
+
+pub fn main() {
+ // A `simple` lustre application doesn't produce `Cmd`s. These are best to
+ // start with if you're just getting started with lustre or you know you don't
+ // need the runtime to manage any side effects.
+ let app = lustre.simple(init, update, render)
+ let assert Ok(_) = lustre.start(app, "[data-lustre-app]")
+
+ Nil
+}
+
+// MODEL -----------------------------------------------------------------------
+
+type Model {
+ Model(email: String, password: String, remember_me: Bool)
+}
+
+fn init() -> Model {
+ Model(email: "", password: "", remember_me: False)
+}
+
+// UPDATE ----------------------------------------------------------------------
+
+type Msg {
+ Typed(Input, String)
+ Toggled(Control, Bool)
+}
+
+type Input {
+ Email
+ Password
+}
+
+type Control {
+ RememberMe
+}
+
+fn update(model: Model, msg: Msg) -> Model {
+ case msg {
+ Typed(Email, email) -> Model(..model, email: email)
+ Typed(Password, password) -> Model(..model, password: password)
+ Toggled(RememberMe, remember_me) -> Model(..model, remember_me: remember_me)
+ }
+}
+
+// RENDER ----------------------------------------------------------------------
+
+fn render(model: Model) -> Element(Msg) {
+ div(
+ [attribute.class("container")],
+ [
+ card([
+ email_input(model.email),
+ password_input(model.password),
+ remember_checkbox(model.remember_me),
+ pre([attribute.class("debug")], [t(string.inspect(model))]),
+ ]),
+ ],
+ )
+}
+
+fn card(content: List(Element(a))) -> Element(a) {
+ div([attribute.class("card")], [div([], content)])
+}
+
+fn email_input(value: String) -> Element(Msg) {
+ render_input(Email, "email", "email-input", value, "Email address")
+}
+
+fn password_input(value: String) -> Element(Msg) {
+ render_input(Password, "password", "password-input", value, "Password")
+}
+
+fn render_input(
+ field: Input,
+ type_: String,
+ id: String,
+ value: String,
+ text: String,
+) -> Element(Msg) {
+ div(
+ [attribute.class("input")],
+ [
+ label([attribute.for(id)], [t(text)]),
+ input([
+ attribute.id(id),
+ attribute.name(id),
+ attribute.type_(type_),
+ attribute.required(True),
+ attribute.value(dynamic.from(value)),
+ event.on_input(fn(value) { Typed(field, value) }),
+ ]),
+ ],
+ )
+}
+
+fn remember_checkbox(checked: Bool) -> Element(Msg) {
+ div(
+ [attribute.class("flex items-center")],
+ [
+ input([
+ attribute.id("remember-me"),
+ attribute.name("remember-me"),
+ attribute.type_("checkbox"),
+ attribute.checked(checked),
+ attribute.class("checkbox"),
+ event.on_click(Toggled(RememberMe, !checked)),
+ ]),
+ label([attribute.for("remember-me")], [t("Remember me")]),
+ ],
+ )
+}
diff --git a/test/examples/input.html b/test/examples/input.html
new file mode 100644
index 0000000..273dffa
--- /dev/null
+++ b/test/examples/input.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html lang="en" class="h-full bg-gray-50">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>lustre | forms</title>
+
+ <script src="https://cdn.tailwindcss.com"></script>
+ <style type="text/tailwindcss">
+ @layer components {
+ .container {
+ @apply flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8;
+ }
+
+ .card {
+ @apply mt-10 sm:mx-auto sm:w-full sm:max-w-[480px];
+ }
+
+ .card > div {
+ @apply bg-white px-6 py-12 shadow sm:rounded-lg sm:px-12 space-y-6;
+ }
+
+ .debug {
+ @apply text-sm text-gray-400 whitespace-pre-line;
+ }
+
+ .input label {
+ @apply block text-sm font-medium leading-6 text-gray-900;
+ }
+
+ .input input {
+ @apply block w-full rounded-md border-0 p-1.5 mt-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-pink-600 sm:text-sm sm:leading-6;
+ }
+
+ .checkbox {
+ @apply h-4 w-4 rounded border-gray-300 text-pink-600 focus:ring-pink-600;
+ }
+
+ .checkbox + label {
+ @apply ml-3 block text-sm leading-6 text-gray-900;
+ }
+ }
+ </style>
+
+ <script type="module">
+ import { main } from "../../build/dev/javascript/lustre/examples/input.mjs";
+
+ document.addEventListener("DOMContentLoaded", main);
+ </script>
+ </head>
+ <body class="h-full">
+ <div data-lustre-app></div>
+ </body>
+</html>
diff --git a/test/examples/nested.gleam b/test/examples/nested.gleam
new file mode 100644
index 0000000..bd94abe
--- /dev/null
+++ b/test/examples/nested.gleam
@@ -0,0 +1,57 @@
+// IMPORTS ---------------------------------------------------------------------
+
+import examples/counter
+import gleam/list
+import gleam/map.{Map}
+import gleam/pair
+import lustre
+import lustre/element.{Element}
+import lustre/html.{div}
+
+// MAIN ------------------------------------------------------------------------
+
+pub fn main() {
+ // A `simple` lustre application doesn't produce `Cmd`s. These are best to
+ // start with if you're just getting started with lustre or you know you don't
+ // need the runtime to manage any side effects.
+ let app = lustre.simple(init, update, render)
+ let assert Ok(_) = lustre.start(app, "[data-lustre-app]")
+
+ Nil
+}
+
+// MODEL -----------------------------------------------------------------------
+
+type Model =
+ Map(Int, counter.Model)
+
+fn init() -> Model {
+ use counters, id <- list.fold(list.range(1, 10), map.new())
+
+ map.insert(counters, id, counter.init())
+}
+
+// UPDATE ----------------------------------------------------------------------
+
+type Msg =
+ #(Int, counter.Msg)
+
+fn update(model: Model, msg: Msg) -> Model {
+ let #(id, counter_msg) = msg
+ let assert Ok(counter) = map.get(model, id)
+
+ map.insert(model, id, counter.update(counter, counter_msg))
+}
+
+// RENDER ----------------------------------------------------------------------
+
+fn render(model: Model) -> Element(Msg) {
+ let counters = {
+ use rest, id, counter <- map.fold(model, [])
+ let el = element.map(counter.render(counter), pair.new(id, _))
+
+ [el, ..rest]
+ }
+
+ div([], counters)
+}
diff --git a/test/examples/nested.html b/test/examples/nested.html
new file mode 100644
index 0000000..420b159
--- /dev/null
+++ b/test/examples/nested.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>lustre | nested</title>
+
+ <script type="module">
+ import { main } from "../../build/dev/javascript/lustre/examples/nested.mjs";
+
+ document.addEventListener("DOMContentLoaded", main);
+ </script>
+ </head>
+ <body>
+ <div data-lustre-app></div>
+ </body>
+</html>