dashboard/shell/components/PromptModal.vue

149 lines
3.8 KiB
Vue

<script>
import { mapState } from 'vuex';
import { isArray } from '@shell/utils/array';
import AppModal from '@shell/components/AppModal.vue';
/**
* @name PromptModal
* @description Modal component.
*/
export default {
name: 'PromptModal',
components: { AppModal },
data() {
return {
opened: false,
backgroundClosing: null,
componentRendered: false
};
},
computed: {
...mapState('action-menu', ['showModal', 'modalData']),
resources() {
let resources = this.modalData?.resources;
if (!isArray(resources)) {
resources = [resources];
}
return resources || [];
},
testId() {
return this.modalData?.testId || 'prompt-modal-generic-testid';
},
returnFocusSelector() {
return this.modalData?.returnFocusSelector || undefined;
},
returnFocusFirstIterableNodeSelector() {
return this.modalData?.returnFocusFirstIterableNodeSelector || undefined;
},
modalWidth() {
// property set from workload.js to overwrite modal default width of 600px, with fallback value as well
return this.modalData?.modalWidth || '600px';
},
customClass() {
return this.modalData?.customClass || undefined;
},
styles() {
return this.modalData?.styles || undefined;
},
height() {
return this.modalData?.height || undefined;
},
component() {
// Looks for a dialog component by looking up in plugins and @shell/dialog/${name}.
return this.$store.getters['type-map/importDialog'](this.modalData?.component);
},
cssProps() {
// this computed property lets us generate a scss var that we can use in the style
return `--prompt-modal-width: ${ this.modalWidth }`;
},
stickyProps() {
const isSticky = !!this.modalData?.modalSticky;
return !isSticky ? '' : 'display: flex; flex-direction: column; ';
},
closeOnClickOutside() {
return this.modalData?.closeOnClickOutside;
},
modalName() {
return this.modalData?.modalName;
}
},
watch: {
showModal(show) {
this.opened = show;
}
},
methods: {
onSlotComponentMounted() {
// variable for the watcher based focus-trap
// so that we know when the component is rendered
this.componentRendered = true;
},
close(data) {
if (!this.opened) {
return;
}
this.errors = [];
this.$store.commit('action-menu/togglePromptModal', data);
if (this.backgroundClosing) {
this.backgroundClosing();
}
this.componentRendered = false;
this.opened = false;
},
// We're using register instead of just making use of $refs because the $refs is always undefined when referencing the component
registerBackgroundClosing(fn) {
this['backgroundClosing'] = fn;
}
},
};
</script>
<template>
<app-modal
v-if="opened && component"
:name="modalName"
:click-to-close="closeOnClickOutside"
:width="modalWidth"
:data-testid="testId"
:custom-class="customClass"
:styles="styles"
:height="height"
:trigger-focus-trap="true"
:return-focus-selector="returnFocusSelector"
:return-focus-first-iterable-node-selector="returnFocusFirstIterableNodeSelector"
:focus-trap-watcher-based-variable="componentRendered"
@close="close"
>
<component
v-bind="modalData.componentProps || {}"
:is="component"
:resources="resources"
:register-background-closing="registerBackgroundClosing"
@vue:mounted="onSlotComponentMounted"
@close="close"
/>
</app-modal>
</template>
<style lang='scss'>
.promptModal-modal {
border-radius: var(--border-radius);
overflow: scroll;
max-height: 100vh;
& ::-webkit-scrollbar-corner {
background: rgba(0,0,0,0);
}
}
</style>