app: dropdown improvements

This commit is contained in:
Nikos Papadakis 2023-11-25 16:49:51 +02:00
parent c81d112eb8
commit 2771a7bcb2
Signed by untrusted user who does not match committer: nikos
GPG key ID: 78871F9905ADFF02
6 changed files with 141 additions and 116 deletions

View file

@ -31,24 +31,30 @@ defmodule PrymnWeb.Button do
def secondary(assigns), do: button(assign(assigns, :variant, "secondary")) def secondary(assigns), do: button(assign(assigns, :variant, "secondary"))
defp button_assigns(assigns) do @doc """
assign( Render a tertiary variant button.
assigns,
:style,
[
"inline-flex justify-center items-center rounded-2xl shadow transition-colors active:shadow-sm",
by_variant(assigns.variant),
by_size(assigns.size),
assigns[:class]
]
)
end
defp button(%{rest: %{href: _}} = assigns), do: link_button(assigns) ## Examples
defp button(%{rest: %{navigate: _}} = assigns), do: link_button(assigns)
defp button(%{rest: %{patch: _}} = assigns), do: link_button(assigns)
defp button(assigns) do <.tertiary>Click me</.tertiary>
<.tertiary href="/a/path" size="sm">Link button</.tertiary>
"""
def tertiary(assigns), do: button(assign(assigns, :variant, "tertiary"))
@doc """
Render a button.
"""
attr :variant, :string, default: "primary"
attr :size, :string, default: "md", values: ~w(sm md lg)
attr :class, :string, default: nil
attr :rest, :global, include: ~w(href navigate patch)
slot :inner_block, required: true
def button(%{rest: %{href: _}} = assigns), do: link_button(assigns)
def button(%{rest: %{navigate: _}} = assigns), do: link_button(assigns)
def button(%{rest: %{patch: _}} = assigns), do: link_button(assigns)
def button(assigns) do
assigns = button_assigns(assigns) assigns = button_assigns(assigns)
~H""" ~H"""
@ -58,6 +64,19 @@ defmodule PrymnWeb.Button do
""" """
end end
defp button_assigns(assigns) do
assign(
assigns,
:style,
[
"inline-flex justify-center items-center rounded-2xl transition-colors",
by_variant(assigns.variant),
by_size(assigns.size),
assigns[:class]
]
)
end
defp link_button(assigns) do defp link_button(assigns) do
assigns = button_assigns(assigns) assigns = button_assigns(assigns)
@ -73,9 +92,14 @@ defmodule PrymnWeb.Button do
defp by_size("lg"), do: "text-lg px-7 py-3" defp by_size("lg"), do: "text-lg px-7 py-3"
defp by_variant("primary"), defp by_variant("primary"),
do: "font-medium bg-black text-white hover:bg-slate-800 active:text-white/80" do:
"font-medium bg-black text-white shadow active:shadow-sm hover:bg-slate-800 active:text-white/80"
defp by_variant("secondary"), defp by_variant("secondary"),
do: do:
"font-medium bg-transparent border border-slate-800 text-slate-800 hover:bg-slate-800 hover:text-white active:text-white/80" "font-medium bg-transparent border shadow active:shadow-sm border-slate-800 text-slate-800 hover:bg-slate-800 hover:text-white active:text-white/80"
defp by_variant("tertiary"),
do:
"font-medium border-none text-black hover:bg-slate-50 focus:bg-slate-50 active:text-black/80"
end end

View file

@ -14,7 +14,7 @@ defmodule PrymnWeb.CoreComponents do
Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage. Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage.
""" """
use Phoenix.Component use Phoenix.Component, global_prefixes: ~w(x-)
alias Phoenix.LiveView.JS alias Phoenix.LiveView.JS
import PrymnWeb.Gettext import PrymnWeb.Gettext
@ -594,10 +594,21 @@ defmodule PrymnWeb.CoreComponents do
attr :title, :string, default: nil attr :title, :string, default: nil
attr :position, :string, default: "left", values: ~w(left right) attr :position, :string, default: "left", values: ~w(left right)
slot :button slot :button do
attr :title, :string
attr :variant, :string, values: ~w(primary secondary tertiary)
attr :size, :string, values: ~w(sm md lg)
end
slot :item slot :item
def dropdown(assigns) do def dropdown(assigns) do
assigns =
assign(assigns, :style, [
(assigns.position == "left" && "left-0") || "right-0",
"absolute z-10 w-56 rounded-xl bg-white shadow-lg ring-1 ring-black ring-opacity-5"
])
~H""" ~H"""
<div <div
class="relative inline-block" class="relative inline-block"
@ -608,36 +619,41 @@ defmodule PrymnWeb.CoreComponents do
focusAfter && focusAfter.focus() focusAfter && focusAfter.focus()
} }
}" }"
x-id="['dropdown-button']"
x-on:keydown.escape.prevent.stop="close($refs.button)" x-on:keydown.escape.prevent.stop="close($refs.button)"
> >
<button <%= for button <- @button do %>
title={@title} <PrymnWeb.Button.button
type="button" title={button[:title]}
x-ref="button" variant={button[:variant] || "primary"}
x-bind:aria-expanded="open" size={button[:size] || "md"}
x-on:click="open = !open" type="button"
> x-ref="button"
<%= render_slot(@button) %> x-bind:aria-expanded="open"
<.icon class="ml-2 h-4 w-4" name="hero-chevron-down" /> x-on:click="open = !open"
</button> >
<%= render_slot(@button) %>
<.icon class="ml-2 h-4 w-4" name="hero-chevron-down" />
</PrymnWeb.Button.button>
<% end %>
<div <div
class="absolute left-0 z-10 w-56 rounded-xl bg-white shadow-lg ring-1 ring-black ring-opacity-5" class={@style}
role="menu" role="menu"
aria-orientation="vertical" aria-orientation="vertical"
tabindex="-1" tabindex="-1"
x-show="open" x-show="open"
x-on:click.outside="close($refs.button)" x-on:click.outside="close($refs.button)"
x-transition.origin.top.left {if assigns.position == "left", do: ["x-transition.origin.top.left": true], else: []}
{if assigns.position == "right", do: ["x-transition.origin.top.right": true], else: []}
> >
<div <.link
:for={item <- @item} :for={item <- @item}
class="my-2 px-4 py-2 hover:bg-slate-100" {item}
role="none" class="my-2 block px-4 py-2 hover:bg-slate-100"
role="menuitem"
x-on:click="close()" x-on:click="close()"
> >
<%= render_slot(item, item) %> <%= render_slot(item) %>
</div> </.link>
</div> </div>
</div> </div>
""" """

View file

@ -16,16 +16,12 @@
</p> </p>
</div> </div>
<div class="flex items-center text-zinc-900"> <div class="flex items-center text-zinc-900">
<.dropdown> <.dropdown position="right">
<:button> <:button variant="tertiary">
<.icon name="hero-user-solid" /> <.icon name="hero-user-solid" />
</:button> </:button>
<:item> <:item href={~p"/users/settings"}>Settings</:item>
<.link href={~p"/users/settings"}>Settings</.link> <:item method="DELETE" href={~p"/auth/log_out"}>Log out</:item>
</:item>
<:item>
<.link method="DELETE" href={~p"/auth/log_out"}>Log out</.link>
</:item>
</.dropdown> </.dropdown>
</div> </div>
</div> </div>

View file

@ -26,6 +26,50 @@ defmodule PrymnWeb.ServerLive.Index do
|> assign(:healths, healths)} |> assign(:healths, healths)}
end end
@impl true
def render(assigns) do
~H"""
<div class="mx-auto max-w-2xl">
<.header>
Your servers
<small class="block">
<%= "#{Enum.count(@servers)} servers" %>
</small>
<:actions>
<Button.primary patch={~p"/servers/new"}>Connect a Server</Button.primary>
</:actions>
</.header>
<div class="space-y-5" phx-update="replace" id="servers">
<.link
:for={server <- @servers}
navigate={~p"/servers/#{server}"}
class="group block rounded-lg bg-gray-100 p-5 shadow-sm shadow-gray-300 hover:bg-black hover:text-white"
>
<div class="flex flex-row flex-wrap justify-between">
<h2 class="text-xl"><%= server.name %></h2>
<.server_status status={server.status} health={@healths[server.public_ip]} />
</div>
<div class="flex flex-row flex-wrap justify-between lg:text-sm">
<span>IP: <%= server.public_ip || "N/A" %></span>
<span
:if={@healths[server.public_ip] && Enum.count(@healths[server.public_ip].tasks)}
class="text-right text-xs text-slate-700"
>
<%= for {name, task} <- Enum.take(@healths[server.public_ip].tasks, 1) do %>
<div>In progress: <%= name %></div>
<div><%= task.progress %></div>
<% end %>
</span>
</div>
</.link>
</div>
<.modal :if={@live_action == :new} id="server-modal" show on_cancel={JS.patch(~p"/servers")}>
<.live_component module={PrymnWeb.ServerLive.NewServer} id={:new} patch={~p"/servers"} />
</.modal>
</div>
"""
end
@impl true @impl true
def handle_params(_params, _url, socket) do def handle_params(_params, _url, socket) do
socket = socket =
@ -63,7 +107,7 @@ defmodule PrymnWeb.ServerLive.Index do
{:registered, nil} -> {:registered, nil} ->
~H""" ~H"""
<.spinner class="w-5" /> <.spinner size="md" />
""" """
{:registered, %Agents.Health{status: :connected}} -> {:registered, %Agents.Health{status: :connected}} ->

View file

@ -1,40 +0,0 @@
<div class="mx-auto max-w-2xl">
<.header>
Your servers
<small class="block">
<%= "#{Enum.count(@servers)} servers" %>
</small>
<:actions>
<Button.primary patch={~p"/servers/new"}>Connect a Server</Button.primary>
</:actions>
</.header>
<div class="space-y-5" phx-update="replace" id="servers">
<.link
:for={server <- @servers}
navigate={~p"/servers/#{server}"}
class="group block rounded-lg bg-gray-100 p-5 shadow-sm shadow-gray-300 hover:bg-black hover:text-white"
>
<div class="flex flex-row flex-wrap justify-between">
<h2 class="text-xl"><%= server.name %></h2>
<.server_status status={server.status} health={@healths[server.public_ip]} />
</div>
<div class="flex flex-row flex-wrap justify-between lg:text-sm">
<span>IP: <%= server.public_ip || "N/A" %></span>
<span
:if={@healths[server.public_ip] && Enum.count(@healths[server.public_ip].tasks)}
class="text-right text-xs text-slate-700"
>
<%= for {name, task} <- Enum.take(@healths[server.public_ip].tasks, 1) do %>
<div>In progress: <%= name %></div>
<div><%= task.progress %></div>
<% end %>
</span>
</div>
</.link>
</div>
<.modal :if={@live_action == :new} id="server-modal" show on_cancel={JS.patch(~p"/servers")}>
<.live_component module={PrymnWeb.ServerLive.NewServer} id={:new} patch={~p"/servers"} />
</.modal>
</div>

View file

@ -15,51 +15,36 @@ defmodule PrymnWeb.ServerLive.Show do
def render(assigns) do def render(assigns) do
~H""" ~H"""
<div class="mx-auto max-w-2xl"> <div class="mx-auto max-w-2xl">
<div class="flex justify-between"> <div class="flex items-center justify-between">
<div id="server-name" class="relative flex items-center"> <div id="server-name" class="relative flex items-center">
<.dropdown title="Select a different server"> <.dropdown title="Select a different server">
<:button>Server <%= @server.name %></:button> <:button variant="tertiary">Server <%= @server.name %></:button>
<:item :for={server <- Enum.filter(@servers, fn s -> s.id != @server.id end)}> <:item
<.link :for={server <- Enum.filter(@servers, fn s -> s.id != @server.id end)}
patch={~p"/servers/#{server}"} patch={~p"/servers/#{server}"}
class="block text-sm text-gray-700" >
role="menuitem" <%= server.name %>
>
<%= server.name %>
</.link>
</:item> </:item>
</.dropdown> </.dropdown>
<button <Button.tertiary title="Edit server name" phx-click={show_edit_server_name()}>
class="ml-4 inline-flex items-center"
title="Edit server name"
phx-click={show_edit_server_name()}
>
<.icon class="h-4 w-4" name="hero-pencil-solid" /> <.icon class="h-4 w-4" name="hero-pencil-solid" />
</button> </Button.tertiary>
<.indicator message={@health.message} /> <.indicator message={@health.message} />
</div> </div>
<form class="hidden" id="server-name-edit" phx-submit={submit_edit_server_name()}> <form class="hidden" id="server-name-edit" phx-submit={submit_edit_server_name()}>
<input <input class="rounded-xl" type="text" name="name" value={@server.name} required />
class="border-0 border-b border-gray-500" <Button.tertiary type="submit" title="Confirm">
type="text"
name="name"
value={@server.name}
required
/>
<button type="submit" title="Confirm">
<.icon name="hero-check" /> <.icon name="hero-check" />
</button> </Button.tertiary>
</form> </form>
<div class="space-x-2"> <div class="space-x-2">
<Button.primary size="sm">New App</Button.primary>
<.dropdown> <.dropdown>
<:button> <:button variant="tertiary" size="sm">Quick actions</:button>
Quick actions
</:button>
<:item> <:item>
Test Test
</:item> </:item>
</.dropdown> </.dropdown>
<Button.primary size="sm">New App</Button.primary>
</div> </div>
</div> </div>
<span class="text-sm opacity-75"><%= @server.public_ip %></span> <span class="text-sm opacity-75"><%= @server.public_ip %></span>