diff --git a/CHANGELOG.md b/CHANGELOG.md index c3eb0ec..80f7563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. Note that the versions prior to `0.6.0` are very unstable. +## [v0.10.0] (unreleased) + +Note: this release includes breaking changes involving translation. The library no longer calls `gettext` functions on +dynamic strings passed to `put_toast` and `send_toast`. + +### Changed + +- [Translation behavior](https://github.com/srcrip/live_toast/pull/78): translation of LiveToast's built-in connection notices is now opt-in; configure `gettext_backend` to enable it. `send_toast/3`, `put_toast/4`, and custom connection-notification copy now render the supplied message and title unchanged, so translate application-owned copy before passing it to LiveToast. +- [TypeScript compatibility](https://github.com/srcrip/live_toast/pull/77): LiveToast no longer augments the global `HTMLElement` interface, and the package now ships and exposes its TypeScript declarations. + ## [v0.1.0] (2024-03-06) 🚀 Initial release. diff --git a/README.md b/README.md index cbba910..ff8ead0 100644 --- a/README.md +++ b/README.md @@ -316,9 +316,9 @@ classes; use a custom component function for the icon, title, body, action, and ### Default custom toast component -Pass `toast_component_fn` to `LiveToast.toast_group` when all programmatic toasts in that host should use the same -custom component. A `component:` passed to an individual `send_toast/3` call takes precedence. This host default does -not change Phoenix flash or connection-notice rendering. +Pass `toast_component_fn` to `LiveToast.toast_group` when programmatic toasts and Phoenix flashes in that host should +use the same custom component. A `component:` passed to an individual `send_toast/3` call takes precedence for that +toast. Connection notices continue to use their default content or the corresponding slot. ```heex ``` +`flash_group_id` optionally adds an addressable container around only the Phoenix flashes. Programmatic toasts remain +outside that container. + ```elixir LiveToast.send_toast(:info, "The message was sent.", title: "Sent") ``` diff --git a/demo/lib/demo_web/components/layouts.ex b/demo/lib/demo_web/components/layouts.ex index 27a66b5..1070eba 100644 --- a/demo/lib/demo_web/components/layouts.ex +++ b/demo/lib/demo_web/components/layouts.ex @@ -35,6 +35,19 @@ defmodule DemoWeb.Layouts do ] end + attr(:kind, :atom, required: true) + attr(:title, :string, default: nil) + attr(:body, :any, required: true) + + def demo_toast_component_fn(assigns) do + ~H""" +
+

{@title}

+

{@body}

+
+ """ + end + attr(:notice, :map, required: true) def connection_notice(assigns) do diff --git a/demo/lib/demo_web/components/layouts/app.html.heex b/demo/lib/demo_web/components/layouts/app.html.heex index e69fe7d..c93675f 100644 --- a/demo/lib/demo_web/components/layouts/app.html.heex +++ b/demo/lib/demo_web/components/layouts/app.html.heex @@ -2,6 +2,9 @@ toast_component_fn on toast_group - to use it for every programmatic toast in that host. + to use it for every programmatic toast and Phoenix flash in that host. +

+
<LiveToast.toast_group
+  flash={@flash}
+  connected={assigns[:socket] != nil}
+  toasts_sync={assigns[:toasts_sync]}
+  toast_component_fn={&notification/1}
+/>
+

+ <.link + class="text-blue-700 hover:text-blue-500 underline" + href="https://github.com/srcrip/live_toast/blob/main/demo/lib/demo_web/components/layouts.ex" + > + View the demo component +

diff --git a/demo/test/demo_web/live/send_toast_test.exs b/demo/test/demo_web/live/send_toast_test.exs index ff1d8ad..f7c91f3 100644 --- a/demo/test/demo_web/live/send_toast_test.exs +++ b/demo/test/demo_web/live/send_toast_test.exs @@ -156,6 +156,31 @@ defmodule DemoWeb.SendToastTest do end end + defmodule FlashComponentLive do + @moduledoc false + + use Phoenix.LiveView + + def mount(_params, _session, socket), do: {:ok, socket} + + def handle_event("show_flash", _params, socket) do + {:noreply, Phoenix.LiveView.put_flash(socket, :info, "Rendered from a live flash.")} + end + + def render(assigns) do + ~H""" + + + """ + end + end + defmodule ClientToastLive do @moduledoc false @@ -262,6 +287,24 @@ defmodule DemoWeb.SendToastTest do """ end + defp dead_flash_component_host(assigns) do + ~H""" + + """ + end + + def flash_component(assigns) do + ~H""" +

{@body}

+ """ + end + describe "LiveToast.send_toast/3" do test "renders info toast", %{conn: conn} do {:ok, view, _html} = live_isolated(conn, TestLive) @@ -344,6 +387,33 @@ defmodule DemoWeb.SendToastTest do end end + describe "Phoenix flashes" do + test "uses the host component in a live host", %{conn: conn} do + {:ok, view, html} = live_isolated(conn, FlashComponentLive) + + assert html =~ ~s(id="live-flashes") + refute html =~ ~s(data-flash-renderer="custom") + + view + |> element("button", "Show Flash") + |> render_click() + + assert render(view) =~ "Rendered from a live flash." + assert has_element?(view, "#live-flashes [data-kind=info]") + end + + test "uses the host component in a dead host" do + html = + render_component(&dead_flash_component_host/1, + flash: %{"error" => "Rendered from a dead flash."} + ) + + assert html =~ ~s(id="dead-flashes") + assert html =~ ~s(data-flash-renderer="custom") + assert html =~ "Rendered from a dead flash." + end + end + describe "client toast API" do test "renders a client toast with supported options", %{conn: conn} do {:ok, view, _html} = live_isolated(conn, ClientToastLive) diff --git a/lib/live_toast.ex b/lib/live_toast.ex index 054befc..3fb62ff 100644 --- a/lib/live_toast.ex +++ b/lib/live_toast.ex @@ -298,7 +298,12 @@ defmodule LiveToast do attr(:toast_component_fn, :any, default: nil, - doc: "default component function used to render programmatic toasts" + doc: "default component function used to render programmatic toasts and Phoenix flashes" + ) + + attr(:flash_group_id, :string, + default: nil, + doc: "optional id for a container around Phoenix flashes" ) attr :toasts_sync, :list, required: true, doc: "toasts that get synchronized when calling `put_toast`" @@ -332,6 +337,7 @@ defmodule LiveToast do client_error_delay={@client_error_delay} toast_class_fn={@toast_class_fn} toast_component_fn={@toast_component_fn} + flash_group_id={@flash_group_id} group_class_fn={@group_class_fn} connection_notifications={@connection_notifications} client_error={@client_error} @@ -345,7 +351,9 @@ defmodule LiveToast do corner={@corner} flash_duration={@flash_duration} toast_class_fn={@toast_class_fn} + toast_component_fn={@toast_component_fn} group_class_fn={@group_class_fn} + flash_group_id={@flash_group_id} client_error_delay={@client_error_delay} connection_notifications={@connection_notifications} client_error={@client_error} diff --git a/lib/live_toast/components.ex b/lib/live_toast/components.ex index b50dfc0..fb9e18c 100644 --- a/lib/live_toast/components.ex +++ b/lib/live_toast/components.ex @@ -133,6 +133,13 @@ defmodule LiveToast.Components do doc: "function to override the toast classes" ) + attr(:toast_component_fn, :any, + default: nil, + doc: "component function used to render Phoenix flash content" + ) + + attr(:id, :string, default: nil, doc: "optional id for the flash-only container") + attr(:connection_notifications, :any, default: %{}, doc: "false disables connection-state notifications; a map overrides their copy and kind" @@ -150,18 +157,10 @@ defmodule LiveToast.Components do assigns = assign(assigns, :connection_notifications, connection_notifications(assigns.connection_notifications)) ~H""" - <.toast - :for={level <- @kinds} - data-component="flash" - corner={@corner} - toast_class_fn={@toast_class_fn} - flash_duration={@flash_duration} - duration={0} - kind={level} - title={String.capitalize(to_string(level))} - phx-update="ignore" - flash={@f} - /> +
+ <.flash_toasts {assigns} /> +
+ <.flash_toasts :if={!@id} {assigns} /> <%= if @connection_notifications do %> <.toast data-component="flash" @@ -224,6 +223,23 @@ defmodule LiveToast.Components do """ end + defp flash_toasts(assigns) do + ~H""" + <.toast + :for={level <- @kinds} + data-component="flash" + corner={@corner} + toast_class_fn={@toast_class_fn} + component={@toast_component_fn} + flash_duration={@flash_duration} + duration={0} + kind={level} + title={String.capitalize(to_string(level))} + flash={@f} + /> + """ + end + defp duration_value(:infinity), do: "Infinity" defp duration_value(duration), do: duration @@ -247,6 +263,13 @@ defmodule LiveToast.Components do doc: "function to override the toast classes" ) + attr(:toast_component_fn, :any, + default: nil, + doc: "component function used to render Phoenix flash content" + ) + + attr(:flash_group_id, :string, default: nil, doc: "optional id for the flash-only container") + attr(:client_error_delay, :integer, default: 3000, doc: "adds a delay before the disconnected client error is shown") attr(:flash_duration, :integer, default: 0, doc: "if provided clears flash after provided milliseconds") @@ -269,6 +292,8 @@ defmodule LiveToast.Components do corner={@corner} flash_duration={@flash_duration} toast_class_fn={@toast_class_fn} + toast_component_fn={@toast_component_fn} + id={@flash_group_id} client_error_delay={@client_error_delay} connection_notifications={@connection_notifications} client_error={@client_error} diff --git a/lib/live_toast/live_component.ex b/lib/live_toast/live_component.ex index 5f9e52c..171f12c 100644 --- a/lib/live_toast/live_component.ex +++ b/lib/live_toast/live_component.ex @@ -141,6 +141,8 @@ defmodule LiveToast.LiveComponent do corner={@corner} flash_duration={@flash_duration} toast_class_fn={@toast_class_fn} + toast_component_fn={@toast_component_fn} + id={@flash_group_id} client_error_delay={@client_error_delay} connection_notifications={@connection_notifications} client_error={@client_error}