Skip to content

Commit

Permalink
A reusable Cancel/Ok dialog box (#12)
Browse files Browse the repository at this point in the history
* A reusable Cancel/Ok dialog box
  • Loading branch information
jeremyowensboggs authored Sep 13, 2021
1 parent 72e1583 commit 247d171
Show file tree
Hide file tree
Showing 10 changed files with 265 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ erl_crash.dump
# Ignore package tarball (built via "mix hex.build").
crunch_berry-*.tar

priv/plts/dialyzer.plt
priv/plts/dialyzer.plt.hash
3 changes: 3 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Config

import_config "#{Mix.env()}.exs"
1 change: 1 addition & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import Config
1 change: 1 addition & 0 deletions config/prod.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import Config
8 changes: 8 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Config

# Because we don't care, and just want to supress the message in test
config :phoenix, :json_library, Jason

config :crunch_berry, CrunchBerry.TestEndpoint,
secret_key_base: "oc2RUnRoYkZlR0dM7JwlpbM9AsauRm0R1n+sUP71YsY9eflg4uWyeVFXHrAzDXL7",
live_view: [signing_salt: "FqRkKpuzaujIZQGN"]
97 changes: 97 additions & 0 deletions lib/components/confirm_modal.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
defmodule CrunchBerry.Components.ConfirmModal do
@moduledoc """
# ConfirmModal
Reusable Cancel/Ok dialog box. ConfirmModal is a stateless component
that takes a title and a message to display, and then sends a
`confirm-ok` or `confirm-cancel` event to the hosting live view based on
user selection.
Required assigns
* `title:` The title of the dialog box
* `message:` The message to display. Should describe an action that the user will confirm or cancel on
Optional assigns
* `call_to_action:` a prompt for action, defaults to "Are you sure you wish to proceed?"
* `event_prefix:` a string to prefix the "ok" and "cancel" events, defaults to 'confirm', so the
default events are "confirm-ok" and "confirm-cancel", but `:event_prefix` was set to "ask", then the events
would be "ask-ok" and "ask-cancel"
## example:
In the markup
```
...
<%= if @reupload do %>
<%= live_component CrunchBerry.Components.ConfirmModal,
title: "Re-upload CSV",
message: "This will clear the current session, and replace it with a new csv file." %>
<% end %>
...
```
In the live view:
```
def handle_event("confirm-ok", _, socket) do
... do stuff, and don't say we didn't warn you
{:noreply, assign(socket, reupload: false)}
end
def handle_event("confirm-cancel", _, socket) do
{:noreply, assign(socket, reupload: false)}
end
```
"""
use Phoenix.HTML
use Phoenix.LiveComponent

@impl Phoenix.LiveComponent
def update(assigns, socket) do
event_prefix = Map.get(assigns, :event_prefix, "confirm")

socket =
assign(socket,
title: assigns.title,
message: assigns.message,
call_to_action: Map.get(assigns, :call_to_action, "Are you sure you wish to proceed?"),
cancel_event: "#{event_prefix}-cancel",
ok_event: "#{event_prefix}-ok"
)

{:ok, socket}
end

@impl Phoenix.LiveComponent
def render(assigns) do
~H"""
<div
class="fixed inset-0 w-full h-full z-20 bg-black bg-opacity-50 overflow-y-auto flex items-center backdrop-filter backdrop-blur-sm"
tabindex="-1"
role="dialog"
aria-modal="true"
phx-capture-click="close"
phx-window-keydown="close"
phx-key="escape"
phx-page-loading>
<div class="relative mx-auto my-10 opacity-100 w-11/12 md:max-w-md rounded overflow-y-auto">
<div class="relative bg-white shadow-lg rounded-md text-gray-900 z-20 flow-root">
<div>
<button phx-click={@cancel_event} aria_hidden="true" class="text-gray-400 text-2xl absolute top-0 right-0 py-1 px-3 rounded-full cursor-pointer hover:no-underline hover:text-black duration-50" title: "Close">&times;</button>
</div>
<div class="w-full max-w-lg p-2">
<h2 class="font-bold block w-full text-center text-grey-darkest mb-2 pt-4"><%= @title %></h2>
<div class="text-center">
<%= @message %>
</div>
<div class="text-center">
<%= @call_to_action %>
</div>
<div class="text-right p-2">
<button phx-click={@cancel_event} class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded">Cancel</button>
<button phx-click={@ok_event} class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded">Ok</button>
</div>
</div>
</div>
</div>
</div>
"""
end
end
10 changes: 9 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ defmodule CrunchBerry.MixProject do
app: :crunch_berry,
version: "0.2.0",
elixir: "~> 1.10",
elixirc_paths: elixirc_paths(Mix.env()),
config_path: "./config/config.exs",
start_permanent: Mix.env() == :prod,
deps: deps(),
dialyzer: [
Expand All @@ -25,14 +27,20 @@ defmodule CrunchBerry.MixProject do
]
end

# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]

# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:phoenix_live_view, ">= 0.16.0"},
{:phoenix_html, ">= 3.0.0"},
{:credo, "~> 1.5", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.0", only: [:dev], runtime: false},
{:ex_doc, "~> 0.24", only: :dev, runtime: false}
{:ex_doc, "~> 0.24", only: :dev, runtime: false},
{:jason, "~> 1.2", only: [:dev, :test]},
{:floki, ">= 0.30.0", only: :test}
]
end
end
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ex_doc": {:hex, :ex_doc, "0.24.2", "e4c26603830c1a2286dae45f4412a4d1980e1e89dc779fcd0181ed1d5a05c8d9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e134e1d9e821b8d9e4244687fb2ace58d479b67b282de5158333b0d57c6fb7da"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"floki": {:hex, :floki, "0.31.0", "f05ee8a8e6a3ced4e62beeb2c79a63bc8e12ab98fbaaf6e6a3d9b76b1278e23f", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "b05afa372f5c345a5bf240ac25ea1f0f3d5fcfd7490ac0beeb4a203f9444891e"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"},
Expand Down
136 changes: 136 additions & 0 deletions test/components/confirm_modal_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
defmodule Optimizer.Api.UserLive.UploadSalesCSV.ConfirmModalTest do
use ExUnit.Case
import Phoenix.ConnTest
alias CrunchBerry.Components.ConfirmModal
import Phoenix.LiveViewTest

@endpoint CrunchBerry.TestEndpoint

setup do
conn = build_conn()
socket = %Phoenix.LiveView.Socket{}
start_supervised!(CrunchBerry.TestEndpoint)
{:ok, socket: socket, conn: conn}
end

describe "update" do
test "defaults are set correctly", %{socket: socket} do
requireds = %{
title: "This is the title",
message: "This event will happen"
}

assert {:ok, %{assigns: assigns}} = ConfirmModal.update(requireds, socket)

assert %{
call_to_action: "Are you sure you wish to proceed?",
cancel_event: "confirm-cancel",
message: "This event will happen",
ok_event: "confirm-ok",
title: "This is the title"
} = assigns
end

test "can override event_prefix", %{socket: socket} do
params = %{
title: "This is the title",
message: "This event will happen",
event_prefix: "ask",
call_to_action: "Do it?"
}

assert {:ok, %{assigns: assigns}} = ConfirmModal.update(params, socket)

assert %{
call_to_action: "Do it?",
cancel_event: "ask-cancel",
message: "This event will happen",
ok_event: "ask-ok",
title: "This is the title"
} = assigns
end
end

describe "render" do
test "defaults render correctly", %{socket: socket} do
requireds = %{
title: "This is the title",
message: "This event will happen"
}

{:ok, %{assigns: assigns}} = ConfirmModal.update(requireds, socket)

result = render_component(ConfirmModal, assigns)

assert result =~ "This is the title"
assert result =~ "This event will happen"
assert result =~ "phx-click=\"confirm-ok\""
assert result =~ "phx-click=\"confirm-cancel\""
end

test "overrides render correctly" do
requireds = %{
title: "This is the title",
message: "This event will happen",
event_prefix: "ask",
call_to_action: "Do it?"
}

result = render_component(ConfirmModal, requireds)

assert result =~ "This is the title"
assert result =~ "This event will happen"
assert result =~ "phx-click=\"ask-ok\""
assert result =~ "phx-click=\"ask-cancel\""
end
end

defmodule LiveViewFixture do
use Phoenix.LiveView

def mount(_params, _session, socket) do
{:ok, assign(socket, message: "None")}
end

def render(assigns) do
~H"""
<%= live_component CrunchBerry.Components.ConfirmModal,
title: "Re-upload CSV",
message: "This will clear the current session, and replace it with a new csv file." %>
<div>
click: <%= @message %>
</div>
"""
end

def handle_event(event, _params, socket) do
{:noreply, assign(socket, message: event)}
end
end

describe "events" do
test "ok click", %{conn: conn} do
{:ok, view, html} = live_isolated(conn, LiveViewFixture)
assert html =~ "click: None"

click_html =
view
|> element("button", "Ok")
|> render_click()

assert click_html =~ "click: confirm-ok"
end

test "cancel click, %", %{conn: conn} do
{:ok, view, html} = live_isolated(conn, LiveViewFixture)
assert html =~ "click: None"

click_html =
view
|> element("button", "Cancel")
|> render_click()

assert click_html =~ "click: confirm-cancel"
end
end
end
6 changes: 6 additions & 0 deletions test/support/test_endpoint.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
defmodule CrunchBerry.TestEndpoint do
@moduledoc """
Fake endpoint for the purposes of running tests
"""
use Phoenix.Endpoint, otp_app: :crunch_berry
end

0 comments on commit 247d171

Please sign in to comment.