Initialer Laravel Commit für BetiX
This commit is contained in:
104
resources/js/components/ui/ConfirmModal.vue
Normal file
104
resources/js/components/ui/ConfirmModal.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
requireHold?: boolean; // If true, user must hold button for 3s
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['close', 'confirm']);
|
||||
|
||||
const holdProgress = ref(0);
|
||||
let holdInterval: number | undefined;
|
||||
|
||||
const startHold = () => {
|
||||
if (!props.requireHold) return;
|
||||
holdProgress.value = 0;
|
||||
holdInterval = window.setInterval(() => {
|
||||
holdProgress.value += 2; // 50 * 2 = 100% in ~2.5s (adjusted for UX)
|
||||
if (holdProgress.value >= 100) {
|
||||
stopHold();
|
||||
emit('confirm');
|
||||
}
|
||||
}, 50); // Update every 50ms
|
||||
};
|
||||
|
||||
const stopHold = () => {
|
||||
if (holdInterval) {
|
||||
clearInterval(holdInterval);
|
||||
holdInterval = undefined;
|
||||
}
|
||||
if (holdProgress.value < 100) {
|
||||
holdProgress.value = 0;
|
||||
}
|
||||
};
|
||||
|
||||
const onConfirmClick = () => {
|
||||
if (!props.requireHold) {
|
||||
emit('confirm');
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.isOpen, (val) => {
|
||||
if (!val) {
|
||||
stopHold();
|
||||
holdProgress.value = 0;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<div v-if="isOpen" class="modal-overlay">
|
||||
<div class="modal-card">
|
||||
<div class="modal-head">{{ title }}</div>
|
||||
<div class="modal-body">{{ message }}</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-cancel" @click="$emit('close')">{{ cancelText || 'Cancel' }}</button>
|
||||
|
||||
<button
|
||||
class="btn-confirm"
|
||||
:class="{ 'holding': requireHold }"
|
||||
@mousedown="startHold"
|
||||
@mouseup="stopHold"
|
||||
@mouseleave="stopHold"
|
||||
@touchstart.prevent="startHold"
|
||||
@touchend.prevent="stopHold"
|
||||
@click="onConfirmClick"
|
||||
>
|
||||
<div v-if="requireHold" class="hold-fill" :style="{ width: `${holdProgress}%` }"></div>
|
||||
<span class="btn-text">{{ confirmText || 'Confirm' }}</span>
|
||||
<span v-if="requireHold && holdProgress > 0" class="hold-hint">Hold...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.8); backdrop-filter: blur(5px); z-index: 9999; display: flex; align-items: center; justify-content: center; }
|
||||
.modal-card { background: #0a0a0a; border: 1px solid #222; border-radius: 16px; padding: 24px; width: 90%; max-width: 400px; box-shadow: 0 20px 50px rgba(0,0,0,0.8); animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
|
||||
.modal-head { font-size: 16px; font-weight: 900; color: #fff; margin-bottom: 10px; text-transform: uppercase; letter-spacing: 1px; }
|
||||
.modal-body { font-size: 13px; color: #bbb; margin-bottom: 24px; line-height: 1.5; }
|
||||
.modal-actions { display: flex; gap: 12px; justify-content: flex-end; }
|
||||
|
||||
.btn-cancel { background: transparent; border: 1px solid #333; color: #888; padding: 10px 16px; border-radius: 8px; font-weight: 700; cursor: pointer; transition: 0.2s; }
|
||||
.btn-cancel:hover { color: #fff; border-color: #555; }
|
||||
|
||||
.btn-confirm { background: #ff007a; border: none; color: #fff; padding: 10px 20px; border-radius: 8px; font-weight: 900; cursor: pointer; position: relative; overflow: hidden; transition: 0.2s; }
|
||||
.btn-confirm:hover { box-shadow: 0 0 15px rgba(255,0,122,0.4); }
|
||||
.btn-confirm.holding { background: #33001a; border: 1px solid #ff007a; }
|
||||
|
||||
.hold-fill { position: absolute; top: 0; left: 0; height: 100%; background: #ff007a; transition: width 0.05s linear; z-index: 0; }
|
||||
.btn-text { position: relative; z-index: 1; }
|
||||
.hold-hint { position: absolute; right: 10px; font-size: 9px; opacity: 0.7; z-index: 1; top: 50%; transform: translateY(-50%); }
|
||||
|
||||
@keyframes popIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
|
||||
.fade-enter-active, .fade-leave-active { transition: opacity 0.2s; }
|
||||
.fade-enter-from, .fade-leave-to { opacity: 0; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user