Files
innovatieplatform/resources/js/Components/MetroMap/FloatingActions.vue
znetsixe 6711cd01a3 Replace demo data with 2026 R&D planning, fix zoom and dimension-aware creation
Seeder: Replace 12 demo projects with 6 real 2026 projects from Planning PPTX:
- BRIDGE (Pilot Klundert), CRISP (Compressor Aanbesteding), WISE (Monsternamekast),
  Gemaal 3.0, Afvlakkingsregeling, Structuur & Borging
- 4 strategic themes: Architectuur, Productiewaardig, Lab, Governance
- Real team members, commitments, documents, and dependencies

MetroCanvas: Fix zoom-out scaling
- Wider transition range (0.6→0.25 instead of 0.5→0.1) for smoother feel
- Animated zoom reset on dimension commit (400ms ease) instead of jarring snap
- Guard against re-entry during transitions with isCommitting flag
- Expose dimension metadata (parentEntityType/Id/Name) for parent components

FloatingActions: Dimension-aware creation
- Shows "Nieuw commitment/document" when inside a project dimension
- Shows "Nieuw project/thema" at root level
- Receives depth and parentEntityType props from MetroMap

MetroMap: Wire dimension tracking
- Tracks canvasDepth/canvasDimension from MetroCanvas dimension-change events
- Updates breadcrumb for both page-level and canvas-level navigation
- Passes dimension context to FloatingActions and CommitmentForm

MapDataService: Add parent metadata to buildProjectChildren output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 08:50:51 +02:00

185 lines
4.2 KiB
Vue

<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
depth: { type: Number, default: 1 },
parentEntityType: { type: String, default: null },
parentProjectId: { type: Number, default: null },
})
const emit = defineEmits([
'create-project',
'create-theme',
'create-commitment',
'create-document',
])
const menuOpen = ref(false)
const toggle = () => {
menuOpen.value = !menuOpen.value
}
/** Options change based on which dimension we're in */
const menuItems = computed(() => {
if (props.depth > 1 && props.parentEntityType === 'project') {
// Inside a project dimension: create project-level items
return [
{ label: 'Nieuw commitment', event: 'create-commitment' },
{ label: 'Nieuw document', event: 'create-document' },
]
}
// Root dimension: create top-level items
return [
{ label: 'Nieuw project', event: 'create-project' },
{ label: 'Nieuw thema', event: 'create-theme' },
]
})
const handleItemClick = (item) => {
menuOpen.value = false
emit(item.event)
}
</script>
<template>
<div class="fab-container">
<!-- Expanded menu -->
<Transition name="fab-menu">
<div v-if="menuOpen" class="fab-menu">
<button
v-for="item in menuItems"
:key="item.event"
class="fab-menu-item"
@click="handleItemClick(item)"
>
<span class="fab-menu-icon">+</span>
<span class="fab-menu-label">{{ item.label }}</span>
</button>
</div>
</Transition>
<!-- Main FAB button -->
<button
class="fab-btn"
:class="{ 'fab-btn--open': menuOpen }"
:title="menuOpen ? 'Sluiten' : 'Nieuw aanmaken'"
@click="toggle"
>
<span class="fab-icon">{{ menuOpen ? '[X]' : '[+]' }}</span>
</button>
</div>
</template>
<style scoped>
.fab-container {
position: fixed;
bottom: 64px;
right: 20px;
z-index: 150;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 8px;
}
.fab-btn {
width: 48px;
height: 48px;
border-radius: 50%;
background: #0f3460;
border: 2px solid #00d2ff;
color: #00d2ff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow:
0 0 12px rgba(0, 210, 255, 0.35),
0 0 24px rgba(0, 210, 255, 0.15);
transition: all 0.2s ease;
}
.fab-btn:hover {
background: rgba(0, 210, 255, 0.15);
box-shadow:
0 0 20px rgba(0, 210, 255, 0.5),
0 0 40px rgba(0, 210, 255, 0.2);
transform: scale(1.08);
}
.fab-btn--open {
border-color: #e94560;
color: #e94560;
box-shadow:
0 0 12px rgba(233, 69, 96, 0.35),
0 0 24px rgba(233, 69, 96, 0.15);
}
.fab-btn--open:hover {
box-shadow:
0 0 20px rgba(233, 69, 96, 0.5),
0 0 40px rgba(233, 69, 96, 0.2);
}
.fab-icon {
font-family: 'VT323', monospace;
font-size: 18px;
line-height: 1;
text-shadow: 0 0 8px currentColor;
}
.fab-menu {
display: flex;
flex-direction: column;
gap: 6px;
align-items: flex-end;
}
.fab-menu-item {
display: flex;
align-items: center;
gap: 8px;
background: #16213e;
border: 1px solid rgba(0, 210, 255, 0.4);
border-radius: 4px;
padding: 8px 14px;
cursor: pointer;
white-space: nowrap;
transition: all 0.15s;
box-shadow: 0 0 10px rgba(0, 210, 255, 0.1);
}
.fab-menu-item:hover {
background: rgba(0, 210, 255, 0.1);
border-color: #00d2ff;
box-shadow: 0 0 16px rgba(0, 210, 255, 0.25);
}
.fab-menu-icon {
font-family: 'VT323', monospace;
font-size: 16px;
color: #00d2ff;
text-shadow: 0 0 6px rgba(0, 210, 255, 0.5);
}
.fab-menu-label {
font-family: 'VT323', monospace;
font-size: 16px;
color: #e8e8e8;
letter-spacing: 0.5px;
}
/* Menu transition */
.fab-menu-enter-active,
.fab-menu-leave-active {
transition: opacity 0.15s ease, transform 0.15s ease;
}
.fab-menu-enter-from,
.fab-menu-leave-to {
opacity: 0;
transform: translateY(8px) scale(0.97);
}
</style>