Skip to content

Instantly share code, notes, and snippets.

@blackfyre
Last active April 3, 2025 21:40
Show Gist options
  • Save blackfyre/ccd2a207948912594b5ac6cf67a11ef8 to your computer and use it in GitHub Desktop.
Save blackfyre/ccd2a207948912594b5ac6cf67a11ef8 to your computer and use it in GitHub Desktop.
Modals in Laravel Nova Tools
<template>
<modal @modal-close="handleClose">
<form
@submit.prevent="handleConfirm"
slot-scope="props"
class="bg-white rounded-lg shadow-lg overflow-hidden"
style="width: 460px"
>
<slot :uppercaseMode="uppercaseMode" :mode="mode">
<div class="p-8">
<heading :level="2" class="mb-6">{{ __('General Modal') }}</heading>
<p class="text-80 leading-normal">{{__('General modal contents')}}</p>
</div>
</slot>
<div class="bg-30 px-6 py-3 flex">
<div class="ml-auto">
<button type="button" data-testid="cancel-button" dusk="cancel-general-button" @click.prevent="handleClose" class="btn text-80 font-normal h-9 px-3 mr-3 btn-link">{{__('Cancel')}}</button>
<button id="confirm-delete-button" ref="confirmButton" data-testid="confirm-button" type="submit" class="btn btn-default btn-danger">{{ __(uppercaseMode) }}</button>
</div>
</div>
</form>
</modal>
</template>
<script>
export default {
name: "GeneralModal",
methods: {
handleClose() {
this.$emit('close')
},
handleConfirm() {
this.$emit('confirm')
},
},
/**
* Mount the component.
*/
mounted() {
this.$refs.confirmButton.focus()
},
}
</script>
<style scoped>
</style>
<template>
<div>
<button @click="openModal">{{__('Open Modal')}}</button>
<portal to="modals">
<transition name="fade">
<general-modal
v-if="modalOpen"
@confirm="confirmModal"
@close="closeModal"
/>
</transition>
</portal>
</div>
</template>
<script>
import GeneralModal from './parts/modals/GeneralModal.vue';
export default {
props: ["resourceName", "resourceId", "field"],
data() {
return {
modalOpen: false
}
},
components: {
GeneralModal
},
mounted() {
},
methods: {
openModal() {
this.modalOpen = true;
},
confirmModal() {
this.modalOpen = false;
},
closeModal() {
this.modalOpen = false;
}
}
};
</script>
@provydon
Copy link

thanks for this mehn

@bmoex
Copy link

bmoex commented Feb 22, 2023

Thank you for your sharing; I fixed it for Nova 4 like this:
Similar to your implementation but changed to match laravel/nova/resources/js/components/Modals/DeleteResourceModal.vue

GeneralModal.vue

<template>
    <Modal @modal-close="handleClose"
           :show="show"
           role="alertdialog"
           size="sm">
        <form
            @submit.prevent="handleConfirm"
            class="mx-auto bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden"
        >
            <slot/>
            <ModalFooter>
                <div class="ml-auto">
                    <LinkButton
                        type="button"
                        data-testid="cancel-button"
                        dusk="cancel-delete-button"
                        @click.prevent="handleClose"
                        class="mr-3"
                    >
                        {{ __('Cancel') }}
                    </LinkButton>

                    <LoadingButton
                        ref="confirmButton"
                        dusk="confirm-delete-button"
                        :processing="working"
                        :disabled="working"
                        component="DangerButton"
                        type="submit"
                    >
                        {{ confirmButtonText }}
                    </LoadingButton>
                </div>
            </ModalFooter>
        </form>
    </Modal>
</template>

<script setup>
import {ref, watchEffect} from 'vue'

const props = defineProps({
    confirmButtonText: {
        type: String,
        default: 'Delete'
    }
})
const emit = defineEmits(['close', 'confirm'])

const confirmButton = ref(null)
watchEffect(() => {
    // Only focus when mounted (e.g. if hidden through :show)
    if (confirmButton.value) {
        confirmButton.value.focus()
    }
})

const handleClose = () => {
    emit('close')
};

const handleConfirm = () => {
    emit('confirm')
};
</script>

Tool.vue

...
        <GeneralModal
            :show="modalOpen"
            confirmButtonText="Delete"
            @close="closeModal"
            @confirm="confirmModal">
            <ModalHeader>Delete resource</ModalHeader>
            <ModalContent>
                <p class="leading-normal">
                    Are you sure you want to delete the resource?
                </p>
            </ModalContent>
        </GeneralModal>
...

@BobbyBorisov
Copy link

@bmoex thanks for the snippet. any idea how to trigger the auto close on click away functionality? I can see that Nova 4 native modals have it

@blackfyre
Copy link
Author

@bmoex thanks for the snippet. any idea how to trigger the auto close on click away functionality? I can see that Nova 4 native modals have it

You really can't do that without modifying the nova code as it usually involves a click listener on the backdrop.

@Yogeshkad
Copy link

any sample for nova 5.19 and vue 3 any plugin or component avaibale now to solve modal popup in nova ?

@blackfyre
Copy link
Author

@Yogeshkad I've not actively using Nova for the past couple years now, so sorry, can't really help with that, but I believe the previous concepts should still apply if not verbatim. You could start by looking at the current Modal component within Nova and work your way from there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment