mirror of
https://github.com/plausible/analytics.git
synced 2024-12-23 09:33:19 +03:00
Add creatable option to ComboBox (#3169)
* Add creatable option to ComboBox This commit changes the ComboBox component to allow a `creatable` option. This option enables users to create new options along with choosing existing options. * Test ComboBox class parameter * Use display_value instead of input * Change scroll block to nearest to prevent glitches
This commit is contained in:
parent
9ed79542f2
commit
16846b16c8
@ -16,7 +16,7 @@ let suggestionsDropdown = function(id) {
|
||||
},
|
||||
scrollTo(idx) {
|
||||
this.$refs[`dropdown-${this.id}-option-${idx}`]?.scrollIntoView(
|
||||
{ block: 'center', behavior: 'smooth', inline: 'start' }
|
||||
{ block: 'nearest', behavior: 'smooth', inline: 'start' }
|
||||
)
|
||||
},
|
||||
focusNext() {
|
||||
|
@ -48,12 +48,15 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
|
||||
attr(:submit_value, :string, default: "")
|
||||
attr(:suggest_mod, :atom, required: true)
|
||||
attr(:suggestions_limit, :integer)
|
||||
attr(:class, :string, default: "")
|
||||
attr(:required, :boolean, default: false)
|
||||
attr(:creatable, :boolean, default: false)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div
|
||||
id={"input-picker-main-#{@id}"}
|
||||
class="mb-3"
|
||||
class={@class}
|
||||
x-data={"window.suggestionsDropdown('#{@id}')"}
|
||||
x-on:keydown.arrow-up="focusPrev"
|
||||
x-on:keydown.arrow-down="focusNext"
|
||||
@ -77,6 +80,7 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
|
||||
value={@display_value}
|
||||
class="border-none py-1 px-1 p-0 w-full inline-block rounded-md focus:outline-none focus:ring-0 text-sm"
|
||||
style="background-color: inherit;"
|
||||
required={@required}
|
||||
/>
|
||||
|
||||
<.dropdown_anchor id={@id} />
|
||||
@ -91,7 +95,14 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<.dropdown ref={@id} suggest_mod={@suggest_mod} suggestions={@suggestions} target={@myself} />
|
||||
<.dropdown
|
||||
ref={@id}
|
||||
suggest_mod={@suggest_mod}
|
||||
suggestions={@suggestions}
|
||||
target={@myself}
|
||||
creatable={@creatable}
|
||||
display_value={@display_value}
|
||||
/>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
@ -123,6 +134,8 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
|
||||
attr(:suggestions, :list, default: [])
|
||||
attr(:suggest_mod, :atom, required: true)
|
||||
attr(:target, :any)
|
||||
attr(:creatable, :boolean, required: true)
|
||||
attr(:display_value, :string, required: true)
|
||||
|
||||
def dropdown(assigns) do
|
||||
~H"""
|
||||
@ -149,8 +162,18 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
|
||||
ref={@ref}
|
||||
/>
|
||||
|
||||
<.option
|
||||
:if={@creatable && String.length(@display_value) > 0}
|
||||
idx={length(@suggestions)}
|
||||
submit_value={@display_value}
|
||||
display_value={@display_value}
|
||||
target={@target}
|
||||
ref={@ref}
|
||||
creatable
|
||||
/>
|
||||
|
||||
<div
|
||||
:if={@suggestions == []}
|
||||
:if={@suggestions == [] && !@creatable}
|
||||
class="relative cursor-default select-none py-2 px-4 text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
No matches found. Try searching for something different.
|
||||
@ -160,10 +183,11 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
|
||||
end
|
||||
|
||||
attr(:display_value, :string, required: true)
|
||||
attr(:submit_value, :integer, required: true)
|
||||
attr(:submit_value, :string, required: true)
|
||||
attr(:ref, :string, required: true)
|
||||
attr(:target, :any)
|
||||
attr(:idx, :integer, required: true)
|
||||
attr(:creatable, :boolean, default: false)
|
||||
|
||||
def option(assigns) do
|
||||
assigns = assign(assigns, :suggestions_limit, suggestions_limit(assigns))
|
||||
@ -183,7 +207,11 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
|
||||
class="block py-2 px-3"
|
||||
>
|
||||
<span class="block truncate">
|
||||
<%= @display_value %>
|
||||
<%= if @creatable do %>
|
||||
Create "<%= @display_value %>"
|
||||
<% else %>
|
||||
<%= @display_value %>
|
||||
<% end %>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
@ -217,6 +245,13 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
|
||||
input = params[target]
|
||||
input_len = input |> String.trim() |> String.length()
|
||||
|
||||
socket =
|
||||
if socket.assigns[:creatable] do
|
||||
assign(socket, display_value: input, submit_value: input)
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
||||
if input_len > 0 do
|
||||
suggestions =
|
||||
input
|
||||
|
@ -64,7 +64,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
|
||||
class: "mt-6 block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
|
||||
) %>
|
||||
|
||||
<div :for={step_idx <- @step_ids} class="flex">
|
||||
<div :for={step_idx <- @step_ids} class="flex mb-3">
|
||||
<div class="w-2/5 flex-1">
|
||||
<.live_component
|
||||
submit_name="funnel[steps][][goal_id]"
|
||||
|
@ -105,6 +105,34 @@ defmodule PlausibleWeb.Live.Components.ComboBoxTest do
|
||||
assert text_of_element(doc, "#dropdown-test-component") ==
|
||||
"No matches found. Try searching for something different."
|
||||
end
|
||||
|
||||
test "dropdown suggests user input when creatable" do
|
||||
doc =
|
||||
render_sample_component([{"USD", "US Dollar"}, {"EUR", "Euro"}],
|
||||
creatable: true,
|
||||
display_value: "Brazilian Real"
|
||||
)
|
||||
|
||||
assert text_of_element(doc, "#dropdown-test-component-option-0") == "US Dollar"
|
||||
assert text_of_element(doc, "#dropdown-test-component-option-1") == "Euro"
|
||||
|
||||
assert text_of_element(doc, "#dropdown-test-component-option-2") ==
|
||||
~s(Create "Brazilian Real")
|
||||
|
||||
refute text_of_element(doc, "#dropdown-test-component") ==
|
||||
"No matches found. Try searching for something different."
|
||||
end
|
||||
|
||||
test "makes the html input required when required option is passed" do
|
||||
input_query = "input[type=text][required]"
|
||||
assert render_sample_component([], required: true) |> element_exists?(input_query)
|
||||
refute render_sample_component([]) |> element_exists?(input_query)
|
||||
end
|
||||
|
||||
test "adds class to html element when class option is passed" do
|
||||
assert render_sample_component([], class: "animate-spin")
|
||||
|> element_exists?("#input-picker-main-test-component.animate-spin")
|
||||
end
|
||||
end
|
||||
|
||||
describe "integration" do
|
||||
@ -165,6 +193,71 @@ defmodule PlausibleWeb.Live.Components.ComboBoxTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "creatable integration" do
|
||||
defmodule CreatableView do
|
||||
use Phoenix.LiveView
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<.live_component
|
||||
submit_name="some_submit_name"
|
||||
module={PlausibleWeb.Live.Components.ComboBox}
|
||||
suggest_mod={ComboBox.StaticSearch}
|
||||
id="test-creatable-component"
|
||||
options={for i <- 1..20, do: {i, "Option #{i}"}}
|
||||
creatable
|
||||
/>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
test "stores selected value from suggestion", %{conn: conn} do
|
||||
{:ok, lv, _html} = live_isolated(conn, CreatableView, session: %{})
|
||||
type_into_combo(lv, "test-creatable-component", "option 20")
|
||||
|
||||
doc =
|
||||
lv
|
||||
|> element("li#dropdown-test-creatable-component-option-0 a")
|
||||
|> render_click()
|
||||
|
||||
assert element_exists?(doc, "input[type=hidden][name=some_submit_name][value=20]")
|
||||
end
|
||||
|
||||
test "suggests creating custom value", %{conn: conn} do
|
||||
{:ok, lv, _html} = live_isolated(conn, CreatableView, session: %{})
|
||||
|
||||
assert lv
|
||||
|> type_into_combo("test-creatable-component", "my new option")
|
||||
|> text_of_element("li#dropdown-test-creatable-component-option-0 a") ==
|
||||
~s(Create "my new option")
|
||||
end
|
||||
|
||||
test "stores new value by clicking on the dropdown custom option", %{conn: conn} do
|
||||
{:ok, lv, _html} = live_isolated(conn, CreatableView, session: %{})
|
||||
type_into_combo(lv, "test-creatable-component", "my new option")
|
||||
|
||||
doc =
|
||||
lv
|
||||
|> element("li#dropdown-test-creatable-component-option-0 a")
|
||||
|> render_click()
|
||||
|
||||
assert element_exists?(
|
||||
doc,
|
||||
"input[type=hidden][name=some_submit_name][value=\"my new option\"]"
|
||||
)
|
||||
end
|
||||
|
||||
test "stores new value while typing", %{conn: conn} do
|
||||
{:ok, lv, _html} = live_isolated(conn, CreatableView, session: %{})
|
||||
|
||||
assert lv
|
||||
|> type_into_combo("test-creatable-component", "my new option")
|
||||
|> element_exists?(
|
||||
"input[type=hidden][name=some_submit_name][value=\"my new option\"]"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp render_sample_component(options, extra_opts \\ []) do
|
||||
render_component(
|
||||
ComboBox,
|
||||
|
Loading…
Reference in New Issue
Block a user