Files
kite/frontend/src/components/layout/OsDragOverlay.vue
T
curo1305 20eceb8983 feat(10-10): implement OsDragOverlay.vue — OS file drag detection with depth counter
- Options API component; emits files-dropped with file list
- Depth-counter pattern (D-16) prevents flicker across child element boundaries
- Guards against in-app drags via dataTransfer.types.includes('Files') check
- window addEventListener/removeEventListener in mounted/beforeUnmount
- Teleport to body; z-[9998] keeps overlay below ToastContainer z-[9999]
- Fade transition via scoped CSS (.fade-enter-active/.fade-leave-active)
- 8/8 tests GREEN
2026-06-15 20:44:39 +02:00

77 lines
2.0 KiB
Vue

<template>
<Teleport to="body">
<Transition name="fade">
<div
v-if="showOverlay"
class="fixed inset-0 z-[9998] bg-indigo-900/40 flex items-center justify-center pointer-events-none"
data-test="os-drag-overlay"
>
<div class="bg-white rounded-2xl px-10 py-8 text-center shadow-xl pointer-events-none">
<AppIcon name="upload" class="w-10 h-10 text-indigo-400 mx-auto mb-3" />
<p class="text-base font-semibold text-gray-800">Drop files to upload</p>
</div>
</div>
</Transition>
</Teleport>
</template>
<script>
import AppIcon from '../ui/AppIcon.vue'
export default {
name: 'OsDragOverlay',
components: { AppIcon },
emits: ['files-dropped'],
data() {
return {
dragDepth: 0,
showOverlay: false,
}
},
methods: {
onDragEnter(e) {
if (!e.dataTransfer?.types.includes('Files')) return
this.dragDepth++
this.showOverlay = true
},
onDragLeave() {
this.dragDepth = Math.max(0, this.dragDepth - 1)
if (this.dragDepth === 0) this.showOverlay = false
},
onDragOver(e) {
e.preventDefault()
},
onDrop(e) {
e.preventDefault()
this.dragDepth = 0
this.showOverlay = false
const files = Array.from(e.dataTransfer.files)
if (files.length) this.$emit('files-dropped', files)
},
},
mounted() {
window.addEventListener('dragenter', this.onDragEnter)
window.addEventListener('dragleave', this.onDragLeave)
window.addEventListener('dragover', this.onDragOver)
window.addEventListener('drop', this.onDrop)
},
beforeUnmount() {
window.removeEventListener('dragenter', this.onDragEnter)
window.removeEventListener('dragleave', this.onDragLeave)
window.removeEventListener('dragover', this.onDragOver)
window.removeEventListener('drop', this.onDrop)
},
}
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.15s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>