K
KoreUI

Overlay - Stacking

Overlay

El sistema overlay soporta abrir overlays encima de otros overlays. El stack sigue un modelo LIFO gestionado por Alpine.

Demo en vivo

Prueba el stacking de overlays. Abre el wizard para ver como se apilan y navegan.

En el wizard, usa Siguiente para apilar, Anterior para volver al paso previo, y Cerrar todo para limpiar el stack completo.

Como funciona el stack

Modelo LIFO (last-in, first-out) para apilar overlays.

Al abrir un overlay mientras otro esta visible:

  1. El ID del overlay actual se agrega al array stack.
  2. El contenido actual hace transicion de salida (300ms).
  3. El nuevo overlay hace transicion de entrada y se convierte en el current activo.
  4. El backdrop permanece visible durante todo el proceso (sin parpadeo).

Al cerrar el overlay superior:

  1. Si el stack no esta vacio, el overlay previo se restaura como activo.
  2. Si el stack esta vacio, todo se cierra (backdrop desaparece, scroll del body se restaura).

Los overlays en el stack mantienen su estado de componente Livewire. Siguen montados en el servidor; solo la visibilidad Alpine cambia.

Abrir overlays anidados

Desde dentro de un overlay, despacha kore:open igual que desde una pagina.

Desde Blade (dentro de un overlay)
{{-- Dentro de la vista Blade de un overlay --}}
<button x-on:click="$dispatch('kore:open', {
    name: 'overlays.nested-detail',
    arguments: { itemId: {{ $itemId }} }
})">
    Ver detalles
</button>
{{-- Dentro de la vista Blade de un overlay --}}
<button x-on:click="$dispatch('kore:open', {
    name: 'overlays.nested-detail',
    arguments: { itemId: {{ $itemId }} }
})">
    Ver detalles
</button>
Desde PHP (dentro de un overlay)
public function openNested(): void
{
    $this->dispatch('kore:open',
        name: 'overlays.nested-detail',
        arguments: ['itemId' => $this->itemId]
    );
}
public function openNested(): void
{
    $this->dispatch('kore:open',
        name: 'overlays.nested-detail',
        arguments: ['itemId' => $this->itemId]
    );
}

Metodos de navegacion

Metodos disponibles en tu OverlayComponent via el trait HasOverlayBehavior.

close()
// Cierra el overlay actual y muestra el previo en el stack.
// Si no hay previo, todo se cierra.
public function save(): void
{
    // Logica de guardado...
    $this->close();
}
// Cierra el overlay actual y muestra el previo en el stack.
// Si no hay previo, todo se cierra.
public function save(): void
{
    // Logica de guardado...
    $this->close();
}
closeAll()
// Cierra todo el stack. Todos los overlays se descartan,
// el backdrop se oculta y el scroll del body se restaura.
public function done(): void
{
    $this->closeAll();
}
// Cierra todo el stack. Todos los overlays se descartan,
// el backdrop se oculta y el scroll del body se restaura.
public function done(): void
{
    $this->closeAll();
}
skipBack(int $count, bool $destroy)
// Cierra el overlay actual y salta $count overlays previos
// antes de navegar hacia atras. Util para flujos tipo "wizard".

// Stack: [A, B, C] — C es actual
// Despues de skipBack(1): C se cierra, B se salta, A se activa
$this->skipBack(1);

// Con destroy: el estado Livewire de B se elimina
$this->skipBack(1, destroy: true);
// Cierra el overlay actual y salta $count overlays previos
// antes de navegar hacia atras. Util para flujos tipo "wizard".

// Stack: [A, B, C] — C es actual
// Despues de skipBack(1): C se cierra, B se salta, A se activa
$this->skipBack(1);

// Con destroy: el estado Livewire de B se elimina
$this->skipBack(1, destroy: true);

closeWith y eventos

Despacha uno o mas eventos Livewire y luego cierra el overlay. Es la forma principal de comunicar resultados al padre.

Uso basico de closeWith()
public function confirm(): void
{
    $this->closeWith(['order-confirmed']);
}
public function confirm(): void
{
    $this->closeWith(['order-confirmed']);
}
Formatos de eventos soportados
// 1. Evento global (string)
$this->closeWith(['user-updated']);
// -> Livewire.dispatch('user-updated')

// 2. Evento global con parametros (array)
$this->closeWith([
    ['user-updated', ['userId' => $this->userId, 'name' => $this->name]],
]);
// -> Livewire.dispatch('user-updated', { userId: 5, name: 'John' })

// 3. Evento dirigido a componente (class => string)
use App\Livewire\UserList;
$this->closeWith([
    UserList::class => 'refresh',
]);
// -> Livewire.dispatchTo('user-list', 'refresh')

// 4. Evento dirigido con parametros (class => array)
$this->closeWith([
    UserList::class => ['user-saved', ['userId' => $this->userId]],
]);
// -> Livewire.dispatchTo('user-list', 'user-saved', { userId: 5 })
// 1. Evento global (string)
$this->closeWith(['user-updated']);
// -> Livewire.dispatch('user-updated')

// 2. Evento global con parametros (array)
$this->closeWith([
    ['user-updated', ['userId' => $this->userId, 'name' => $this->name]],
]);
// -> Livewire.dispatch('user-updated', { userId: 5, name: 'John' })

// 3. Evento dirigido a componente (class => string)
use App\Livewire\UserList;
$this->closeWith([
    UserList::class => 'refresh',
]);
// -> Livewire.dispatchTo('user-list', 'refresh')

// 4. Evento dirigido con parametros (class => array)
$this->closeWith([
    UserList::class => ['user-saved', ['userId' => $this->userId]],
]);
// -> Livewire.dispatchTo('user-list', 'user-saved', { userId: 5 })
Mezclar formatos en una sola llamada
$this->closeWith([
    'global-notification',
    UserList::class => 'refresh',
    ['audit-log', ['action' => 'updated']],
]);
$this->closeWith([
    'global-notification',
    UserList::class => 'refresh',
    ['audit-log', ['action' => 'updated']],
]);

Stacking de tipos mixtos

Overlays de diferentes tipos se pueden apilar libremente. La posicion, tamano y animacion se actualizan automaticamente.

Ejemplo: drawer -> confirm -> fullscreen
{{-- Pagina: abrir un drawer --}}
<button x-on:click="$dispatch('kore:open', {
    name: 'overlays.settings-drawer'
})">
    Configuracion
</button>

{{-- Dentro de settings-drawer: abrir un confirm --}}
<button x-on:click="$dispatch('kore:open', {
    name: 'overlays.reset-confirm'
})">
    Restablecer valores
</button>

{{-- Dentro de reset-confirm: abrir fullscreen --}}
<button x-on:click="$dispatch('kore:open', {
    name: 'overlays.preview-fullscreen'
})">
    Vista previa
</button>
{{-- Pagina: abrir un drawer --}}
<button x-on:click="$dispatch('kore:open', {
    name: 'overlays.settings-drawer'
})">
    Configuracion
</button>

{{-- Dentro de settings-drawer: abrir un confirm --}}
<button x-on:click="$dispatch('kore:open', {
    name: 'overlays.reset-confirm'
})">
    Restablecer valores
</button>

{{-- Dentro de reset-confirm: abrir fullscreen --}}
<button x-on:click="$dispatch('kore:open', {
    name: 'overlays.preview-fullscreen'
})">
    Vista previa
</button>

Estado del stack:

[settings-drawer, reset-confirm] con preview-fullscreen como actual.

Cerrar fullscreen retorna al confirm; cerrar confirm retorna al drawer.

Ejemplo: wizard multi-paso

Flujo de pasos donde cada paso es un overlay que abre el siguiente.

Wizard con close() y closeAll()
class WizardStep1 extends OverlayComponent
{
    public function next(): void
    {
        $this->dispatch('kore:open',
            name: 'overlays.wizard-step2',
            arguments: ['data' => $this->formData]
        );
    }

    public function render()
    {
        return view('livewire.overlays.wizard-step1');
    }
}

class WizardStep2 extends OverlayComponent
{
    public function back(): void
    {
        // Cierra step 2, retorna a step 1
        $this->close();
    }

    public function finish(): void
    {
        // Guarda y cierra todo
        $this->closeAll();
    }

    public function render()
    {
        return view('livewire.overlays.wizard-step2');
    }
}
class WizardStep1 extends OverlayComponent
{
    public function next(): void
    {
        $this->dispatch('kore:open',
            name: 'overlays.wizard-step2',
            arguments: ['data' => $this->formData]
        );
    }

    public function render()
    {
        return view('livewire.overlays.wizard-step1');
    }
}

class WizardStep2 extends OverlayComponent
{
    public function back(): void
    {
        // Cierra step 2, retorna a step 1
        $this->close();
    }

    public function finish(): void
    {
        // Guarda y cierra todo
        $this->closeAll();
    }

    public function render()
    {
        return view('livewire.overlays.wizard-step2');
    }
}

Ejemplo: skipBack en stack profundo

Salta multiples overlays en el stack para ir directamente a uno anterior.

skipBack con y sin destroy
// Stack: [Step1, Step2, Step3] — Step3 es actual

class WizardStep3 extends OverlayComponent
{
    public function backToStart(): void
    {
        // Salta Step2, va directamente a Step1
        $this->skipBack(1);
    }

    public function backToStartAndCleanUp(): void
    {
        // Salta Step2 y destruye su estado, va a Step1
        $this->skipBack(1, destroy: true);
    }

    public function render()
    {
        return view('livewire.overlays.wizard-step3');
    }
}
// Stack: [Step1, Step2, Step3] — Step3 es actual

class WizardStep3 extends OverlayComponent
{
    public function backToStart(): void
    {
        // Salta Step2, va directamente a Step1
        $this->skipBack(1);
    }

    public function backToStartAndCleanUp(): void
    {
        // Salta Step2 y destruye su estado, va a Step1
        $this->skipBack(1, destroy: true);
    }

    public function render()
    {
        return view('livewire.overlays.wizard-step3');
    }
}

Con destroy: true, los overlays saltados tienen su estado de componente Livewire eliminado del servidor, liberando recursos.