aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorHayleigh Thompson <me@hayleigh.dev>2023-09-22 10:42:33 +0100
committerHayleigh Thompson <me@hayleigh.dev>2023-09-22 10:42:33 +0100
commit60ca120b030de72aedd675e16961f0ece2fe8063 (patch)
treee3ab36c3ce430b2dd9c205b04ffe646cd7102075 /examples
parent5e1dd775e7c1974e88df0494eb2e3effb7bcb9f2 (diff)
downloadlustre-60ca120b030de72aedd675e16961f0ece2fe8063.tar.gz
lustre-60ca120b030de72aedd675e16961f0ece2fe8063.zip
:truck: Move examples out of test so CI doesn't break.
Diffstat (limited to 'examples')
-rw-r--r--examples/components.gleam100
-rw-r--r--examples/components.html17
-rw-r--r--examples/counter.gleam56
-rw-r--r--examples/counter.html17
-rw-r--r--examples/index.html27
-rw-r--r--examples/input.gleam132
-rw-r--r--examples/input.html54
-rw-r--r--examples/nested.gleam57
-rw-r--r--examples/nested.html17
-rw-r--r--examples/svg.gleam107
-rw-r--r--examples/svg.html17
11 files changed, 601 insertions, 0 deletions
diff --git a/examples/components.gleam b/examples/components.gleam
new file mode 100644
index 0000000..b87cf84
--- /dev/null
+++ b/examples/components.gleam
@@ -0,0 +1,100 @@
+// IMPORTS ---------------------------------------------------------------------
+
+import gleam/dynamic
+import gleam/function
+import gleam/int
+import gleam/list
+import gleam/map
+import gleam/result
+import lustre
+import lustre/attribute
+import lustre/effect
+import lustre/element.{element, text}
+import lustre/element/html.{button, div, li, ol, p, slot}
+import lustre/event
+
+// MAIN ------------------------------------------------------------------------
+
+pub fn main() {
+ let assert Ok(_) =
+ lustre.component(
+ "custom-counter",
+ counter_init,
+ counter_update,
+ counter_view,
+ map.from_list([
+ #(
+ "count",
+ fn(attr) {
+ dynamic.int(attr)
+ |> result.map(GotCount)
+ },
+ ),
+ ]),
+ )
+
+ // A `simple` lustre application doesn't produce `Effect`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, view)
+ let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
+
+ Nil
+}
+
+fn init(_) {
+ []
+}
+
+fn update(history, msg) {
+ case msg {
+ "reset" -> []
+ _ -> [msg, ..history]
+ }
+}
+
+fn view(history) {
+ let on_custom_click = event.on("custom-click", function.constant(Ok("click")))
+
+ div(
+ [],
+ [
+ button([event.on_click("reset")], [text("Reset")]),
+ ol([], list.map(history, fn(msg) { li([], [text(msg)]) })),
+ element(
+ "custom-counter",
+ [on_custom_click, attribute.property("count", list.length(history))],
+ [ol([], list.map(history, fn(msg) { li([], [text(msg)]) }))],
+ ),
+ ],
+ )
+}
+
+// COUNTER ---------------------------------------------------------------------
+
+fn counter_init() {
+ #(0, effect.none())
+}
+
+type CounterMsg {
+ GotCount(Int)
+ Clicked
+}
+
+fn counter_update(count, msg) {
+ case msg {
+ GotCount(count) -> #(count, effect.none())
+ Clicked -> #(count, event.emit("custom-click", Nil))
+ }
+}
+
+fn counter_view(count) {
+ div(
+ [],
+ [
+ button([event.on_click(Clicked)], [text("Click me!")]),
+ p([], [text("Count: "), text(int.to_string(count))]),
+ slot([]),
+ ],
+ )
+}
diff --git a/examples/components.html b/examples/components.html
new file mode 100644
index 0000000..f4b4530
--- /dev/null
+++ b/examples/components.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 | components</title>
+
+ <script type="module">
+ import { main } from "../../build/dev/javascript/lustre/examples/components.mjs";
+
+ document.addEventListener("DOMContentLoaded", main);
+ </script>
+ </head>
+ <body>
+ <div data-lustre-app></div>
+ </body>
+</html>
diff --git a/examples/counter.gleam b/examples/counter.gleam
new file mode 100644
index 0000000..4faf00c
--- /dev/null
+++ b/examples/counter.gleam
@@ -0,0 +1,56 @@
+// IMPORTS ---------------------------------------------------------------------
+
+import gleam/int
+import lustre
+import lustre/element.{Element, text}
+import lustre/element/html.{button, div, p}
+import lustre/event
+
+// MAIN ------------------------------------------------------------------------
+
+pub fn main() {
+ // A `simple` lustre application doesn't produce `Effect`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, view)
+ let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
+}
+
+// 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 view(model: Model) -> Element(Msg) {
+ div(
+ [],
+ [
+ button([event.on_click(Incr)], [text("+")]),
+ button([event.on_click(Decr)], [text("-")]),
+ button([event.on_click(Reset)], [text("Reset")]),
+ p([], [text(int.to_string(model))]),
+ ],
+ )
+}
diff --git a/examples/counter.html b/examples/counter.html
new file mode 100644
index 0000000..2b120fd
--- /dev/null
+++ b/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/examples/index.html b/examples/index.html
new file mode 100644
index 0000000..70d4196
--- /dev/null
+++ b/examples/index.html
@@ -0,0 +1,27 @@
+<!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>
+ <li>
+ <a href="svg.html">svg</a>
+ </li>
+ <li>
+ <a href="components.html">components</a>
+ </li>
+ </menu>
+ </body>
+</html>
diff --git a/examples/input.gleam b/examples/input.gleam
new file mode 100644
index 0000000..ff4d794
--- /dev/null
+++ b/examples/input.gleam
@@ -0,0 +1,132 @@
+// IMPORTS ---------------------------------------------------------------------
+
+import gleam/dynamic
+import gleam/string
+import lustre
+import lustre/attribute.{attribute}
+import lustre/element.{Element, text}
+import lustre/element/html.{div, input, label, pre}
+import lustre/event
+
+// MAIN ------------------------------------------------------------------------
+
+pub fn main() {
+ // A `simple` lustre application doesn't produce `Effect`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, view)
+ let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
+
+ 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 view(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")],
+ [
+ string.inspect(model)
+ |> string.replace("(", "(\n ")
+ |> string.replace(", ", ",\n ")
+ |> string.replace(")", "\n)")
+ |> text,
+ ],
+ ),
+ ]),
+ ],
+ )
+}
+
+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,
+ label_: String,
+) -> Element(Msg) {
+ div(
+ [attribute.class("input")],
+ [
+ label([attribute.for(id)], [text(label_)]),
+ 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")], [text("Remember me")]),
+ ],
+ )
+}
diff --git a/examples/input.html b/examples/input.html
new file mode 100644
index 0000000..3bd6463
--- /dev/null
+++ b/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;
+ }
+
+ .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/examples/nested.gleam b/examples/nested.gleam
new file mode 100644
index 0000000..91c2da4
--- /dev/null
+++ b/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/element/html.{div}
+
+// MAIN ------------------------------------------------------------------------
+
+pub fn main() {
+ // A `simple` lustre application doesn't produce `Effect`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, view)
+ let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
+
+ 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(Nil))
+}
+
+// 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 view(model: Model) -> Element(Msg) {
+ let counters = {
+ use rest, id, counter <- map.fold(model, [])
+ let el = element.map(counter.view(counter), pair.new(id, _))
+
+ [el, ..rest]
+ }
+
+ div([], counters)
+}
diff --git a/examples/nested.html b/examples/nested.html
new file mode 100644
index 0000000..420b159
--- /dev/null
+++ b/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>
diff --git a/examples/svg.gleam b/examples/svg.gleam
new file mode 100644
index 0000000..e895f1a
--- /dev/null
+++ b/examples/svg.gleam
@@ -0,0 +1,107 @@
+// IMPORTS ---------------------------------------------------------------------
+
+import gleam/int
+import lustre
+import lustre/attribute.{attribute}
+import lustre/element.{Element, text}
+import lustre/element/html.{button, div, p, svg}
+import lustre/element/svg.{path}
+import lustre/event
+
+// MAIN ------------------------------------------------------------------------
+
+pub fn main() {
+ // A `simple` lustre application doesn't produce `Effect`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, view)
+ let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
+}
+
+// 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 view(model: Model) -> Element(Msg) {
+ div(
+ [],
+ [
+ button(
+ [event.on_click(Incr)],
+ [plus([attribute.style([#("color", "red")])])],
+ ),
+ button([event.on_click(Decr)], [minus([])]),
+ button([event.on_click(Reset)], [text("Reset")]),
+ p([], [text(int.to_string(model))]),
+ ],
+ )
+}
+
+fn plus(attrs) {
+ svg(
+ [
+ attribute("width", "15"),
+ attribute("height", "15"),
+ attribute("viewBox", "0 0 15 15"),
+ attribute("fill", "none"),
+ ..attrs
+ ],
+ [
+ path([
+ attribute(
+ "d",
+ "M8 2.75C8 2.47386 7.77614 2.25 7.5 2.25C7.22386 2.25 7 2.47386 7 2.75V7H2.75C2.47386 7 2.25 7.22386 2.25 7.5C2.25 7.77614 2.47386 8 2.75 8H7V12.25C7 12.5261 7.22386 12.75 7.5 12.75C7.77614 12.75 8 12.5261 8 12.25V8H12.25C12.5261 8 12.75 7.77614 12.75 7.5C12.75 7.22386 12.5261 7 12.25 7H8V2.75Z",
+ ),
+ attribute("fill", "currentColor"),
+ attribute("fill-rule", "evenodd"),
+ attribute("clip-rule", "evenodd"),
+ ]),
+ ],
+ )
+}
+
+fn minus(attrs) {
+ svg(
+ [
+ attribute("width", "15"),
+ attribute("height", "15"),
+ attribute("viewBox", "0 0 15 15"),
+ attribute("fill", "none"),
+ ..attrs
+ ],
+ [
+ path([
+ attribute(
+ "d",
+ "M2.25 7.5C2.25 7.22386 2.47386 7 2.75 7H12.25C12.5261 7 12.75 7.22386 12.75 7.5C12.75 7.77614 12.5261 8 12.25 8H2.75C2.47386 8 2.25 7.77614 2.25 7.5Z",
+ ),
+ attribute("fill", "currentColor"),
+ attribute("fill-rule", "evenodd"),
+ attribute("clip-rule", "evenodd"),
+ ]),
+ ],
+ )
+}
diff --git a/examples/svg.html b/examples/svg.html
new file mode 100644
index 0000000..12af526
--- /dev/null
+++ b/examples/svg.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 | svg</title>
+
+ <script type="module">
+ import { main } from "../../build/dev/javascript/lustre/examples/svg.mjs";
+
+ document.addEventListener("DOMContentLoaded", main);
+ </script>
+ </head>
+ <body>
+ <div data-lustre-app></div>
+ </body>
+</html>