64 lines
3.4 KiB
PHP
64 lines
3.4 KiB
PHP
@props([
|
|
// Client mode: dispatch openModal directly from the browser.
|
|
'component' => null, // e.g. 'modals.edit-credential'
|
|
'arguments' => [], // assoc array, e.g. ['serverId' => $server->id]
|
|
// Server mode: a wire:click method that itself dispatches openModal (e.g. a confirm
|
|
// action that must issue a signed token first). e.g. "confirmKeyRemoval(3)".
|
|
'action' => null,
|
|
// Passthrough to the underlying button component (variant/size/icon).
|
|
'variant' => 'secondary',
|
|
'size' => 'sm',
|
|
'icon' => false,
|
|
// Override the timeout error-toast copy (defaults to the shared shell key).
|
|
'failMessage' => null,
|
|
])
|
|
@php
|
|
// Server-mode opens take a round-trip before the modal mounts -> a longer watchdog.
|
|
$timeout = $action ? 3500 : 2200;
|
|
$fail = $failMessage ?? __('shell.toast_modal_failed');
|
|
|
|
$xData = 'modalTrigger('
|
|
.\Illuminate\Support\Js::from($component).', '
|
|
.\Illuminate\Support\Js::from((object) $arguments).', '
|
|
.\Illuminate\Support\Js::from($fail).', '
|
|
.$timeout.')';
|
|
|
|
// All dynamic attributes are built here and forwarded to the button via :attributes —
|
|
// NOT as a directive/echo inside the component tag (block directives compile to broken
|
|
// PHP, and a raw attributes->merge() echo inside a component tag is not treated as a bag).
|
|
// Server mode keeps wire:click + arms the watchdog; client mode dispatches via open().
|
|
//
|
|
// While pending we block re-clicks + dim via a class — NOT x-bind:disabled, which would
|
|
// clobber a caller's status-based :disabled (e.g. services start/stop disabled by state).
|
|
$triggerAttrs = [
|
|
'x-data' => $xData,
|
|
'x-bind:aria-busy' => 'pending',
|
|
'x-bind:class' => "pending && 'pointer-events-none opacity-70'",
|
|
// Capture-phase guard: while pending, swallow the click BEFORE wire:click/open() run —
|
|
// blocks keyboard (Enter/Space) and programmatic re-activation too, not just the mouse
|
|
// (pointer-events-none only covers the pointer), so a server-mode wire:click can't double-fire.
|
|
'x-on:click.capture' => 'if (pending) { $event.preventDefault(); $event.stopImmediatePropagation(); }',
|
|
'x-on:click' => $action ? 'arm()' : 'open()',
|
|
];
|
|
if ($action) {
|
|
$triggerAttrs['wire:click'] = $action;
|
|
}
|
|
@endphp
|
|
{{--
|
|
One trigger for every modal in the app (R10). It ALWAYS shows an immediate spinner on
|
|
click (Alpine flips `pending` synchronously — no round-trip needed) and clears it the
|
|
instant the modal mounts. If the modal never appears within the timeout, it raises an
|
|
error toast ONLY when the click produced no Livewire commit at all (a true silent
|
|
failure); a server method that committed but chose not to open (no-op / validation)
|
|
stays quiet and surfaces its own feedback. So a click can never look dead, and a
|
|
genuinely failed open is never silent. See window.Alpine.data('modalTrigger') in
|
|
resources/js/app.js.
|
|
--}}
|
|
<x-btn :variant="$variant" :size="$size" :icon="$icon" :attributes="$attributes->merge($triggerAttrs)">
|
|
<span x-show="!pending" class="contents">{{ $slot }}</span>
|
|
<svg x-show="pending" x-cloak class="h-3.5 w-3.5 animate-spin" viewBox="0 0 24 24" fill="none"
|
|
stroke="currentColor" stroke-width="2.5" aria-hidden="true">
|
|
<path d="M21 12a9 9 0 1 1-6.219-8.56" stroke-linecap="round" />
|
|
</svg>
|
|
</x-btn>
|