app: alpine.js + dropdown

This commit is contained in:
Nikos Papadakis 2023-11-24 00:56:38 +02:00
parent c7b9be3ee4
commit efd5f1d126
Signed by untrusted user who does not match committer: nikos
GPG key ID: 78871F9905ADFF02
8 changed files with 192 additions and 64 deletions

View file

@ -2,6 +2,10 @@ root = true
charset = utf-8
end_of_line = lf
[*.js]
indent_style = space
indent_size = 2
[*.nix]
indent_style = space
indent_size = 2

View file

@ -15,15 +15,26 @@
// import "some-package"
//
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"
import Alpine from "alpinejs"
Alpine.start()
window.Alpine = Alpine
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
let liveSocket = new LiveSocket("/live", Socket, {
params: {_csrf_token: csrfToken},
dom: {
onBeforeElUpdated(from, to) {
if (from._x_dataStack) {
window.Alpine.clone(from, to)
}
}
}
})
// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})

33
app/assets/package-lock.json generated Normal file
View file

@ -0,0 +1,33 @@
{
"name": "assets",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"alpinejs": "^3.13.3"
}
},
"node_modules/@vue/reactivity": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
"integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
"dependencies": {
"@vue/shared": "3.1.5"
}
},
"node_modules/@vue/shared": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
},
"node_modules/alpinejs": {
"version": "3.13.3",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.3.tgz",
"integrity": "sha512-WZ6WQjkAOl+WdW/jukzNHq9zHFDNKmkk/x6WF7WdyNDD6woinrfXCVsZXm0galjbco+pEpYmJLtwlZwcOfIVdg==",
"dependencies": {
"@vue/reactivity": "~3.1.1"
}
}
}
}

5
app/assets/package.json Normal file
View file

@ -0,0 +1,5 @@
{
"dependencies": {
"alpinejs": "^3.13.3"
}
}

View file

@ -36,7 +36,7 @@ defmodule PrymnWeb.Button do
assigns,
:style,
[
"inline-flex justify-center items-end rounded-2xl shadow transition-colors active:shadow-sm",
"inline-flex justify-center items-center rounded-2xl shadow transition-colors active:shadow-sm",
by_variant(assigns.variant),
by_size(assigns.size),
assigns[:class]

View file

@ -587,6 +587,62 @@ defmodule PrymnWeb.CoreComponents do
"""
end
@doc """
A simple dropdown menu
"""
attr :title, :string, default: nil
attr :position, :string, default: "left", values: ~w(left right)
slot :button
slot :item
def dropdown(assigns) do
~H"""
<div
class="relative inline-block"
x-data="{
open: false,
close(focusAfter) {
this.open = false
focusAfter && focusAfter.focus()
}
}"
x-id="['dropdown-button']"
x-on:keydown.escape.prevent.stop="close($refs.button)"
>
<button
title={@title}
type="button"
x-ref="button"
x-bind:aria-expanded="open"
x-on:click="open = !open"
>
<%= render_slot(@button) %>
<.icon class="ml-2 h-4 w-4" name="hero-chevron-down" />
</button>
<div
class="absolute left-0 z-10 w-56 rounded-xl bg-white shadow-lg ring-1 ring-black ring-opacity-5"
role="menu"
aria-orientation="vertical"
tabindex="-1"
x-show="open"
x-on:click.outside="close($refs.button)"
x-transition.origin.top.left
>
<div
:for={item <- @item}
class="my-2 px-4 py-2 hover:bg-slate-100"
role="none"
x-on:click="close()"
>
<%= render_slot(item, item) %>
</div>
</div>
</div>
"""
end
## JS Commands
def show(js \\ %JS{}, selector) do

View file

@ -16,18 +16,17 @@
</p>
</div>
<div class="flex items-center text-zinc-900">
<.link class="border-r border-l px-3 py-4 hover:bg-white" href={~p"/users/settings"}>
<.icon name="hero-user" />
<.icon name="hero-chevron-down" />
</.link>
<.link
title="Log out"
class="border-r px-3 py-4 hover:bg-white"
method="DELETE"
href={~p"/auth/log_out"}
>
<.icon name="hero-x-mark" />
</.link>
<.dropdown>
<:button>
<.icon name="hero-user-solid" />
</:button>
<:item>
<.link href={~p"/users/settings"}>Settings</.link>
</:item>
<:item>
<.link method="DELETE" href={~p"/auth/log_out"}>Log out</.link>
</:item>
</.dropdown>
</div>
</div>
</header>

View file

@ -1,5 +1,4 @@
defmodule PrymnWeb.ServerLive.Show do
alias DBConnection.App
use PrymnWeb, :live_view
require Logger
@ -7,44 +6,38 @@ defmodule PrymnWeb.ServerLive.Show do
@impl true
def mount(_params, _session, socket) do
{:ok, socket}
# TODO: A more lightweight call instead of listing all data?
servers = Servers.list_servers()
{:ok, assign(socket, :servers, servers)}
end
@impl true
def render(assigns) do
~H"""
<div class="mx-auto max-w-2xl">
<.header>
<span id="server-name" class="relative flex items-center">
<button title="Select a different server">
Server <%= @server.name %>
<.icon name="hero-chevron-down" />
</button>
<button class="ml-4" title="Edit server name" phx-click={show_edit_server_name()}>
<.icon class="h-3 w-3" name="hero-pencil" />
</button>
<span
role="tooltip"
class={[
"absolute -left-6 inline-flex h-3 w-3 before:-translate-x-1/2 before:-translate-y-full",
"before:-top-2 before:left-1/2 before:absolute before:text-sm before:text-white",
"before:font-normal before:content-[attr(data-tip)] before:opacity-0",
"hover:before:opacity-100 before:py-1 before:px-2 before:bg-black",
"before:rounded before:pointer-events-none before:transition-opacity"
]}
data-tip={@health.message}
<div class="flex justify-between">
<div id="server-name" class="relative flex items-center">
<.dropdown title="Select a different server">
<:button>Server <%= @server.name %></:button>
<:item :for={server <- Enum.filter(@servers, fn s -> s.id != @server.id end)}>
<.link
patch={~p"/servers/#{server}"}
class="block text-sm text-gray-700"
role="menuitem"
>
<%= server.name %>
</.link>
</:item>
</.dropdown>
<button
class="ml-4 inline-flex items-center"
title="Edit server name"
phx-click={show_edit_server_name()}
>
<%= case @health.message do %>
<% "Connected" -> %>
<span class="absolute top-0 left-0 h-full w-full animate-ping rounded-full bg-green-400 opacity-75" />
<span class="h-3 w-3 rounded-full bg-green-500" />
<% "Disconnected" -> %>
<span class="h-3 w-3 rounded-full bg-red-500" />
<% _ -> %>
<span class="h-3 w-3 rounded-full bg-yellow-500" />
<% end %>
</span>
</span>
<.icon class="h-4 w-4" name="hero-pencil-solid" />
</button>
<.indicator message={@health.message} />
</div>
<form class="hidden" id="server-name-edit" phx-submit={submit_edit_server_name()}>
<input
class="border-0 border-b border-gray-500"
@ -53,25 +46,26 @@ defmodule PrymnWeb.ServerLive.Show do
value={@server.name}
required
/>
<button type="submit" title="Confirm" class="ml-4">
<.icon class="h-4 w-4" name="hero-check" />
<button type="submit" title="Confirm">
<.icon name="hero-check" />
</button>
</form>
<:subtitle>
<%= @server.public_ip %>
</:subtitle>
<:actions>
<div class="space-x-2">
<Button.primary size="sm">New App</Button.primary>
<Button.secondary size="sm">
Quick actions <.icon name="hero-chevron-down" class="ml-1" />
</Button.secondary>
</:actions>
</.header>
<div class="my-3 text-sm text-slate-700">
<%= for {name, task} <- @health.tasks do %>
Background task in progress: <%= name %>
<p><%= task.progress %> complete</p>
<% end %>
<.dropdown>
<:button>
Quick actions
</:button>
<:item>
Test
</:item>
</.dropdown>
</div>
</div>
<span class="text-sm opacity-75"><%= @server.public_ip %></span>
<div :for={{name, task} <- @health.tasks} class="my-3 text-sm text-slate-700">
<p>Background task in progress: <%= name %></p>
<p><%= task.progress %> complete</p>
</div>
<div :if={@server.status == :unregistered} class="my-10">
<p class="mb-9">
@ -229,4 +223,30 @@ defmodule PrymnWeb.ServerLive.Show do
|> JS.hide()
|> JS.show(to: "#server-name", display: "flex")
end
defp indicator(assigns) do
~H"""
<span
role="tooltip"
class={[
"absolute -left-6 inline-flex h-3 w-3 before:-translate-x-1/2 before:-translate-y-full",
"before:-top-2 before:left-1/2 before:absolute before:text-sm before:text-white",
"before:font-normal before:content-[attr(data-tip)] before:opacity-0",
"hover:before:opacity-100 before:py-1 before:px-2 before:bg-black",
"before:rounded before:pointer-events-none before:transition-opacity"
]}
data-tip={@message}
>
<%= case @message do %>
<% "Connected" -> %>
<span class="absolute top-0 left-0 h-full w-full animate-ping rounded-full bg-green-400 opacity-75" />
<span class="h-3 w-3 rounded-full bg-green-500" />
<% "Disconnected" -> %>
<span class="h-3 w-3 rounded-full bg-red-500" />
<% _ -> %>
<span class="h-3 w-3 rounded-full bg-yellow-500" />
<% end %>
</span>
"""
end
end