feat: reusable dynamic confirmation modal
This commit is contained in:
103
resources/views/components/bento/confirm-modal.blade.php
Normal file
103
resources/views/components/bento/confirm-modal.blade.php
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
@props([
|
||||||
|
'name' => 'confirm-modal',
|
||||||
|
])
|
||||||
|
|
||||||
|
<div x-show="show"
|
||||||
|
x-data="{
|
||||||
|
show: false,
|
||||||
|
title: '',
|
||||||
|
message: '',
|
||||||
|
confirmLabel: 'Confirm',
|
||||||
|
type: 'danger',
|
||||||
|
action: null,
|
||||||
|
open(data) {
|
||||||
|
this.title = data.title || 'Are you sure?';
|
||||||
|
this.message = data.message || 'This action cannot be undone.';
|
||||||
|
this.confirmLabel = data.confirmLabel || 'Confirm';
|
||||||
|
this.type = data.type || 'danger';
|
||||||
|
this.action = data.action || null;
|
||||||
|
this.show = true;
|
||||||
|
},
|
||||||
|
confirm() {
|
||||||
|
if (this.action) {
|
||||||
|
this.action();
|
||||||
|
}
|
||||||
|
this.show = false;
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
x-on:open-{{ $name }}.window="open($event.detail)"
|
||||||
|
class="fixed inset-0 z-[110] flex items-center justify-center p-4 lg:p-8"
|
||||||
|
style="display: none;"
|
||||||
|
x-cloak>
|
||||||
|
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div x-show="show"
|
||||||
|
x-transition:enter="transition ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0"
|
||||||
|
x-transition:enter-end="opacity-100"
|
||||||
|
x-transition:leave="transition ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100"
|
||||||
|
x-transition:leave-end="opacity-0"
|
||||||
|
@click="show = false"
|
||||||
|
class="absolute inset-0 bg-zinc-950/80 backdrop-blur-xl"></div>
|
||||||
|
|
||||||
|
<!-- Modal Card -->
|
||||||
|
<div x-show="show"
|
||||||
|
x-transition:enter="transition ease-out duration-500"
|
||||||
|
x-transition:enter-start="opacity-0 scale-95 translate-y-8"
|
||||||
|
x-transition:enter-end="opacity-100 scale-100 translate-y-0"
|
||||||
|
x-transition:leave="transition ease-in duration-300"
|
||||||
|
x-transition:leave-start="opacity-100 scale-100 translate-y-0"
|
||||||
|
x-transition:leave-end="opacity-0 scale-95 translate-y-8"
|
||||||
|
class="w-full max-w-[400px] bg-zinc-900 border border-white/10 rounded-[32px] p-8 relative overflow-hidden shadow-2xl">
|
||||||
|
|
||||||
|
<!-- Background Glow based on type -->
|
||||||
|
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-64 h-64 rounded-full blur-[80px] -z-10 opacity-20"
|
||||||
|
:class="{
|
||||||
|
'bg-rose-500': type === 'danger',
|
||||||
|
'bg-blue-500': type === 'info',
|
||||||
|
'bg-amber-500': type === 'warning',
|
||||||
|
'bg-emerald-500': type === 'success'
|
||||||
|
}"></div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<!-- Icon -->
|
||||||
|
<div class="w-16 h-16 rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-xl"
|
||||||
|
:class="{
|
||||||
|
'bg-rose-500/10 text-rose-500': type === 'danger',
|
||||||
|
'bg-blue-500/10 text-blue-500': type === 'info',
|
||||||
|
'bg-amber-500/10 text-amber-500': type === 'warning',
|
||||||
|
'bg-emerald-500/10 text-emerald-500': type === 'success'
|
||||||
|
}">
|
||||||
|
<!-- Danger Icon -->
|
||||||
|
<svg x-show="type === 'danger'" class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
||||||
|
<!-- Warning Icon -->
|
||||||
|
<svg x-show="type === 'warning'" class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
||||||
|
<!-- Info Icon -->
|
||||||
|
<svg x-show="type === 'info'" class="w-8 h-8" 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>
|
||||||
|
<!-- Success Icon -->
|
||||||
|
<svg x-show="type === 'success'" class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="text-2xl font-black text-white mb-2 tracking-tight uppercase italic" x-text="title"></h3>
|
||||||
|
<p class="text-xs font-bold text-zinc-500 uppercase tracking-widest leading-relaxed mb-8 px-4" x-text="message"></p>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<button @click="show = false"
|
||||||
|
class="py-4 rounded-2xl bg-white/5 border border-white/10 text-zinc-400 font-black text-[10px] uppercase tracking-[0.2em] hover:bg-white/10 hover:text-white transition-all">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button @click="confirm()"
|
||||||
|
class="py-4 rounded-2xl font-black text-[10px] uppercase tracking-[0.2em] shadow-xl transition-all"
|
||||||
|
:class="{
|
||||||
|
'bg-rose-600 text-white hover:bg-rose-500 shadow-rose-900/20': type === 'danger',
|
||||||
|
'bg-blue-600 text-white hover:bg-blue-500 shadow-blue-900/20': type === 'info',
|
||||||
|
'bg-amber-600 text-white hover:bg-amber-500 shadow-amber-900/20': type === 'warning',
|
||||||
|
'bg-emerald-600 text-white hover:bg-emerald-500 shadow-emerald-900/20': type === 'success'
|
||||||
|
}"
|
||||||
|
x-text="confirmLabel">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -22,6 +22,9 @@
|
|||||||
}"
|
}"
|
||||||
x-init="$watch('selectedId', value => { if(value && window.innerWidth < 1024) mobileView = 'detail' })"
|
x-init="$watch('selectedId', value => { if(value && window.innerWidth < 1024) mobileView = 'detail' })"
|
||||||
@resize.window="if (window.innerWidth >= 1280) sidebarOpen = true">
|
@resize.window="if (window.innerWidth >= 1280) sidebarOpen = true">
|
||||||
|
|
||||||
|
<!-- Global Confirmation Modal -->
|
||||||
|
<x-bento.confirm-modal />
|
||||||
|
|
||||||
<!-- Mobile Sidebar Backdrop -->
|
<!-- Mobile Sidebar Backdrop -->
|
||||||
<div x-show="sidebarOpen"
|
<div x-show="sidebarOpen"
|
||||||
@@ -93,8 +96,13 @@
|
|||||||
title="Copy Address">
|
title="Copy Address">
|
||||||
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /></svg>
|
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /></svg>
|
||||||
</button>
|
</button>
|
||||||
<button wire:click="deleteMailbox({{ $currentMailbox['id'] }})"
|
<button @click="$dispatch('open-confirm-modal', {
|
||||||
wire:confirm="Are you sure you want to delete this session? All emails will be lost."
|
title: 'Burn Mailbox?',
|
||||||
|
message: 'Are you sure you want to delete this mailbox? All emails will be permanently lost.',
|
||||||
|
confirmLabel: 'Burn Now',
|
||||||
|
type: 'danger',
|
||||||
|
action: () => $wire.deleteMailbox({{ $currentMailbox['id'] }})
|
||||||
|
})"
|
||||||
class="p-1.5 rounded-lg bg-rose-500/10 text-rose-500/60 hover:text-rose-500 hover:bg-rose-500/20 transition-all"
|
class="p-1.5 rounded-lg bg-rose-500/10 text-rose-500/60 hover:text-rose-500 hover:bg-rose-500/20 transition-all"
|
||||||
title="Delete Session">
|
title="Delete Session">
|
||||||
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
||||||
@@ -347,7 +355,13 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-6 w-px bg-white/5"></div>
|
<div class="h-6 w-px bg-white/5"></div>
|
||||||
<button wire:click="deleteEmail({{ $selectedEmailId }})"
|
<button @click="$dispatch('open-confirm-modal', {
|
||||||
|
title: 'Burn Email?',
|
||||||
|
message: 'Are you sure you want to delete this email? This action is permanent.',
|
||||||
|
confirmLabel: 'Burn Now',
|
||||||
|
type: 'danger',
|
||||||
|
action: () => $wire.deleteEmail({{ $selectedEmailId }})
|
||||||
|
})"
|
||||||
class="p-2 rounded-xl bg-white/5 border border-white/5 text-zinc-400 hover:text-rose-500 hover:bg-rose-500/10 transition-all" title="Delete Email">
|
class="p-2 rounded-xl bg-white/5 border border-white/5 text-zinc-400 hover:text-rose-500 hover:bg-rose-500/10 transition-all" title="Delete Email">
|
||||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" /></svg>
|
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" /></svg>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user