120 lines
2.9 KiB
Vue
120 lines
2.9 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, onUnmounted } from 'vue'
|
|
|
|
interface CarteNode {
|
|
id: string
|
|
label: string
|
|
family?: string
|
|
nature?: 'essai' | 'projet'
|
|
statut?: 'gestation' | 'edite'
|
|
resume?: string | null
|
|
intention?: string
|
|
}
|
|
|
|
const props = defineProps<{
|
|
node: CarteNode | null
|
|
x: number
|
|
y: number
|
|
}>()
|
|
|
|
const emit = defineEmits<{ close: [] }>()
|
|
|
|
function onKeydown(e: KeyboardEvent) {
|
|
if (e.key === 'Escape' && props.node) emit('close')
|
|
}
|
|
function onClickOutside(e: MouseEvent) {
|
|
const el = document.getElementById('carte-o-context-menu')
|
|
if (el && !el.contains(e.target as Node)) emit('close')
|
|
}
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('keydown', onKeydown)
|
|
setTimeout(() => window.addEventListener('click', onClickOutside), 50)
|
|
})
|
|
onUnmounted(() => {
|
|
window.removeEventListener('keydown', onKeydown)
|
|
window.removeEventListener('click', onClickOutside)
|
|
})
|
|
|
|
const naturLabel = computed(() => props.node?.nature === 'projet' ? 'Projet archi' : 'Essai politique')
|
|
const naturColor = computed(() => props.node?.nature === 'projet' ? '#b45309' : '#1d4ed8')
|
|
const texte = computed(() => props.node?.resume || props.node?.intention || 'En cours d\'ecriture.')
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
v-if="node"
|
|
id="carte-o-context-menu"
|
|
class="context-menu"
|
|
:style="{ left: x + 'px', top: y + 'px' }"
|
|
role="dialog"
|
|
:aria-label="node.label"
|
|
>
|
|
<button class="close-btn" type="button" @click="emit('close')" aria-label="Fermer">x</button>
|
|
<span class="nature-badge" :style="{ backgroundColor: naturColor }">{{ naturLabel }}</span>
|
|
<h3 class="title" :style="{ fontWeight: node.statut === 'edite' ? 'bold' : 'normal' }">
|
|
{{ node.label }}
|
|
</h3>
|
|
<p class="resume">{{ texte }}</p>
|
|
<p v-if="node.statut === 'edite'" class="edite-badge">publie</p>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.context-menu {
|
|
position: absolute;
|
|
z-index: 1000;
|
|
background: #ffffff;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 8px;
|
|
padding: 12px 14px;
|
|
width: 220px;
|
|
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
|
|
font-size: 13px;
|
|
}
|
|
.close-btn {
|
|
position: absolute;
|
|
top: 6px;
|
|
right: 8px;
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
color: #9ca3af;
|
|
font-size: 14px;
|
|
line-height: 1;
|
|
padding: 2px 4px;
|
|
}
|
|
.close-btn:hover { color: #374151; }
|
|
.nature-badge {
|
|
display: inline-block;
|
|
padding: 2px 7px;
|
|
border-radius: 4px;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
color: #fff;
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
margin-bottom: 6px;
|
|
font-family: 'Courier New', Courier, monospace;
|
|
}
|
|
.title {
|
|
font-size: 14px;
|
|
color: #1f2937;
|
|
margin: 4px 0 6px 0;
|
|
line-height: 1.3;
|
|
}
|
|
.resume {
|
|
font-size: 12px;
|
|
color: #4b5563;
|
|
line-height: 1.5;
|
|
margin: 0;
|
|
}
|
|
.edite-badge {
|
|
margin-top: 8px;
|
|
font-size: 10px;
|
|
color: #059669;
|
|
font-family: 'Courier New', Courier, monospace;
|
|
font-weight: 600;
|
|
}
|
|
</style>
|