Skip to content

Commit

Permalink
feat: sidenav overflow as popup
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed May 16, 2023
1 parent 3311f11 commit da6c29f
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 41 deletions.
98 changes: 84 additions & 14 deletions packages/devtools/client/components/SideNav.vue
@@ -1,21 +1,41 @@
<script setup lang="ts">
const client = useClient()
const categories = useCategorizedTabs()
const allTabs = useEnabledTabs()
const show = ref(false)
const showDocking = ref(false)
const showMoreTabs = ref(false)
const panel = ref()
const button = ref<HTMLButtonElement>()
function toggle() {
show.value = !show.value
function toggleDocking() {
showDocking.value = !showDocking.value
}
function toggleMoreTabs() {
showMoreTabs.value = !showMoreTabs.value
}
const ITEM_HEIGHT = 45
const { height: windowHeight } = useWindowSize()
const containerCapacity = computed(() => {
const containerHeight = windowHeight.value - 130
return Math.max(0, Math.floor(containerHeight / ITEM_HEIGHT))
})
const inlineTabs = computed(() => allTabs.value.slice(0, containerCapacity.value))
const overflowTabs = computed(() => allTabs.value.slice(containerCapacity.value))
const categorizedInlineTabs = getCategorizedTabs(inlineTabs)
const categorizedOverflowTabs = getCategorizedTabs(overflowTabs)
onClickOutside(
panel,
(e) => {
if (e.target === button.value)
return
show.value = false
showDocking.value = false
showMoreTabs.value = false
},
{ detectIframe: true },
)
Expand All @@ -26,27 +46,33 @@ onClickOutside(
<div flex="~ none col items-center">
<VDropdown
placement="left-start"
:distance="20"
:distance="12"
:skidding="5"
:triggers="[]"
:shown="show"
:shown="showDocking"
:auto-hide="true"
>
<button
ref="button"
i-logos-nuxt-icon my3 h-6 w-6 pb-2 text-lg outline-none
flex="~"
hover="bg-active"
relative my1 h-10 w-10 select-none items-center justify-center rounded-xl p1 text-secondary
exact-active-class="!text-primary bg-active"
:class="client ? '' : 'saturate-0'"
:title="client ? 'Nuxt DevTools' : 'DevTools Client not connected, try open it in iframe mode'"
@click="toggle"
/>
@click="toggleDocking"
>
<div i-logos-nuxt-icon h-6 w-6 />
</button>
<template #popper>
<DockingPanel ref="panel" />
</template>
</VDropdown>
<div h-1px w-8 border="b base" />
</div>

<div flex="~ auto col gap-0.5 items-center" of-auto class="no-scrollbar" py1>
<template v-for="[name, tabs], idx of categories" :key="name">
<div flex="~ auto col gap-0.5 items-center" of-hidden py1>
<template v-for="[name, tabs], idx of categorizedInlineTabs" :key="name">
<template v-if="tabs.length">
<div v-if="idx" my1 h-1px w-8 border="b base" />
<SideNavItem
Expand All @@ -59,13 +85,57 @@ onClickOutside(
<div flex-auto />
</div>

<div flex="~ none col items-center">
<div flex="~ none col items-center gap-1" pb1>
<div h-1px w-8 border="b base" />
<VDropdown
v-if="overflowTabs.length"
placement="left-end"
:distance="12"
:triggers="[]"
:shown="showMoreTabs"
:auto-hide="true"
>
<button
flex="~"
hover="bg-active" relative
h-10 w-10 select-none items-center justify-center rounded-xl p1 text-secondary
exact-active-class="!text-primary bg-active"
@click="toggleMoreTabs"
>
<TabIcon
text-xl
icon="carbon:overflow-menu-vertical" title="More tabs" :show-title="false"
/>
<div
absolute bottom-0 right-0 h-4 w-4 rounded-full text-9px
flex="~ items-center justify-center"
border="~ base"
>
<span translate-y-0.5px>{{ overflowTabs.length }}</span>
</div>
</button>
<template #popper>
<div flex="~ col gap-1" max-w-80 py1>
<template v-for="[name, tabs], idx of categorizedOverflowTabs" :key="name">
<template v-if="tabs.length">
<div v-if="idx" h-1px border="b base" />
<div flex="~ wrap" px1>
<SideNavItem
v-for="tab of tabs"
:key="tab.name"
:tab="tab"
/>
</div>
</template>
</template>
</div>
</template>
</VDropdown>
<NuxtLink
to="/settings"
flex="~ items-center justify-center"
hover="bg-active"
relative my1 block h-10 w-10 select-none rounded-xl p1 text-secondary
relative block h-10 w-10 select-none rounded-xl p1 text-secondary
exact-active-class="!text-primary bg-active"
>
<TabIcon
Expand Down
70 changes: 43 additions & 27 deletions packages/devtools/client/composables/state.ts
@@ -1,6 +1,8 @@
import type { Component } from 'nuxt/schema'
import { $fetch } from 'ofetch'
import { unref } from 'vue'
import type { Ref } from 'vue'
import type { MaybeRef } from '@vueuse/core'
import { objectPick } from '@antfu/utils'
import type { HookInfo, InstallModuleReturn, InstalledModuleInfo, ModuleBuiltinTab, ModuleCustomTab, ModuleStaticInfo, RouteInfo, TabCategory } from '../../src/types'

Expand Down Expand Up @@ -190,49 +192,63 @@ export function useAllTabs() {
])
}

export function useCategorizedTabs(enabledOnly = true) {
const tabs = enabledOnly
? useEnabledTabs()
: useAllTabs()

const settings = useDevToolsOptions()
function getCategorizedRecord(): Record<TabCategory, (ModuleCustomTab | ModuleBuiltinTab)[]> {
return {
app: [],
server: [],
analyze: [],
modules: [],
documentation: [],
advanced: [],
}
}

export function getCategorizedTabs(tabs: MaybeRef<(ModuleCustomTab | ModuleBuiltinTab)[]>) {
return computed(() => {
const categories: Record<TabCategory, (ModuleCustomTab | ModuleBuiltinTab)[]> = {
app: [],
server: [],
analyze: [],
modules: [],
documentation: [],
advanced: [],
}

for (const tab of tabs.value) {
const categories = getCategorizedRecord()
for (const tab of unref(tabs)) {
const category = (tab.category || 'app')
if (enabledOnly && settings.hiddenTabCategories.value.includes(category))
continue
if (!categories[category])
console.warn(`Unknown tab category: ${category}`)
else
categories[category].push(tab)
}

for (const key of Object.keys(categories)) {
if (categories[key as TabCategory].length === 0)
delete categories[key as TabCategory]
}

return Object.entries(categories)
})
}

export function useCategorizedTabs(enabledOnly = true) {
const tabs = enabledOnly
? useEnabledTabs()
: useAllTabs()

return getCategorizedTabs(tabs)
}

export function useEnabledTabs() {
const tabs = useAllTabs()
const settings = useDevToolsOptions()

return computed(() => tabs.value.filter((tab) => {
const _tab = tab as ModuleBuiltinTab
if (_tab.show && !_tab.show())
return false
if (settings.hiddenTabs.value.includes(_tab.name))
return false
return true
}))
const categoryOrder = Object.keys(getCategorizedRecord())

return computed(() => tabs.value
.filter((tab) => {
const _tab = tab as ModuleBuiltinTab
if (_tab.show && !_tab.show())
return false
if (settings.hiddenTabs.value.includes(_tab.name))
return false
if (settings.hiddenTabCategories.value.includes(tab.category || 'app'))
return false
return true
})
.sort((a, b) => categoryOrder.indexOf(a.category || 'app') - categoryOrder.indexOf(b.category || 'app')),
)
}

export function useAllRoutes() {
Expand Down

0 comments on commit da6c29f

Please sign in to comment.