156 lines
3.3 KiB
Vue
156 lines
3.3 KiB
Vue
<template>
|
|
<Teleport to="body">
|
|
<Transition name="confirm-dialog">
|
|
<div v-if="open" class="confirm-dialog__overlay" @click.self="$emit('cancel')">
|
|
<div
|
|
class="confirm-dialog"
|
|
role="alertdialog"
|
|
aria-modal="true"
|
|
:aria-label="title"
|
|
@keydown.escape="$emit('cancel')"
|
|
>
|
|
<p class="confirm-dialog__title">{{ title }}</p>
|
|
<p class="confirm-dialog__message">{{ message }}</p>
|
|
<div class="confirm-dialog__actions">
|
|
<button
|
|
ref="cancelBtn"
|
|
class="confirm-dialog__btn confirm-dialog__btn--cancel"
|
|
type="button"
|
|
@click="$emit('cancel')"
|
|
>
|
|
{{ cancelLabel }}
|
|
</button>
|
|
<button
|
|
class="confirm-dialog__btn confirm-dialog__btn--confirm"
|
|
type="button"
|
|
@click="$emit('confirm')"
|
|
>
|
|
{{ confirmLabel }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, watch, nextTick } from 'vue'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
open: boolean
|
|
title?: string
|
|
message?: string
|
|
confirmLabel?: string
|
|
cancelLabel?: string
|
|
}>(),
|
|
{
|
|
title: 'Are you sure?',
|
|
message: '',
|
|
confirmLabel: 'Remove',
|
|
cancelLabel: 'Cancel',
|
|
},
|
|
)
|
|
|
|
defineEmits<{
|
|
confirm: []
|
|
cancel: []
|
|
}>()
|
|
|
|
const cancelBtn = ref<HTMLButtonElement | null>(null)
|
|
|
|
watch(
|
|
() => props.open,
|
|
async (isOpen) => {
|
|
if (isOpen) {
|
|
await nextTick()
|
|
cancelBtn.value?.focus()
|
|
}
|
|
},
|
|
)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.confirm-dialog__overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: var(--color-glass-overlay);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
padding: var(--spacing-lg);
|
|
}
|
|
|
|
.confirm-dialog {
|
|
background: linear-gradient(135deg, var(--color-glass-strong) 0%, var(--color-glass-subtle) 100%);
|
|
border: 1px solid var(--color-glass-border);
|
|
backdrop-filter: blur(24px);
|
|
-webkit-backdrop-filter: blur(24px);
|
|
border-radius: var(--radius-card);
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
padding: var(--spacing-xl);
|
|
max-width: 320px;
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-md);
|
|
}
|
|
|
|
.confirm-dialog__title {
|
|
font-size: 1.05rem;
|
|
font-weight: 700;
|
|
color: var(--color-text-on-gradient);
|
|
}
|
|
|
|
.confirm-dialog__message {
|
|
font-size: 0.9rem;
|
|
font-weight: 400;
|
|
color: var(--color-text-soft);
|
|
}
|
|
|
|
.confirm-dialog__actions {
|
|
display: flex;
|
|
gap: var(--spacing-sm);
|
|
justify-content: flex-end;
|
|
margin-top: var(--spacing-xs);
|
|
}
|
|
|
|
.confirm-dialog__btn {
|
|
padding: 0.5rem 1rem;
|
|
border: none;
|
|
border-radius: var(--radius-button);
|
|
font-family: inherit;
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: opacity 0.15s ease;
|
|
}
|
|
|
|
.confirm-dialog__btn:hover {
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.confirm-dialog__btn--cancel {
|
|
background: var(--color-glass);
|
|
border: 1px solid var(--color-glass-border);
|
|
color: var(--color-text-on-gradient);
|
|
}
|
|
|
|
.confirm-dialog__btn--confirm {
|
|
background: #d32f2f;
|
|
color: #fff;
|
|
}
|
|
|
|
.confirm-dialog-enter-active,
|
|
.confirm-dialog-leave-active {
|
|
transition: opacity 0.15s ease;
|
|
}
|
|
|
|
.confirm-dialog-enter-from,
|
|
.confirm-dialog-leave-to {
|
|
opacity: 0;
|
|
}
|
|
</style>
|