Created
January 27, 2026 23:17
-
-
Save laszlokorte/67b0c4a81eb105d27da50d2c03f72786 to your computer and use it in GitHub Desktop.
Phoenix Single file app
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Mix.install([ | |
| {:phoenix_playground, "~> 0.1.8"} | |
| ]) | |
| defmodule SpinnerComponent do | |
| use Phoenix.LiveComponent | |
| def mount(socket) do | |
| {:ok, assign(socket, count: 0)} | |
| end | |
| defp classes(classes \\ []) do | |
| classes | |
| |> Enum.filter(&elem(&1, 1)) | |
| |> Enum.map(&elem(&1, 0)) | |
| |> Enum.join(" ") | |
| end | |
| def render(assigns) do | |
| ~H""" | |
| <span class={classes(spinner: true, positive: @count > 0, negative: @count < 0)}> | |
| <button phx-click="remove-element" value={"#{ @id }"}>×</button> | |
| <button phx-click="dec" phx-target={"#{ @myself }"}>-</button> | |
| <output class="spinner-value"><%= @count %></output> | |
| <button phx-click="inc" phx-target={"#{ @myself }"}>+</button> | |
| </span> | |
| """ | |
| end | |
| def handle_event("inc", _params, socket) do | |
| {:noreply, update(socket, :count, &(&1 + 1))} | |
| end | |
| def handle_event("dec", _params, socket) do | |
| {:noreply, update(socket, :count, &(&1 - 1))} | |
| end | |
| end | |
| defmodule DemoLive do | |
| use Phoenix.LiveView | |
| def mount(_params, _session, socket) do | |
| {:ok, assign(socket, spinners: [])} | |
| end | |
| def render(assigns) do | |
| ~H""" | |
| <h1>Phoenix Spinner List Demo</h1> | |
| <div class="list"> | |
| <.live_component :for={s <- @spinners} module={SpinnerComponent} id={"spinner-#{s}"} /> | |
| <button phx-click="add-spinner">Add spinner</button> | |
| </div> | |
| <style type="text/css"> | |
| body { | |
| padding: 1em; | |
| margin: 0; | |
| font-family: monospace; | |
| } | |
| h1 { | |
| margin: 0 0 1em; | |
| } | |
| button { | |
| font: inherit; | |
| padding: 0.5ex 1ex; | |
| vertical-align: center; | |
| border: none; | |
| display: flex inline; | |
| align-items: center; | |
| background-color: #111; | |
| color: #fff; | |
| cursor: pointer; | |
| text-align: center; | |
| justify-content: center; | |
| } | |
| button:hover { | |
| background-color: #1f1f1f; | |
| } | |
| button:active { | |
| background-color: #000; | |
| } | |
| .spinner { | |
| display: flex inline; | |
| gap: 1ex; | |
| align-items: baseline; | |
| justify-content: space-between; | |
| padding: 4px; | |
| background: #ddd; | |
| } | |
| .spinner.positive { | |
| background-color: #cfc; | |
| } | |
| .spinner.negative { | |
| background-color: #fcc; | |
| } | |
| .spinner-value { | |
| flex: 2em 1 0; | |
| text-align: center; | |
| } | |
| .list { | |
| display: flex; | |
| width: max-content; | |
| flex-direction: column-reverse; | |
| gap: 2px; | |
| } | |
| .list > :last-child { | |
| position: sticky; | |
| padding: 2ex 2em; | |
| top: 1em | |
| } | |
| </style> | |
| """ | |
| end | |
| def handle_event("add-spinner", _params, socket) do | |
| {:noreply, update(socket, :spinners, &[Integer.to_string(Enum.count(&1)) | &1])} | |
| end | |
| def handle_event("remove-element", %{"value" => "spinner-" <> id}, socket) do | |
| {:noreply, update(socket, :spinners, &Enum.filter(&1, fn x -> x != id end))} | |
| end | |
| end | |
| PhoenixPlayground.start(live: DemoLive) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment