mirror of
https://github.com/nocodb/nocodb.git
synced 2026-05-01 17:17:06 +00:00
326 lines
7.6 KiB
Vue
326 lines
7.6 KiB
Vue
<script lang="ts" setup>
|
|
import type { NcButtonProps } from './Button.vue'
|
|
import type { NcModalProps } from './Modal.vue'
|
|
|
|
/**
|
|
* NcModalConfirm component - A customizable modal confirmation dialog.
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const isOpen = ref(true)
|
|
*
|
|
* const { close } = useDialog(NcModalConfirm, {
|
|
* 'visible': isOpen,
|
|
* 'title': 'Confirm Action',
|
|
* 'content': 'Are you sure you want to proceed?',
|
|
* 'okText': 'Yes',
|
|
* 'cancelText': 'No',
|
|
* 'onCancel': closeDialog,
|
|
* 'onOk': async () => {
|
|
* closeDialog()
|
|
* await performAction()
|
|
* },
|
|
* 'update:visible': closeDialog,
|
|
* })
|
|
*
|
|
* function closeDialog() {
|
|
* isOpen.value = false
|
|
* close(1000)
|
|
* }
|
|
* ```
|
|
*/
|
|
|
|
/**
|
|
* Props interface extending NcModalProps with additional customization options.
|
|
*/
|
|
export interface NcConfirmModalProps extends NcModalProps {
|
|
/** Type of modal (affects icon and styling) */
|
|
type?: 'error' | 'success' | 'warning' | 'info'
|
|
|
|
/** Whether to show an icon next to the title */
|
|
showIcon?: boolean
|
|
|
|
/** Title of the modal */
|
|
title?: string
|
|
|
|
/** Additional class for title styling */
|
|
titleClass?: string
|
|
|
|
/** Content of the modal */
|
|
content?: string
|
|
|
|
/** Additional class for content styling */
|
|
contentClass?: string
|
|
|
|
/** Text for the OK button */
|
|
okText?: string
|
|
|
|
/** Additional class for the OK button */
|
|
okClass?: string
|
|
|
|
/** Whether to show the OK button */
|
|
showOkBtn?: boolean
|
|
|
|
okProps?: Partial<NcButtonProps>
|
|
|
|
/** Text for the Cancel button */
|
|
cancelText?: string
|
|
|
|
/** Additional class for the Cancel button */
|
|
cancelClass?: string
|
|
|
|
/** Whether to show the Cancel button */
|
|
showCancelBtn?: boolean
|
|
|
|
cancelProps?: Partial<NcButtonProps>
|
|
|
|
/** Extra HTML attributes for the `.nc-modal-confirm` wrapper div */
|
|
wrapperProps?: Record<string, any>
|
|
|
|
/** Determines which button gets focus on open */
|
|
focusBtn?: 'ok' | 'cancel' | null
|
|
}
|
|
|
|
const props = withDefaults(defineProps<NcConfirmModalProps>(), {
|
|
maskClosable: false,
|
|
showSeparator: false,
|
|
size: 'xs',
|
|
height: 'auto',
|
|
|
|
type: 'warning',
|
|
showIcon: true,
|
|
|
|
title: '',
|
|
titleClass: '',
|
|
|
|
content: '',
|
|
contentClass: '',
|
|
|
|
okText: '',
|
|
okClass: '',
|
|
showOkBtn: true,
|
|
|
|
cancelText: '',
|
|
cancelClass: '',
|
|
showCancelBtn: true,
|
|
|
|
focusBtn: 'ok',
|
|
})
|
|
|
|
const emits = defineEmits<Emits>()
|
|
|
|
const { visible: _visible, showOkBtn: _showOkBtn, showCancelBtn: _showCancelBtn, ...restProps } = props
|
|
|
|
const initialFocus = ref<boolean>(false)
|
|
|
|
interface Emits {
|
|
(e: 'update:visible', value: boolean): void
|
|
// cancel is generic, on click cancel or close modal using keybord shortcut or overlay click
|
|
(e: 'cancel'): void
|
|
(e: 'clickCancel'): void
|
|
(e: 'ok'): void
|
|
}
|
|
|
|
const vVisible = useVModel(props, 'visible', emits, { defaultValue: false })
|
|
|
|
const vModel = computed({
|
|
get: () => vVisible.value,
|
|
set: (value: boolean) => {
|
|
vVisible.value = value
|
|
|
|
emits('update:visible', value)
|
|
|
|
if (!value) {
|
|
emits('cancel')
|
|
}
|
|
},
|
|
})
|
|
|
|
const { type, showCancelBtn, showOkBtn } = toRefs(props)
|
|
|
|
const iconName = computed<IconMapKey>(() => {
|
|
if (type.value === 'success') {
|
|
return 'circleCheckSolid'
|
|
}
|
|
|
|
if (type.value === 'error') {
|
|
return 'ncAlertCircleFilled'
|
|
}
|
|
|
|
if (type.value === 'warning') {
|
|
return 'alertTriangleSolid'
|
|
}
|
|
|
|
if (type.value === 'info') {
|
|
return 'ncInfoSolid'
|
|
}
|
|
|
|
return 'alertTriangleSolid'
|
|
})
|
|
|
|
const cancelBtnRef = ref<HTMLButtonElement>()
|
|
|
|
const okBtnRef = ref<HTMLButtonElement>()
|
|
|
|
const onClickCancel = () => {
|
|
vModel.value = false
|
|
emits('clickCancel')
|
|
}
|
|
|
|
/** Watches for cancel button reference and sets focus if applicable */
|
|
watch(cancelBtnRef, () => {
|
|
if (!showCancelBtn.value || !cancelBtnRef.value?.$el || props.focusBtn !== 'cancel') return
|
|
;(cancelBtnRef.value?.$el as HTMLButtonElement)?.focus()
|
|
initialFocus.value = true
|
|
})
|
|
|
|
/** Watches for OK button reference and sets focus if applicable */
|
|
watch(okBtnRef, () => {
|
|
if (!showOkBtn.value || !okBtnRef.value?.$el || props.focusBtn !== 'ok') return
|
|
;(okBtnRef.value?.$el as HTMLButtonElement)?.focus()
|
|
initialFocus.value = true
|
|
})
|
|
|
|
useSelectedCellKeydownListener(
|
|
vModel,
|
|
(e: KeyboardEvent) => {
|
|
switch (e.key) {
|
|
case 'Enter':
|
|
if (
|
|
isActiveInputElementExist() ||
|
|
isActiveButtonOrLinkElementExist() ||
|
|
!document.activeElement?.closest('.nc-modal-confirm-wrapper')
|
|
) {
|
|
return
|
|
}
|
|
|
|
emits('ok')
|
|
break
|
|
case 'Tab':
|
|
if (initialFocus.value) {
|
|
e.preventDefault()
|
|
|
|
initialFocus.value = false
|
|
|
|
// if focusBtn provided set first focus to the button on first tab
|
|
if (props.focusBtn === 'cancel') {
|
|
cancelBtnRef.value?.$el?.focus()
|
|
} else {
|
|
okBtnRef.value?.$el?.focus()
|
|
}
|
|
}
|
|
break
|
|
}
|
|
},
|
|
{
|
|
immediate: true,
|
|
isGridCell: false,
|
|
},
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<NcModal v-bind="restProps" v-model:visible="vModel" title="" wrap-class-name="nc-modal-confirm-wrapper">
|
|
<div class="nc-modal-confirm flex flex-col gap-5" :class="[`nc-modal-confirm-type-${type}`]" v-bind="wrapperProps">
|
|
<div class="flex gap-4">
|
|
<div v-if="showIcon" class="nc-modal-confirm-icon-wrapper">
|
|
<slot name="icon">
|
|
<GeneralIcon :icon="iconName" class="nc-confirm-modal-icon" />
|
|
</slot>
|
|
</div>
|
|
<div class="flex-1 flex flex-col gap-2">
|
|
<div class="flex items-start gap-3">
|
|
<div class="nc-modal-confirm-title" :class="titleClass">
|
|
<slot name="title">{{ title }}</slot>
|
|
</div>
|
|
<slot name="headerAction"></slot>
|
|
</div>
|
|
<div v-if="content || $slots.content" class="nc-modal-confirm-content" :class="contentClass">
|
|
<slot name="content">{{ content }}</slot>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<slot name="extraContent"></slot>
|
|
|
|
<div class="flex flex-row w-full justify-end gap-2">
|
|
<NcButton
|
|
v-if="showCancelBtn"
|
|
ref="cancelBtnRef"
|
|
:type="cancelProps?.type ?? 'secondary'"
|
|
size="small"
|
|
class="nc-modal-confirm-cancel-btn"
|
|
:class="cancelClass"
|
|
v-bind="cancelProps"
|
|
:hide-focus="initialFocus"
|
|
@click="onClickCancel"
|
|
>
|
|
{{ cancelText || $t('general.cancel') }}
|
|
</NcButton>
|
|
<NcButton
|
|
v-if="showOkBtn"
|
|
ref="okBtnRef"
|
|
:type="okProps?.type ?? 'primary'"
|
|
size="small"
|
|
class="nc-modal-confirm-ok-btn"
|
|
:class="okClass"
|
|
v-bind="okProps"
|
|
:hide-focus="initialFocus"
|
|
@click="emits('ok')"
|
|
>
|
|
{{ okText || $t('general.ok') }}
|
|
</NcButton>
|
|
</div>
|
|
</div>
|
|
</NcModal>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.nc-modal-confirm {
|
|
.nc-modal-confirm-icon-wrapper {
|
|
@apply flex children:flex-none;
|
|
|
|
.nc-confirm-modal-icon {
|
|
@apply h-6 w-6;
|
|
}
|
|
}
|
|
|
|
.nc-modal-confirm-title {
|
|
@apply text-base text-nc-content-gray font-weight-700 flex-1;
|
|
}
|
|
|
|
.nc-modal-confirm-content {
|
|
@apply text-sm text-nc-content-gray-subtle2 font-weight-500 line-clamp-3;
|
|
}
|
|
|
|
&.nc-modal-confirm-type-success {
|
|
.nc-modal-confirm-icon-wrapper {
|
|
@apply text-green-700;
|
|
}
|
|
}
|
|
|
|
&.nc-modal-confirm-type-error {
|
|
.nc-modal-confirm-icon-wrapper {
|
|
@apply text-red-700;
|
|
}
|
|
}
|
|
|
|
&.nc-modal-confirm-type-warning {
|
|
.nc-modal-confirm-icon-wrapper {
|
|
@apply text-orange-500;
|
|
}
|
|
}
|
|
|
|
&.nc-modal-confirm-type-info {
|
|
.nc-modal-confirm-icon-wrapper {
|
|
@apply text-nc-content-brand;
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<style lang="scss">
|
|
.nc-modal-confirm-wrapper {
|
|
@apply z-1050;
|
|
}
|
|
</style>
|