Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions assets/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@ import type { ViewHook } from '../deps/phoenix_live_view'

declare const createLiveToastHook: () => ViewHook

declare global {
interface Window {
addToast: (kind: string, msg: string, options?: Options) => void
}
}

export { createLiveToastHook }
21 changes: 21 additions & 0 deletions assets/js/live_toast/live_toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,35 @@ async function animateOut(this: ViewHook) {
export function createLiveToastHook(duration = 6000, maxItems = 3) {
return {
destroyed(this: ViewHook) {
if (this.el.dataset.part === 'toast-group') {
return
}

doAnimations.bind(this)(duration, maxItems)
},
updated(this: ViewHook) {
if (this.el.dataset.part === 'toast-group') {
return
}

// animate to targetDestination in 0ms
const keyframes = { y: [this.el.targetDestination] }
animate(this.el, keyframes, { duration: 0 })
},
mounted(this: ViewHook) {
if (this.el.dataset.part === 'toast-group') {
window.addToast = (kind: string, msg: string, options = {}) => {
const payload = {
kind,
msg,
options
}
this.pushEventTo(this.el, 'add_toast', payload)
}

return
}

// for the special flashes, check if they are visible, and if not, return early out of here.
if (['server-error', 'client-error'].includes(this.el.id)) {
if (isHidden(document.getElementById(this.el.id))) {
Expand Down
29 changes: 17 additions & 12 deletions demo/assets/js/app.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
import 'phoenix_html'
import { Socket } from 'phoenix'
import { LiveSocket } from 'phoenix_live_view'
import { createLiveToastHook } from '../../../assets/js/live_toast/live_toast.ts'
import "phoenix_html"
import { Socket } from "phoenix"
import { LiveSocket } from "phoenix_live_view"
import { createLiveToastHook } from "../../../assets/js/live_toast/live_toast.ts"

let csrfToken = document
.querySelector("meta[name='csrf-token']")
.getAttribute('content')
let liveSocket = new LiveSocket('/live', Socket, {
.getAttribute("content")

let liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: { _csrf_token: csrfToken },
hooks: { LiveToast: createLiveToastHook() },
hooks: {
LiveToast: createLiveToastHook()
}
})

console.log(liveSocket)

// connect if there are any LiveViews on the page
liveSocket.connect()

window.addEventListener('phx:close-menu', (e) => {
const menu = document.querySelector('aside#main-navigation')
menu.classList.remove('max-md:block')
window.addEventListener("phx:close-menu", e => {
const menu = document.querySelector("aside#main-navigation")
menu.classList.remove("max-md:block")

const backdrop = document.querySelector('#backdrop')
backdrop.classList.remove('max-md:block')
const backdrop = document.querySelector("#backdrop")
backdrop.classList.remove("max-md:block")
})

// expose liveSocket on window for web console debug logs and latency simulation:
Expand Down
22 changes: 21 additions & 1 deletion lib/live_toast/live_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@ defmodule LiveToast.LiveComponent do
@impl Phoenix.LiveComponent
def render(assigns) do
~H"""
<div id={assigns[:id] || "toast-group"} class={@group_class_fn.(assigns)}>
<div
id={assigns[:id] || "toast-group"}
phx-hook="LiveToast"
data-part="toast-group"
class={@group_class_fn.(assigns)}
>
<div class="contents" id="toast-group-stream" phx-update="stream">
<Components.toast
:for={
Expand Down Expand Up @@ -132,4 +137,19 @@ defmodule LiveToast.LiveComponent do
# have dismissed the toast before the animation ended.
{:noreply, socket}
end

@impl Phoenix.LiveComponent
def handle_event("add_toast", %{"kind" => kind, "msg" => msg} = payload, socket) do
options = map_to_keyword(payload["options"] || %{})

LiveToast.send_toast(kind, msg, options)

{:noreply, socket}
end

defp map_to_keyword(map) when is_map(map) do
map
|> Map.to_list()
|> Enum.map(fn {key, value} -> {String.to_atom("#{key}"), value} end)
end
end
Loading