initial commit
This commit is contained in:
125
app/components/NavDrawer.vue
Normal file
125
app/components/NavDrawer.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<Transition name="drawer-backdrop">
|
||||
<button
|
||||
v-if="modelValue"
|
||||
type="button"
|
||||
class="fixed inset-0 z-20 block h-full w-full border-0 bg-black/50 p-0 md:hidden"
|
||||
aria-label="Close navigation"
|
||||
@click="close"
|
||||
/>
|
||||
</Transition>
|
||||
<aside
|
||||
class="nav-drawer fixed left-0 top-0 z-30 flex h-full w-[260px] flex-col border-r border-kestrel-border bg-kestrel-surface transition-transform duration-200 ease-out"
|
||||
:class="{ '-translate-x-full': !modelValue }"
|
||||
role="navigation"
|
||||
aria-label="Main navigation"
|
||||
:aria-expanded="modelValue"
|
||||
>
|
||||
<div
|
||||
class="flex h-14 shrink-0 items-center justify-between border-b border-kestrel-border bg-kestrel-surface px-4 shadow-glow-sm [box-shadow:0_0_20px_-4px_rgba(34,201,201,0.15)]"
|
||||
>
|
||||
<h2 class="text-sm font-medium uppercase tracking-wider text-kestrel-muted">
|
||||
Navigation
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded p-1 text-kestrel-muted transition-colors hover:bg-kestrel-border hover:text-kestrel-accent"
|
||||
aria-label="Close navigation"
|
||||
@click="close"
|
||||
>
|
||||
<span class="text-xl leading-none">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<nav class="flex-1 overflow-auto py-2">
|
||||
<ul class="space-y-0.5 px-2">
|
||||
<li
|
||||
v-for="item in navItems"
|
||||
:key="item.to"
|
||||
>
|
||||
<NuxtLink
|
||||
:to="item.to"
|
||||
class="block rounded px-3 py-2 text-sm transition-colors"
|
||||
:class="isActive(item.to)
|
||||
? 'border-l-2 border-kestrel-accent bg-kestrel-surface-hover font-medium text-kestrel-accent [text-shadow:0_0_8px_rgba(34,201,201,0.25)]'
|
||||
: 'border-l-2 border-transparent text-kestrel-muted hover:bg-kestrel-border hover:text-kestrel-text'"
|
||||
@click="close"
|
||||
>
|
||||
{{ item.label }}
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const route = useRoute()
|
||||
const { canEditPois } = useUser()
|
||||
|
||||
const navItems = computed(() => {
|
||||
const items = [
|
||||
{ to: '/', label: 'Map' },
|
||||
{ to: '/account', label: 'Account' },
|
||||
{ to: '/cameras', label: 'Cameras' },
|
||||
{ to: '/poi', label: 'POI' },
|
||||
{ to: '/members', label: 'Members' },
|
||||
{ to: '/settings', label: 'Settings' },
|
||||
]
|
||||
if (canEditPois.value) {
|
||||
items.splice(1, 0, { to: '/share-live', label: 'Share live' })
|
||||
}
|
||||
return items
|
||||
})
|
||||
|
||||
const isActive = to => to === '/' ? route.path === '/' : route.path.startsWith(to)
|
||||
|
||||
function close() {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
function onEscape(e) {
|
||||
if (e.key === 'Escape') close()
|
||||
}
|
||||
|
||||
defineExpose({ close })
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', onEscape)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('keydown', onEscape)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.drawer-backdrop-enter-active,
|
||||
.drawer-backdrop-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.drawer-backdrop-enter-from,
|
||||
.drawer-backdrop-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Same elevation as content: no right-edge shadow on desktop so drawer and navbar read as one layer */
|
||||
.nav-drawer {
|
||||
box-shadow: 8px 0 24px -4px rgba(34, 201, 201, 0.12);
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.nav-drawer {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user