This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div class="flex h-full shrink-0">
|
||||
<Transition name="drawer-backdrop">
|
||||
<button
|
||||
v-if="modelValue"
|
||||
v-if="isMobile && 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"
|
||||
@@ -10,18 +10,19 @@
|
||||
/>
|
||||
</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 }"
|
||||
class="nav-drawer flex h-full flex-col bg-kestrel-surface transition-[width] duration-200 ease-out md:relative md:translate-x-0"
|
||||
:class="[
|
||||
isMobile && !modelValue ? 'fixed left-0 top-14 z-30 -translate-x-full' : 'fixed left-0 top-14 z-30 md:relative md:top-0',
|
||||
showCollapsed ? 'w-16' : 'w-[260px]',
|
||||
]"
|
||||
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 shadow-glow-header"
|
||||
v-if="isMounted && isMobile"
|
||||
class="flex shrink-0 items-center justify-end border-b border-kestrel-border bg-kestrel-surface px-2 py-1"
|
||||
>
|
||||
<h2 class="text-sm font-medium uppercase tracking-wider text-kestrel-muted">
|
||||
Navigation
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="kestrel-close-btn"
|
||||
@@ -31,7 +32,7 @@
|
||||
<span class="text-xl leading-none">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<nav class="flex-1 overflow-auto py-2">
|
||||
<nav class="flex-1 overflow-auto bg-kestrel-surface py-2">
|
||||
<ul class="space-y-0.5 px-2">
|
||||
<li
|
||||
v-for="item in navItems"
|
||||
@@ -39,50 +40,91 @@
|
||||
>
|
||||
<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-glow-sm'
|
||||
: 'border-l-2 border-transparent text-kestrel-muted hover:bg-kestrel-border hover:text-kestrel-text'"
|
||||
@click="close"
|
||||
class="flex items-center gap-3 rounded px-3 py-2 text-sm transition-colors"
|
||||
:class="[
|
||||
showCollapsed ? 'justify-center px-2' : '',
|
||||
isActive(item.to)
|
||||
? 'bg-kestrel-surface-hover font-medium text-kestrel-accent text-shadow-glow-sm'
|
||||
: 'text-kestrel-muted hover:bg-kestrel-border hover:text-kestrel-text',
|
||||
!showCollapsed && (isActive(item.to) ? 'border-l-2 border-kestrel-accent' : 'border-l-2 border-transparent'),
|
||||
]"
|
||||
:title="showCollapsed ? item.label : undefined"
|
||||
@click="isMobile ? close() : undefined"
|
||||
>
|
||||
{{ item.label }}
|
||||
<Icon
|
||||
:name="item.icon"
|
||||
class="size-5 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
v-show="!showCollapsed"
|
||||
class="truncate"
|
||||
>{{ item.label }}</span>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div
|
||||
v-if="isMounted && !isMobile"
|
||||
class="shrink-0 border-t border-kestrel-border bg-kestrel-surface py-2"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center gap-3 rounded px-3 py-2 text-sm text-kestrel-muted transition-colors hover:bg-kestrel-border hover:text-kestrel-text"
|
||||
:class="showCollapsed ? 'justify-center px-2' : ''"
|
||||
:aria-label="showCollapsed ? 'Expand sidebar' : 'Collapse sidebar'"
|
||||
@click="toggleCollapsed"
|
||||
>
|
||||
<Icon
|
||||
:name="showCollapsed ? 'tabler:chevron-right' : 'tabler:chevron-left'"
|
||||
class="size-5 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span v-show="!showCollapsed">Collapse sidebar</span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
const props = defineProps({
|
||||
modelValue: { type: Boolean, default: false },
|
||||
collapsed: { type: Boolean, default: false },
|
||||
isMobile: { type: Boolean, default: true },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const emit = defineEmits(['update:modelValue', 'update:collapsed'])
|
||||
|
||||
const isMounted = ref(false)
|
||||
const route = useRoute()
|
||||
const { canEditPois } = useUser()
|
||||
|
||||
const NAV_ITEMS = Object.freeze([
|
||||
{ to: '/', label: 'Map', icon: 'tabler:map' },
|
||||
{ to: '/cameras', label: 'Cameras', icon: 'tabler:video' },
|
||||
{ to: '/poi', label: 'POI', icon: 'tabler:map-pin' },
|
||||
{ to: '/members', label: 'Members', icon: 'tabler:users' },
|
||||
{ to: '/account', label: 'Account', icon: 'tabler:user-circle' },
|
||||
{ to: '/settings', label: 'Settings', icon: 'tabler:settings' },
|
||||
])
|
||||
|
||||
const SHARE_LIVE_ITEM = { to: '/share-live', label: 'Share live', icon: 'tabler:live-photo' }
|
||||
|
||||
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
|
||||
if (!canEditPois.value) return NAV_ITEMS
|
||||
const list = [...NAV_ITEMS]
|
||||
list.splice(3, 0, SHARE_LIVE_ITEM)
|
||||
return list
|
||||
})
|
||||
|
||||
const isActive = to => to === '/' ? route.path === '/' : route.path.startsWith(to)
|
||||
const showCollapsed = computed(() => props.collapsed && !props.isMobile)
|
||||
|
||||
function toggleCollapsed() {
|
||||
emit('update:collapsed', !props.collapsed)
|
||||
}
|
||||
|
||||
const isActive = to => (to === '/' ? route.path === '/' : route.path.startsWith(to))
|
||||
|
||||
function close() {
|
||||
emit('update:modelValue', false)
|
||||
@@ -95,6 +137,7 @@ function onEscape(e) {
|
||||
defineExpose({ close })
|
||||
|
||||
onMounted(() => {
|
||||
isMounted.value = true
|
||||
document.addEventListener('keydown', onEscape)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user