fix(ux): Align filters horizontally on md breakpoints and higher (#5265)

Fixes #5231 
Fixes #5232 


<img width="636" alt="Screenshot 2024-06-05 at 7 26 19 PM"
src="https://github.com/firezone/firezone/assets/167144/8e40ba37-9757-4f83-98ad-24c61efbad36">
<img width="1792" alt="Screenshot 2024-06-05 at 7 26 11 PM"
src="https://github.com/firezone/firezone/assets/167144/eca5084b-ce35-4df6-bb30-474811944ea2">
<img width="720" alt="Screenshot 2024-06-05 at 7 26 03 PM"
src="https://github.com/firezone/firezone/assets/167144/c3eccdba-b3c0-467a-91c0-5197e2a74ed6">

<img width="1791" alt="Screenshot 2024-06-05 at 7 32 16 PM"
src="https://github.com/firezone/firezone/assets/167144/64d417e3-cf74-4f20-9cf5-22b7c0cd620c">
<img width="748" alt="Screenshot 2024-06-05 at 7 32 07 PM"
src="https://github.com/firezone/firezone/assets/167144/11cd2f3a-f8ee-4098-bad9-ab21fd6c000c">
<img width="1792" alt="Screenshot 2024-06-05 at 7 31 50 PM"
src="https://github.com/firezone/firezone/assets/167144/c601eec9-956b-4229-a1c4-484c4bca5001">
<img width="1792" alt="Screenshot 2024-06-05 at 7 31 48 PM"
src="https://github.com/firezone/firezone/assets/167144/2bd2c61a-e39b-4215-8e76-b7b3835dd5aa">
<img width="1792" alt="Screenshot 2024-06-05 at 7 31 43 PM"
src="https://github.com/firezone/firezone/assets/167144/c06d431d-37c1-4ca1-8ab2-67a879cf609b">
This commit is contained in:
Jamil
2024-06-06 13:01:45 -07:00
committed by GitHub
parent 55276ea768
commit ba6685e2f2
6 changed files with 220 additions and 254 deletions

View File

@@ -332,7 +332,7 @@ defmodule Web.CoreComponents do
def label(assigns) do
~H"""
<label for={@for} class={["block text-sm text-neutral-900", @class]}>
<label for={@for} class={["block text-sm text-neutral-900 mb-2", @class]}>
<%= render_slot(@inner_block) %>
</label>
"""
@@ -406,10 +406,10 @@ defmodule Web.CoreComponents do
def list(assigns) do
~H"""
<div class="mt-14">
<dl class="-my-4 divide-y divide-zinc-100">
<dl class="-my-4 divide-y divide-neutral-100">
<div :for={item <- @item} class="flex gap-4 py-4 text-sm leading-6 sm:gap-8">
<dt class="w-1/4 flex-none text-zinc-500"><%= item.title %></dt>
<dd class="text-zinc-700"><%= render_slot(item) %></dd>
<dt class="w-1/4 flex-none text-neutral-500"><%= item.title %></dt>
<dd class="text-neutral-700"><%= render_slot(item) %></dd>
</div>
</dl>
</div>

View File

@@ -32,9 +32,8 @@ defmodule Web.FormComponents do
attr :type, :string,
default: "text",
values:
~w(checkbox color date datetime-local email file hidden month number password
range radio readonly search group_select select tel text textarea taglist time url week)
values: ~w(checkbox color date datetime-local email file hidden month number password
range radio readonly search group_select select tel text textarea time url week)
attr :field, Phoenix.HTML.FormField,
doc: "a form field struct retrieved from the form, for example: @form[:email]"
@@ -116,7 +115,7 @@ defmodule Web.FormComponents do
~H"""
<div phx-feedback-for={@name}>
<label class="flex items-center gap-4 text-sm leading-6 text-zinc-600">
<label class="flex items-center gap-4 text-sm leading-6 text-neutral-600">
<input
type="checkbox"
id={@id}
@@ -143,7 +142,7 @@ defmodule Web.FormComponents do
def input(%{type: "group_select"} = assigns) do
~H"""
<div phx-feedback-for={@name}>
<.label for={@id}><%= @label %></.label>
<.label :if={@label} for={@id}><%= @label %></.label>
<input
:if={not is_nil(@value) and not is_nil(@rest[:disabled])}
type="hidden"
@@ -221,7 +220,7 @@ defmodule Web.FormComponents do
id={@id}
name={@name}
class={[
"mt-2 block w-full rounded sm:text-sm sm:leading-6",
"block w-full rounded sm:text-sm sm:leading-6",
"bg-neutral-50",
"border border-neutral-300 text-neutral-900 rounded",
"phx-no-feedback:border-neutral-300",
@@ -237,54 +236,6 @@ defmodule Web.FormComponents do
"""
end
def input(%{type: "taglist"} = assigns) do
values =
if is_nil(assigns.value),
do: [],
else: Enum.map(assigns.value, &Phoenix.HTML.Form.normalize_value("text", &1))
assigns = assign(assigns, :values, values)
~H"""
<div phx-feedback-for={@name}>
<.label for={@id}><%= @label %></.label>
<div :for={{value, index} <- Enum.with_index(@values)} class="flex mt-2">
<input
type="text"
name={"#{@name}[]"}
id={@id}
value={value}
class={[
"bg-neutral-50 p-2.5 block w-full rounded border text-neutral-900 text-sm",
"phx-no-feedback:border-neutral-300",
"disabled:bg-neutral-50 disabled:text-neutral-500 disabled:border-neutral-200 disabled:shadow-none",
"border-neutral-300",
@errors != [] && "border-rose-400"
]}
{@rest}
/>
<.button
type="button"
phx-click={"delete:#{@name}"}
phx-value-index={index}
class="align-middle ml-2 inline-block whitespace-nowrap"
>
<.icon name="hero-minus" /> Delete
</.button>
</div>
<.button type="button" phx-click={"add:#{@name}"} class="mt-2">
<.icon name="hero-plus" /> Add
</.button>
<.error :for={msg <- @errors} inline={@inline_errors} data-validation-error-for={@name}>
<%= msg %>
</.error>
</div>
"""
end
def input(%{type: "hidden"} = assigns) do
~H"""
<input

View File

@@ -312,7 +312,7 @@ defmodule Web.NavigationComponents do
<div class="mt-16">
<.link
navigate={@navigate}
class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
class="text-sm font-semibold leading-6 text-neutral-900 hover:text-neutral-700"
>
<.icon name="hero-arrow-left-solid" class="h-3 w-3" />
<%= render_slot(@inner_block) %>

View File

@@ -111,8 +111,7 @@ defmodule Web.Groups.Show do
}
kind={:info}
>
<p>This group is managed by Firezone and cannot be edited.</p>
<p>It will contain all actors with at least one authentication identity.</p>
<p>This Group contains all Users and cannot be edited.</p>
</.flash>
<.flash :if={Actors.group_synced?(@group)} kind={:info}>
This group is synced from an external source and cannot be edited.

View File

@@ -40,7 +40,7 @@ defmodule Web.Resources.New do
<label for="resource_type" class="block mb-2 text-sm text-neutral-900">
Type
</label>
<div class="flex text-sm leading-6 text-zinc-600">
<div class="flex text-sm leading-6 text-neutral-600">
<div class="flex items-center me-4">
<.input
id="resource-type--dns"

View File

@@ -89,30 +89,6 @@ defmodule Web.LiveTable do
Map.take(filter.params, keys) != %{}
end
defp resource_filter(assigns) do
~H"""
<.form
:if={@filters != []}
id={"#{@live_table_id}-filters"}
for={@form}
phx-change="filter"
phx-debounce="100"
onkeydown="return event.key != 'Enter';"
>
<.input type="hidden" name="table_id" value={@live_table_id} />
<div
:for={filter <- @filters}
class="flex flex-col md:flex-row items-center justify-between space-y-3 md:space-y-0 md:space-x-4 pb-4"
>
<div class="w-full xl:w-1/2">
<.filter live_table_id={@live_table_id} form={@form} filter={filter} />
</div>
</div>
</.form>
"""
end
def datetime_input(assigns) do
~H"""
<div phx-feedback-for={@field.name} class={["flex items-center"]}>
@@ -168,6 +144,30 @@ defmodule Web.LiveTable do
defp normalize_value(_, nil),
do: nil
defp resource_filter(assigns) do
~H"""
<.form
:if={@filters != []}
id={"#{@live_table_id}-filters"}
for={@form}
phx-change="filter"
phx-debounce="100"
onkeydown="return event.key != 'Enter';"
>
<.input type="hidden" name="table_id" value={@live_table_id} />
<div class="mb-2 space-y-2 md:space-y-0 md:gap-2 md:flex md:justify-end">
<.filter
:for={filter <- @filters}
live_table_id={@live_table_id}
form={@form}
filter={filter}
/>
</div>
</.form>
"""
end
defp filter(%{filter: %{type: {:range, :datetime}}} = assigns) do
~H"""
<div class="flex items-center">
@@ -190,137 +190,147 @@ defmodule Web.LiveTable do
defp filter(%{filter: %{type: {:string, :websearch}}} = assigns) do
~H"""
<div class={["relative w-full"]} phx-feedback-for={@form[@filter.name].name}>
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<.icon name="hero-magnifying-glass" class="w-5 h-5 text-neutral-500" />
</div>
<div class="flex items-center order-last">
<div class="relative w-full" phx-feedback-for={@form[@filter.name].name}>
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<.icon name="hero-magnifying-glass" class="w-5 h-5 text-neutral-500" />
</div>
<input
type="text"
name={@form[@filter.name].name}
id={@form[@filter.name].id}
value={Phoenix.HTML.Form.normalize_value("text", @form[@filter.name].value)}
placeholder={"Search by " <> @filter.title}
class={[
"bg-neutral-50 border border-neutral-300 text-neutral-900 text-sm rounded",
"block w-full pl-10 p-2",
"phx-no-feedback:border-neutral-300",
"disabled:bg-neutral-50 disabled:text-neutral-500 disabled:border-neutral-200 disabled:shadow-none",
"focus:outline-none focus:border-1 focus:ring-0",
"border-neutral-300",
@form[@filter.name].errors != [] && "border-rose-400"
]}
/>
<.error
:for={msg <- @form[@filter.name].errors}
data-validation-error-for={@form[@filter.name].name}
>
<%= msg %>
</.error>
<input
type="text"
name={@form[@filter.name].name}
id={@form[@filter.name].id}
value={Phoenix.HTML.Form.normalize_value("text", @form[@filter.name].value)}
placeholder={"Search by " <> @filter.title}
class={[
"bg-neutral-50 border border-neutral-300 text-neutral-900 text-sm rounded",
"block w-full pl-10 p-2",
"phx-no-feedback:border-neutral-300",
"disabled:bg-neutral-50 disabled:text-neutral-500 disabled:border-neutral-200 disabled:shadow-none",
"focus:outline-none focus:border-1 focus:ring-0",
"border-neutral-300",
@form[@filter.name].errors != [] && "border-rose-400"
]}
/>
<.error
:for={msg <- @form[@filter.name].errors}
data-validation-error-for={@form[@filter.name].name}
>
<%= msg %>
</.error>
</div>
</div>
"""
end
defp filter(%{filter: %{type: {:string, :email}}} = assigns) do
~H"""
<div class={["relative w-full"]} phx-feedback-for={@form[@filter.name].name}>
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<.icon name="hero-magnifying-glass" class="w-5 h-5 text-neutral-500" />
</div>
<div class="flex items-center order-last">
<div class="relative w-full" phx-feedback-for={@form[@filter.name].name}>
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<.icon name="hero-magnifying-glass" class="w-5 h-5 text-neutral-500" />
</div>
<input
type="text"
name={@form[@filter.name].name}
id={@form[@filter.name].id}
value={Phoenix.HTML.Form.normalize_value("text", @form[@filter.name].value)}
placeholder={"Search by " <> @filter.title}
class={[
"bg-neutral-50 border border-neutral-300 text-neutral-900 text-sm rounded",
"block w-full pl-10 p-2",
"phx-no-feedback:border-neutral-300",
"disabled:bg-neutral-50 disabled:text-neutral-500 disabled:border-neutral-200 disabled:shadow-none",
"focus:outline-none focus:border-1 focus:ring-0",
"border-neutral-300",
@form[@filter.name].errors != [] && "border-rose-400"
]}
/>
<.error
:for={msg <- @form[@filter.name].errors}
data-validation-error-for={@form[@filter.name].name}
>
<%= msg %>
</.error>
<input
type="text"
name={@form[@filter.name].name}
id={@form[@filter.name].id}
value={Phoenix.HTML.Form.normalize_value("text", @form[@filter.name].value)}
placeholder={"Search by " <> @filter.title}
class={[
"bg-neutral-50 border border-neutral-300 text-neutral-900 text-sm rounded",
"block w-full pl-10 p-2",
"phx-no-feedback:border-neutral-300",
"disabled:bg-neutral-50 disabled:text-neutral-500 disabled:border-neutral-200 disabled:shadow-none",
"focus:outline-none focus:border-1 focus:ring-0",
"border-neutral-300",
@form[@filter.name].errors != [] && "border-rose-400"
]}
/>
<.error
:for={msg <- @form[@filter.name].errors}
data-validation-error-for={@form[@filter.name].name}
>
<%= msg %>
</.error>
</div>
</div>
"""
end
defp filter(%{filter: %{type: {:string, :uuid}}} = assigns) do
~H"""
<.input
type="group_select"
field={@form[@filter.name]}
options={
[
{nil, [{"For any " <> @filter.title, nil}]}
] ++ @filter.values
}
/>
<div class="flex items-center order-4">
<div class="w-full">
<.input
type="group_select"
field={@form[@filter.name]}
options={
[
{nil, [{"For any " <> @filter.title, nil}]}
] ++ @filter.values
}
/>
</div>
</div>
"""
end
defp filter(%{filter: %{type: :string, values: values}} = assigns)
when 0 < length(values) and length(values) < 5 do
~H"""
<div class="inline-flex rounded-md shadow-sm" role="group">
<.intersperse_blocks>
<:item>
<label
for={"#{@live_table_id}-#{@filter.name}-__all__"}
class={[
"px-4 py-2 text-sm border-neutral-200 text-neutral-900",
"hover:bg-neutral-200 hover:text-neutral-700",
"cursor-pointer",
"border-y border-l rounded-l",
is_nil(@form[@filter.name].value) && "bg-neutral-100"
]}
>
<.input
id={"#{@live_table_id}-#{@filter.name}-__all__"}
type="radio"
field={@form[@filter.name]}
name={"_reset:" <> @form[@filter.name].name}
value="true"
checked={is_nil(@form[@filter.name].value)}
class="hidden"
/> All
</label>
</:item>
<div class="flex items-center order-first">
<div class="flex rounded" role="group">
<.intersperse_blocks>
<:item>
<label
for={"#{@live_table_id}-#{@filter.name}-__all__"}
class={[
"px-4 py-2 text-sm border-neutral-200 text-neutral-900",
"hover:bg-neutral-200 hover:text-neutral-700",
"cursor-pointer",
"border-y border-l rounded-l",
is_nil(@form[@filter.name].value) && "bg-neutral-100"
]}
>
<.input
id={"#{@live_table_id}-#{@filter.name}-__all__"}
type="radio"
field={@form[@filter.name]}
name={"_reset:" <> @form[@filter.name].name}
value="true"
checked={is_nil(@form[@filter.name].value)}
class="hidden"
/> All
</label>
</:item>
<:item :let={position} :for={{label, value} <- @filter.values}>
<label
for={"#{@live_table_id}-#{@filter.name}-#{value}"}
class={[
"px-4 py-2 text-sm border-neutral-200 text-neutral-900",
"hover:bg-neutral-200 hover:text-neutral-700",
"cursor-pointer",
@form[@filter.name].value == value && "bg-neutral-100",
position == :first && "border-y border-l rounded-l",
position == :last && "border-y border-r rounded-r",
position != :first && position != :last && "border"
]}
>
<.input
id={"#{@live_table_id}-#{@filter.name}-#{value}"}
type="radio"
field={@form[@filter.name]}
value={value}
checked={@form[@filter.name].value == value}
class="hidden"
/>
<%= label %>
</label>
</:item>
</.intersperse_blocks>
<:item :let={position} :for={{label, value} <- @filter.values}>
<label
for={"#{@live_table_id}-#{@filter.name}-#{value}"}
class={[
"px-4 py-2 text-sm border-neutral-200 text-neutral-900",
"hover:bg-neutral-200 hover:text-neutral-700",
"cursor-pointer",
@form[@filter.name].value == value && "bg-neutral-100",
position == :first && "border-y border-l rounded-l",
position == :last && "border-y border-r rounded-r",
position != :first && position != :last && "border"
]}
>
<.input
id={"#{@live_table_id}-#{@filter.name}-#{value}"}
type="radio"
field={@form[@filter.name]}
value={value}
checked={@form[@filter.name].value == value}
class="hidden"
/>
<%= label %>
</label>
</:item>
</.intersperse_blocks>
</div>
</div>
"""
end
@@ -328,71 +338,77 @@ defmodule Web.LiveTable do
defp filter(%{filter: %{type: {:list, :string}, values: values}} = assigns)
when 0 < length(values) and length(values) < 5 do
~H"""
<div class="inline-flex rounded-md shadow-sm" role="group">
<.intersperse_blocks>
<:item>
<label
for={"#{@live_table_id}-#{@filter.name}-__all__"}
class={[
"px-4 py-2 text-sm border-neutral-200 text-neutral-900",
"hover:bg-neutral-200 hover:text-neutral-700",
"cursor-pointer",
"border-y border-l rounded-l",
is_nil(@form[@filter.name].value) && "bg-neutral-100"
]}
>
<.input
id={"#{@live_table_id}-#{@filter.name}-__all__"}
type="checkbox"
field={@form[@filter.name]}
name={"_reset:" <> @form[@filter.name].name}
value={true}
checked={is_nil(@form[@filter.name].value)}
class="hidden"
/> All
</label>
</:item>
<div class="flex items-center order-first">
<div class="flex rounded w-full" role="group">
<.intersperse_blocks>
<:item>
<label
for={"#{@live_table_id}-#{@filter.name}-__all__"}
class={[
"px-4 py-2 text-sm border-neutral-200 text-neutral-900",
"hover:bg-neutral-200 hover:text-neutral-700",
"cursor-pointer",
"border-y border-l rounded-l",
is_nil(@form[@filter.name].value) && "bg-neutral-100"
]}
>
<.input
id={"#{@live_table_id}-#{@filter.name}-__all__"}
type="checkbox"
field={@form[@filter.name]}
name={"_reset:" <> @form[@filter.name].name}
value={true}
checked={is_nil(@form[@filter.name].value)}
class="hidden"
/> All
</label>
</:item>
<:item :let={position} :for={{label, value} <- @filter.values}>
<label
for={"#{@live_table_id}-#{@filter.name}-#{value}"}
class={[
"px-4 py-2 text-sm border-neutral-200 text-neutral-900",
"hover:bg-neutral-200 hover:text-neutral-700",
"cursor-pointer",
@form[@filter.name].value && value in @form[@filter.name].value && "bg-neutral-100",
position == :first && "border-y border-l rounded-l",
position == :last && "border-y border-r rounded-r",
position != :first && position != :last && "border"
]}
>
<.input
id={"#{@live_table_id}-#{@filter.name}-#{value}"}
type="checkbox"
field={@form[@filter.name]}
name={@form[@filter.name].name <> "[]"}
value={value}
checked={@form[@filter.name].value && value in @form[@filter.name].value}
class="hidden"
/>
<%= label %>
</label>
</:item>
</.intersperse_blocks>
<:item :let={position} :for={{label, value} <- @filter.values}>
<label
for={"#{@live_table_id}-#{@filter.name}-#{value}"}
class={[
"px-4 py-2 text-sm border-neutral-200 text-neutral-900",
"hover:bg-neutral-200 hover:text-neutral-700",
"cursor-pointer",
@form[@filter.name].value && value in @form[@filter.name].value && "bg-neutral-100",
position == :first && "border-y border-l rounded-l",
position == :last && "border-y border-r rounded-r",
position != :first && position != :last && "border"
]}
>
<.input
id={"#{@live_table_id}-#{@filter.name}-#{value}"}
type="checkbox"
field={@form[@filter.name]}
name={@form[@filter.name].name <> "[]"}
value={value}
checked={@form[@filter.name].value && value in @form[@filter.name].value}
class="hidden"
/>
<%= label %>
</label>
</:item>
</.intersperse_blocks>
</div>
</div>
"""
end
defp filter(%{filter: %{type: :string, values: values}} = assigns) when length(values) > 0 do
~H"""
<.input
type="group_select"
field={@form[@filter.name]}
options={[
{nil, [{"For any " <> @filter.title, nil}]},
{@filter.title, @filter.values}
]}
/>
<div class="flex items-center order-4">
<div class="w-full">
<.input
type="group_select"
field={@form[@filter.name]}
options={[
{nil, [{"For any " <> @filter.title, nil}]},
{@filter.title, @filter.values}
]}
/>
</div>
</div>
"""
end