Search code examples
javascriptvue.jsnuxt.jsvuetify.js

Custom confirmation component not opening inside v-menu in vue.js


I am trying to show confirmation component whenever any button clicked on header is clicked. It is opening whenever I click other element except from dropdown using v-menu.

App.vue

<template>
  {{isConfirmDialogVisible}}
  <div class="header">
    <div class="left-menus">
      <div
        v-for="(item, index) in items"
        :key="index"
        class="menu"
        style="background-color: red; gap: 10px; padding: 10px"
        @click="opendialog()"
      >
        {{ item.title }}
      </div>
    </div>
    <v-menu open-on-hover transition="slide-y-transition">
      <template #activator="{ props }">
        <div style="cursor: pointer">
          <v-icon v-bind="props">mdi-menu-down</v-icon>
        </div>
      </template>

      <v-list>
        <v-list-item
          v-for="(item, index) in userOptions"
          :key="index"
          class="dropdown"
          @click="opendialog()"
        >
          <v-list-item-title>{{ item.title }}</v-list-item-title>
        </v-list-item>
      </v-list>
    </v-menu>
    <Confirmation
      v-model="isConfirmDialogVisible"
      v-if="isConfirmDialogVisible"
      action-button="submit"
      action-button-left="cancle"
      action-button-right="confirm"
    >
      <div style="font-size: 20px">Are you sure?</div>
    </Confirmation>
  </div>
</template>

<script>
  import { ref } from 'vue'
  import Confirmation from './Confirmation.vue'

  const isConfirmDialogVisible = ref(false)

  const userOptions = ref([{ title: 'Logout' }, { title: 'Setting' }])

  const opendialog = () => {
    isConfirmDialogVisible.value = true
  }

  export default {
    components: {
      Confirmation,
    },
    data: () => ({
      items: [
        { title: 'Click Me' },
        { title: 'Click Me' },
        { title: 'Click Me' },
        { title: 'Click Me 2' },
      ],

      opendialog,
      isConfirmDialogVisible,
      userOptions,
    }),
  }
</script>

<style>
  .header .left-menus {
    display: flex;
    align-items: center;
    gap: 16px;
  }
</style>

Confirmation.vue

<template>
  <v-dialog v-model="dialogVisible" persistent activator="parent" width="auto">
    <div class="dialog_container">
      <v-card>
        <div class="card-components">
          <div class="warning_image"></div>
          <v-card-text>
            <slot></slot>
          </v-card-text>
          <v-card-actions>
            <v-btn class="back" @click="closeDialog"
              >{{ actionButtonLeft }}</v-btn
            >
            <v-btn @click="confirmSelection" class="confirm-submit">
              {{ actionButtonRight}}
            </v-btn>
          </v-card-actions>
        </div>
      </v-card>
    </div>
  </v-dialog>
</template>
<script>
  import { ref } from 'vue'

  export default {
    props: {
      modelValue: Boolean,
      actionButton: {
        type: String,
        required: true,
      },
      actionButtonRight: {
        type: String,
        required: true,
      },
      actionButtonLeft: {
        type: String,
        required: false,
        default: 'cancel',
      },
    },

    emits: ['update:modelValue', 'confirmAction'],
    setup(props, { emit }) {
      const dialogVisible = ref(false)
      const closeDialog = () => {
        dialogVisible.value = false
        emit('update:modelValue', false)
      }
      const confirmSelection = () => {
        closeDialog()
        emit('confirmAction')
      }

      const returnBack = () => {
        closeDialog()
      }

      const handleOutsideClick = event => {
        console.log(event)
      }

      return {
        dialogVisible,
        closeDialog,
        returnBack,
        confirmSelection,
        handleOutsideClick,
      }
    },
  }
</script>

<style scoped>
  .card-components {
    display: flex;
    flex-direction: column;
    gap: 24px;
  }

  .dialog_container {
    height: 225px;
    width: 648px;
  }
  .v-card {
    padding: 16px 24px 24px 24px;
  }
  .v-card-text {
    padding: 0;
  }
  .warning_image {
    display: flex;
    width: 100%;
    justify-content: center;
  }

  .v-card-actions {
    min-height: 0;
    padding: 0;
    display: flex;
    justify-content: flex-end;
  }
  .v-card-actions .v-btn {
    border: 1px solid #9ca3af;
    color: #9ca3af;
    width: 140px;
    justify-content: center;
    align-items: center;
    border-radius: 4px;
  }

  .v-card-actions .confirm-submit {
    color: white;
    border: 1px solid #41a1e0;
    background: #41a1e0;
  }
</style>

I am trying to show confirmation component whenever any button clicked on header is clicked. It is opening whenever I click other element except from dropdown using v-menu.


Solution

  • In Confirmation.vue, the VDialog relies on the parent component to become visible:

    <template>
      <v-dialog v-model="dialogVisible" activator="parent">
      ...
    </template>
    <script setup>
      const dialogVisible = ref(false)
      ...
    </script>
    

    So when a Confirmation component becomes visible, VDialog will show the dialog and update dialogVisible.

    For some reason, this does not work when VMenu is visible. Instead, use the component's modelValue to trigger the dialog, which is more meaningful anyway. There are several ways to do this, one is to set dialogVisible in a watcher:

    setup(props, { emit }) {
      const dialogVisible = ref(false)
      watchEffect(() => dialogVisible.value = props.modelValue)
    

    You then have to remove the activator="parent" for it to work.

    Here it is in a playground