Toastr

Flash message component powered by AlpineJS and Blade Components.

Usage

Use this component only when data submitted through ajax.

// inside body of master blade view

<x-toastr />

Submitting a form via fetch api.

// in blade view
<div
    x-cloak
    x-data="{
        save($event, $dispatch) {
            fetch('/posts', {
                credentials: 'same-origin',
                method: 'POST',
                body: JSON.stringify({
                    title: $event.target.title.value,
                    body: $event.target.body.value,
                    category: selectedValues
                }),
                headers: {
                    'X-Requested-With': 'XMLHttpRequest',
                    'X-CSRF-TOKEN': '{{ csrf_token() }}',
                    'Content-Type': 'application/json',
                    'Accept': 'application/json',
                }
            })
                .then(async response => {
                    const data = await response.json()
                    if (response.ok) {
                        return data
                    } else {
                        return Promise.reject(data)
                    }
                })
                .then(
                    data => {
                        // show toastr message
                        $dispatch('notice', { type: 'success', text: 'Post created' });
                        location.href = '/posts';
                    },
                    error => {
                        if (error.message.length) {
                            $dispatch('js-errors', { errors: error.errors });
                        }
                    }
                );
            }
        }
    "
>

    ...

</div>

Component

// components/toastr.blade.php

<div
    x-data="{
        notices: [],
        visible: [],
        add(notice) {
            notice.id = Date.now()
            this.notices.push(notice)
            this.fire(notice.id)
        },
        fire(id) {
            this.visible.push(this.notices.find(notice => notice.id == id))
            const timeShown = 2500 * this.visible.length
            setTimeout(() => {
                this.remove(id)
            }, timeShown)
        },
        remove(id) {
            const notice = this.visible.find(notice => notice.id == id)
            const index = this.visible.indexOf(notice)
            this.visible.splice(index, 1)
        }
    }"
    @notice.window="add($event.detail)"
    x-cloak
    class="fixed flex flex-col-reverse inset-y-0 right-0 items-start sm:items-end justify-end pointer-events-none p-4 z-40">
    <template x-for="notice of notices" :key="notice.id">
        <div
            x-show="visible.includes(notice)"
            x-transition:enter="ease-out duration-300 transition transform"
            x-transition:enter-start="translate-x-5 opacity-0"
            x-transition:enter-end="translate-x-0 opacity-100"
            x-transition:leave="transition-all ease-out duration-300 transform"
            x-transition:leave-start="translate-x-0 opacity-100"
            x-transition:leave-end="translate-x-5 opacity-0"
            x-on:click="remove(notice.id)"
            class="max-w-sm w-full rounded-lg shadow-lg cursor-pointer pointer-events-auto mb-4"
            :class="{
                'bg-green-100 text-green-600': notice.type === 'success',
                'bg-blue-100 text-blue-600': notice.type === 'info',
                'bg-red-100 text-red-500': notice.type === 'error'
            }">
            <div class="rounded-lg shadow-xs overflow-hidden flex relative pl-3 pr-6 py-3">
                <div class="flex-shrink-0 mr-3">
                    <template x-if="notice.type === 'info'">
                        <svg class="w-8 h-8" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
                    </template>
                    <template x-if="notice.type === 'success'">
                        <svg class="w-8 h-8" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path></svg>
                    </template>
                    <template x-if="notice.type === 'error'">
                        <svg class="w-8 h-8" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>
                    </template>
                </div>
                <div class="flex-1 pt-1" x-text="notice.text"></div>
            </div>
        </div>
    </template>
</div>