ptitlutins/app/components/organisms/NodeHeader.vue
2026-06-15 23:34:49 +02:00

183 lines
4.2 KiB
Vue

<template>
<Card
:style="{
padding: '0.8rem 0.85rem',
border: '1.5px solid var(--color-7)',
boxShadow: 'var(--shadow-3)',
}"
>
<div class="node-header__row">
<IconButton
v-if="!isRoot"
icon="carbon:chevron-left"
variant="outline"
size="sm"
:disabled="!prevNode"
:label="
prevNode
? $t('voyage.node.prev', { title: prevNode.event.title })
: $t('voyage.node.first')
"
:style="{ flexShrink: 0 }"
@click="prevNode && $emit('goSibling', prevNode)"
/>
<div class="node-header__center">
<EditableTitle :node="node" @rename="(t) => $emit('rename', node, t)" />
<div v-if="isRoot && node.dateLabel" class="node-header__date">
<Icon
name="carbon:calendar"
size="0.85rem"
:style="{ color: 'var(--color-6)' }"
/>
{{ node.dateLabel }}
</div>
<div class="node-header__pills">
<TimePill
v-if="!isRoot"
:node="node"
@route="$emit('routeTime', node)"
/>
<PlacePill :node="node" @route="$emit('routePlace', node)" />
</div>
<div v-if="isRoot && node.lutins?.length" class="node-header__lutins">
<div class="node-header__avatars">
<Avatar
v-for="(name, i) in node.lutins.slice(0, 4)"
:key="name"
:name="name"
:size="26"
:style="{ marginLeft: i === 0 ? '0' : '-7px' }"
/>
</div>
<span class="node-header__lutins-count"
>{{ node.lutins.length }} lutins</span
>
</div>
</div>
<IconButton
v-if="!isRoot"
icon="carbon:chevron-right"
variant="outline"
size="sm"
:disabled="!nextNode"
:label="
nextNode
? $t('voyage.node.next', { title: nextNode.event.title })
: $t('voyage.node.last')
"
:style="{ flexShrink: 0 }"
@click="nextNode && $emit('goSibling', nextNode)"
/>
</div>
<div v-if="!isRoot && total > 1" class="node-header__dots">
<div class="node-header__dots-track">
<span
v-for="(_, i) in total"
:key="i"
class="node-header__dot"
:style="{
width: i === index ? '16px' : '6px',
background: i === index ? 'var(--color-5)' : 'var(--neutral-30)',
}"
/>
</div>
<span class="node-header__dots-count">{{ index + 1 }} / {{ total }}</span>
</div>
</Card>
</template>
<script setup lang="ts">
import type { Node } from "~/types"
const props = defineProps<{
node: Node
prevNode: Node | null
nextNode: Node | null
index: number
total: number
}>()
defineEmits<{
rename: [node: Node, title: string]
routeTime: [node: Node]
routePlace: [node: Node]
goSibling: [node: Node]
}>()
const isRoot = computed(() => props.node.root)
</script>
<style scoped>
.node-header__row {
display: flex;
align-items: center;
gap: var(--space-2);
}
.node-header__center {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-2);
}
.node-header__date {
display: inline-flex;
align-items: center;
gap: 5px;
font-family: var(--font-mono);
font-size: 0.74rem;
color: var(--text-muted);
}
.node-header__pills {
display: flex;
gap: 6px;
justify-content: center;
flex-wrap: wrap;
}
.node-header__lutins {
display: flex;
align-items: center;
gap: 6px;
margin-top: 2px;
}
.node-header__avatars {
display: flex;
}
.node-header__lutins-count {
font-family: var(--font-body);
font-size: 0.76rem;
color: var(--text-muted);
}
.node-header__dots {
margin-top: var(--space-2);
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
}
.node-header__dots-track {
display: flex;
gap: var(--space-1);
}
.node-header__dot {
height: 6px;
border-radius: var(--radius-pill);
transition: width 140ms ease;
}
.node-header__dots-count {
font-family: var(--font-mono);
font-size: 0.66rem;
color: var(--text-faint);
}
</style>