feat(v2): NexusLayout, Sidebar, NavGroup, NavItem, Topbar, FlowBoard placeholder
CI - Build & Test / Backend (.NET) (push) Failing after 22s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 14s
CI - Build & Test / Security Check (push) Successful in 2s

This commit is contained in:
2026-06-12 00:20:27 +02:00
parent f378d7aed4
commit 3672e56994
9 changed files with 725 additions and 1 deletions
+164
View File
@@ -0,0 +1,164 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '../../stores/auth'
import { navigation, icons } from '../../composables/icons'
import NavGroup from './NavGroup.vue'
const auth = useAuthStore()
const router = useRouter()
const ownerInitials = computed(() =>
auth.user?.displayName?.split(' ').map(p => p[0]).join('').slice(0, 2).toUpperCase()
?? 'OW'
)
function logout() {
auth.logout()
router.replace('/login')
}
</script>
<template>
<aside class="sidebar">
<!-- Brand -->
<div class="side-top">
<div class="brand-mark" v-html="icons.command || ''"></div>
<div>
<div class="brand-name">NEXUS</div>
<div class="brand-sub">Mission Control</div>
</div>
</div>
<!-- Navigation -->
<nav class="nav">
<NavGroup
v-for="(group, idx) in navigation"
:key="idx"
:label="group.group"
:items="group.items"
/>
</nav>
<!-- Footer -->
<div class="side-foot">
<div class="avatar">{{ ownerInitials }}</div>
<div class="owner-info">
<div class="owner-name">{{ auth.user?.displayName ?? 'Owner' }}</div>
<div class="owner-role">{{ auth.user?.role ?? 'Owner' }}</div>
</div>
</div>
</aside>
</template>
<style scoped>
.sidebar {
width: 248px;
flex: 0 0 248px;
height: 100vh;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, rgba(14,12,32,.92), rgba(8,6,20,.92));
border-right: 1px solid var(--line);
backdrop-filter: blur(14px);
padding: 0;
position: relative;
z-index: 2;
}
.side-top {
display: flex;
align-items: center;
gap: 11px;
padding: 18px 18px 16px;
}
.brand-mark {
width: 38px;
height: 38px;
border-radius: 11px;
display: grid;
place-items: center;
background: var(--grad);
box-shadow: var(--glow-purple);
flex: 0 0 auto;
}
.brand-mark :deep(svg) {
width: 20px;
height: 20px;
color: #fff;
}
.brand-name {
font-family: 'Space Grotesk', sans-serif;
font-weight: 700;
font-size: 17px;
letter-spacing: .14em;
line-height: 1;
}
.brand-sub {
font-size: 10.5px;
color: var(--tx-3);
letter-spacing: .05em;
margin-top: 3px;
}
.nav {
flex: 1;
overflow-y: auto;
padding: 6px 12px 12px;
display: flex;
flex-direction: column;
gap: 2px;
}
.side-foot {
padding: 12px;
border-top: 1px solid var(--line);
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
transition: background .15s;
}
.side-foot:hover {
background: rgba(124,108,255,.06);
}
.avatar {
width: 34px;
height: 34px;
border-radius: 10px;
background: var(--grad-soft);
border: 1px solid var(--line-2);
display: grid;
place-items: center;
font-weight: 700;
font-size: 13px;
color: var(--tx);
flex-shrink: 0;
}
.owner-info {
min-width: 0;
}
.owner-name {
font-size: 12px;
font-weight: 600;
color: var(--tx);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.owner-role {
font-size: 10px;
color: var(--tx-3);
margin-top: 1px;
text-transform: capitalize;
}
</style>