Files
veridian/app/components/Sidenav/NavHome.vue
T
2026-01-11 05:04:29 -06:00

102 lines
4.1 KiB
Vue

<script setup lang="ts">
const { agents, createAgent } = await useAgents()
const route = useRoute()
const agentsListRef = ref<HTMLElement | null>(null)
const agentsOpen = ref(true)
const agentsListHeight = ref('auto')
const agentsListOpacity = ref(1)
const agentsListScale = ref(1)
const creatingAgent = ref(false)
function easeInOutQuad(x: number): number {
return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2;
}
const toggleAgentsList = () => {
if (!agentsListRef.value) return;
let animationLength = 200;
let animationStart: number | null = null;
let startHeight: number;
let startOpacity = agentsListOpacity.value;
let startScale = agentsListScale.value;
if (agentsListHeight.value === 'auto') {
startHeight = agentsListRef.value.clientHeight;
} else {
startHeight = Number(agentsListHeight.value.replace('px', ''));
}
let targetHeight = agentsOpen.value ? 0 : agentsListRef.value.scrollHeight;
let targetOpacity = agentsOpen.value ? 0 : 1;
let targetScale = agentsOpen.value ? 0.95 : 1;
agentsOpen.value = !agentsOpen.value;
const animate = (timestamp: number) => {
if (!animationStart) animationStart = timestamp;
const elapsed = timestamp - animationStart;
const progress = Math.min(elapsed / animationLength, 1);
const currentHeight = startHeight + (targetHeight - startHeight) * easeInOutQuad(progress);
const currentOpacity = startOpacity + (targetOpacity - startOpacity) * easeInOutQuad(progress);
const currentScale = startScale + (targetScale - startScale) * easeInOutQuad(progress);
agentsListOpacity.value = currentOpacity;
agentsListScale.value = currentScale;
agentsListHeight.value = `${currentHeight}px`;
if (progress < 1) {
requestAnimationFrame(animate);
} else {
if (agentsOpen.value) {
agentsListHeight.value = 'auto';
}
}
};
requestAnimationFrame(animate);
}
const newAgent = async () => {
creatingAgent.value = true;
const agent = await createAgent();
creatingAgent.value = false;
navigateTo(`/agent/${agent!.id}`);
}
</script>
<template>
<nav class="flex flex-col gap-1">
<SidenavItem name="Search" icon="mynaui:search" />
<NuxtLink to="/" class="decoration-none text-[var(--color-text)]">
<SidenavItem class="bg-[var(--color-highlight)]" name="Home" icon="mynaui:home" />
</NuxtLink>
<!-- Agents Section -->
<div class="relative group">
<div class="flex items-center justify-between px-2 h-9 rounded-lg hover:bg-[var(--color-highlight)] transition-colors cursor-pointer"
@click="toggleAgentsList()">
<div class="flex items-center gap-0.5">
<span class="text-sm">Agents</span>
<Icon name="mynaui:chevron-right-solid"
:class="['transition-transform duration-200 ease-in-out transform-origin-center', agentsOpen ? 'rotate-90' : '']" />
</div>
<button aria-label="create new agent"
class="opacity-0 group-hover:opacity-100 p-1 rounded-md transition-all bg-transparent hover:bg-[var(--color-highlight)] text-[var(--color-subtle)] active:text-[var(--color-text)]"
@click.stop="newAgent">
<Icon v-if="!creatingAgent" name="mynaui:plus" class="w-4 h-4" />
<Icon v-else name="svg-spinners:ring-resize" class="w-4 h-4" />
</button>
</div>
<div ref="agentsListRef"
:style="{ height: agentsListHeight, opacity: agentsListOpacity, transform: `scale(${agentsListScale})` }"
class="mt-1 gap-1 flex flex-col overflow-hidden transform-origin-center-top">
<NuxtLink v-for="agent in agents" :to="`/agent/${agent.id}`" :key="agent.id"
class="decoration-none text-[var(--color-subtle)]">
<SidenavItem :name="agent.name" icon="mynaui:check-hexagon" />
</NuxtLink>
</div>
</div>
</nav>
</template>