Skip to content

Instantly share code, notes, and snippets.

@greenspace10
Created June 14, 2021 17:16
Show Gist options
  • Save greenspace10/98190ce1031f9c344310b4f40ad94039 to your computer and use it in GitHub Desktop.
Save greenspace10/98190ce1031f9c344310b4f40ad94039 to your computer and use it in GitHub Desktop.
Laravel, Livewire, Alpine JS Toast Notifications
  1. Create a componet notification.blade.php

  2. Add to your layout

    <x-notification />

  3. Add Macro to your AppServiceProvider.php

  4. Call Macro $this->notify('Some Message', 'Yeah Baby!', 'success');

Options:

- $this->notify('You saved something.');  //Default Success Toast NO TITLE
- $this->notify('You saved something.', 'Success!');  //Default Success Toast WITH TITLE

- $this->notify('You have amessage', 'Message', '');  //Message Toast WITH TITLE

- $this->notify('You saved something.', 'Yay You!', 'success');  	//success Toast WITH TITLE
- $this->notify('Oh No', 'There was a problem', 'error');  		//Error Toast WITH TITLE
- $this->notify('You can't do that...', 'Warning...', 'warning'); 	//Warning Toast WITH TITLE
- $this->notify('Here's Something New!', 'New Product', 'info');  	//Info Toast WITH TITLE	
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Component::macro('notify', function ($message, $title = '', $type = 'success') {
$this->dispatchBrowserEvent('notify', ['message' => $message, 'title' => $title, 'type' => $type]);
});
}
<div
x-data="{
messages: [],
remove(message) {
this.messages.splice(this.messages.indexOf(message), 1)
},
}"
@notify.window="let message = $event.detail; messages.push(message); setTimeout(() => { remove(message) }, 2500)"
class="fixed inset-0 z-50 flex flex-col items-end justify-center px-4 py-6 space-y-4 pointer-events-none sm:p-6 sm:justify-start"
>
<template x-for="(message, messageIndex) in messages" :key="messageIndex" hidden>
<div
x-transition:enter="transform ease-out duration-300 transition"
x-transition:enter-start="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
x-transition:enter-end="translate-y-0 opacity-100 sm:translate-x-0"
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="w-full max-w-sm bg-white rounded-lg shadow-lg pointer-events-auto"
>
<div
:class="{
'ring-green-500': message.type === 'success',
'ring-red-500': message.type === 'error',
'ring-blue-500': message.type === 'info',
'ring-yellow-500': message.type === 'warning',
}"
class="w-full max-w-sm overflow-hidden bg-white rounded-lg shadow-lg pointer-events-auto ring-2 ring-opacity-50 ring-black"
>
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<svg x-show="message.type === 'success'" class="w-6 h-6 text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg x-show="message.type === 'error'" class="w-6 h-6 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg x-show="message.type === 'info'" class="w-6 h-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg x-show="message.type === 'warning'" class="w-6 h-6 text-yellow-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.618 5.984A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016zM12 9v2m0 4h.01" />
</svg>
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p x-text="message.title" class="font-medium text-gray-900 text-md"></p>
<p x-text="message.message" class="mt-1 text-sm text-gray-500"></p>
</div>
<div class="flex flex-shrink-0 ml-4">
<button @click="remove(message)" class="inline-flex text-gray-400 transition duration-150 ease-in-out focus:outline-none focus:text-gray-500">
<svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</template>
</div>
@scottzirkel
Copy link

I have something similar I'm using (I didn't abstract it in the Service Provider, nice touch!) - but for any notifications that happen after a redirect, I had to duplicate this and call it via flash session data. Any ideas on how to get it to work with both? My initial thought was to look for the flash notification on page load, and fire a browser event, but that didn't go well.

@greenspace10
Copy link
Author

I will look into this when I return to my office next week, but it may be possible to set the component to listen for both and merge them together. I will test and post back

@XolphinMike
Copy link

Any update to this approach? You mentioned a merge two months ago.

@ilearnbydoing
Copy link

Class 'App\Providers\Component' not found

thats the error I am getting

@scottzirkel
Copy link

Class 'App\Providers\Component' not found

thats the error I am getting

Sounds like you just need to import the Component class.

use Illuminate\View\Component;

@ilearnbydoing
Copy link

Class 'App\Providers\Component' not found
thats the error I am getting

Sounds like you just need to import the Component class.

use Illuminate\View\Component;

I tried but issue remains same.

What Component class require to import in AppServiceProvider

@ilearnbydoing
Copy link

use Illuminate\View\Component; has no macroable trait
it gives error Call to undefined method Illuminate\View\Component::macro()

@pktharindu
Copy link

It should be use Livewire\Component;

@ilearnbydoing
Copy link

It should be use Livewire\Component;

thanks that worked.

@scottzirkel
Copy link

Ha! Sorry @ilearnbydoing, I don't know why I suggested the Blade Component. Glad you got it working!

@jayenne
Copy link

jayenne commented Jan 13, 2022

collio ty.

@Michael-Stokoe
Copy link

Any ideas why the transitions aren't applying when new notifications are pushed / notifications are removed?

@zacktagnan
Copy link

Complete and detailed explanation here.

@jdion84
Copy link

jdion84 commented Jan 18, 2024

i found that this bugs out a little bit when you close a toast and open another.

its better to give each toast its own unique id imo, heres how i did it:

<div
    x-data="{ 
        toasts: [],
        remove(key) {
            this.toasts = this.toasts.filter((toast) => toast.key != key)
        },
    }"
    @toast.window="
        const toast = {
            key: Date.now(),
            message: $event.detail.message,
            type: $event.detail.type,
        }; 

        toasts.push(toast); 

        setTimeout(() => { remove(toast.key) }, 5000);
    "
    class="absolute top-0 right-0 space-y-4 w-full max-w-xs px-4 pb-4 pointer-events-none"
>
    <template x-for="toast in toasts" :key="toast.key">
        <div class="flex items-start bg-white rounded-lg shadow gap-2 p-4 pointer-events-auto">
            <div class="mt-0.5">
                <x-heroicon-o-check-circle x-show="toast.type == 'success'" class="text-green-600 w-5 h-5" />
                <x-heroicon-o-x-circle x-show="toast.type == 'error'" class="text-red-600 w-5 h-5" />
            </div>

            <p x-text="toast.message" class="flex-grow"></p>

            <button type="button" @click="remove(toast.key)" class="text-gray-400 hover:text-black mt-0.5">
                <x-heroicon-o-x-mark class="w-5 h-5" />
            </button>
        </div>
    </template>
</div>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment