emargareten/inertia-modal

Inertia Modal is a Laravel package that lets you implement backend-driven modal dialogs for Inertia apps.

v1.4.0 2024-10-31 14:02 UTC

README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

Inertia Modal is a Laravel package that lets you implement backend-driven modal dialogs for Inertia apps. With this package, you can define modal routes on the backend and dynamically render them when you visit a dialog route.

Note

This package supports Vue 3 only

Installation

You can install the package via composer:

composer require emargareten/inertia-modal

Frontend Setup

Warning

The package utilizes axios under the hood. If your app is already using axios as a dependency, make sure to lock it to the same version Inertia uses.

Modal Component

Modal is a headless component, meaning you have full control over its look, whether it's a modal dialog or a slide-over panel. You are free to use any 3rd-party solutions to power your modals, such as Headless UI.

Put the Modal component somewhere within the layout.

<script setup>
import { Modal } from '../../vendor/emargareten/inertia-modal'
</script>

<template>
  <div>
    <!-- layout -->
    <Modal />
  </div>
</template>

Note

Ensure that the layout remains persistent throughout the entire application. If you have multiple layouts, create a base layout that should invoke the modal, using nested layouts.

Plugin

Set up a modal plugin with the same component resolver you use to render Inertia pages.

Vite

import { modal } from '../../vendor/emargareten/inertia-modal'

createInertiaApp({
  resolve: (name) => resolvePageComponent(name, import.meta.glob('./Pages/**/*.vue')),
  setup({ el, app, props, plugin }) {
    createApp({ render: () => h(app, props) })
      .use(modal, {
        resolve: (name) => resolvePageComponent(name, import.meta.glob('./Pages/**/*.vue')),
      })
      .use(plugin)
      .mount(el)
  }
})

Laravel Mix

import { modal } from '../../vendor/emargareten/inertia-modal'

createInertiaApp({
  resolve: (name) => require(`./Pages/${name}`),
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(modal, {
        resolve: (name) => import(`./Pages/${name}`),
      })
      .use(plugin)
      .mount(el)
  }
})

Usage

Modals have their own routes, letting you access them even via direct URLs. Define routes for your modal pages.

// background context / base page
Route::get('users', [UserController::class, 'index'])->name('users.index');

// modal route
Route::get('users/{user}', [UserController::class, 'show'])->name('users.show');

Render a modal from a controller. Specify the base route to render the background when the modal is accessed directly.

use Emargareten\InertiaModal\Modal;
use Inertia\Inertia;

class UserController extends Controller
{
    // ...
    
    public function show(User $user): Modal
    {
        return Inertia::modal('Users/Show', ['user' => $user])->baseRoute('users.index');
    }
}

By default, the backdrop component will be preserved with its current [stale] data (besides for the validation errors), in most cases this is fine since it will refresh when we close the modal (redirect to the base route), if your app does need fresh data for the backdrop, add the refreshBackdrop method:

    public function show(User $user): Modal
    {
        return Inertia::modal('Users/Show', ['user' => $user])
            ->baseRoute('users.index')
            ->refreshBackdrop();
    }

To force a specific route as the backdrop add the forceBase method:

    public function show(User $user): Modal
    {
        return Inertia::modal('Users/Show', ['user' => $user])
            ->baseRoute('users.index')
            ->forceBase();
    }

This will force re-render of the base route (or even redirect to a different base route).

Both of the above methods can also accept a boolean whether to refresh etc.

Frontend implementation

Use the useModal() composable in your modal component.

This example is a simple headlessui modal, you can add more transitions etc. see https://headlessui.com/vue/dialog.

<template>
  <TransitionRoot appear as="template" :show="show">
    <Dialog as="div" class="relative z-10" @close="close">
      <TransitionChild @after-leave="redirect" as="template">
        <div class="fixed inset-0 bg-black/75 transition-opacity" />
      </TransitionChild>

      <div class="fixed inset-0 overflow-y-auto">
        <div class="flex min-h-full items-center justify-center p-4 text-center">
          <TransitionChild as="template">
            <DialogPanel class="w-full max-w-lg transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
              <DialogTitle as="h3" class="text-lg font-medium leading-6 text-gray-900">
                <slot name="title" />
              </DialogTitle>
              <slot />
            </DialogPanel>
          </TransitionChild>
        </div>
      </div>
    </Dialog>
  </TransitionRoot>
</template>

<script setup>
import { TransitionRoot, TransitionChild, Dialog, DialogPanel, DialogTitle } from '@headlessui/vue'
import { useModal } from '../../vendor/emargareten/inertia-modal'

const { show, close, redirect } = useModal()
</script>

The redirect method will redirect to the base route, you can pass in all inertia visit options as a parameter.

redirect({ preserveScroll: true })

The close method will close the modal without redirecting to the base route.

Note

For a more concise setup, consider configuring aliases instead of specifying the full path.

Using vite:

// vite.config.js
export default defineConfig({
  resolve: {
    alias: {
      'inertia-modal': path.resolve('vendor/emargareten/inertia-modal'),
    },
  },
});

Using mix:

// webpack.mix.js
mix.alias({
  'inertia-modal': path.resolve('vendor/emargareten/inertia-modal'),
});

Now you can import the modules like this:

import { useModal } from 'inertia-modal'

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

This package was highly inspired by momentum-modal

License

The MIT License (MIT). Please see License File for more information.