11<script setup lang="ts">
2- import type { DevToolsViewGroup } from ' @vitejs/devtools-kit'
2+ import type { DevToolsDockEntry , DevToolsViewGroup } from ' @vitejs/devtools-kit'
33import type { DocksContext } from ' @vitejs/devtools-kit/client'
4- import { computed } from ' vue'
5- import { getGroupMembersGrouped } from ' ../../state/dock-settings'
4+ import { useElementBounding , watchDebounced } from ' @vueuse/core'
5+ import { computed , h , ref , useTemplateRef } from ' vue'
6+ import { deriveSidebarCapacity , docksSplitGroupsWithCapacity , getGroupMembersGrouped } from ' ../../state/dock-settings'
67import { sharedStateToRef } from ' ../../state/docks'
7- import { setFloatingTooltip } from ' ../../state/floating-tooltip'
8+ import { setDocksSidebarOverflowPanel , setFloatingTooltip , useDocksSidebarOverflowPanel } from ' ../../state/floating-tooltip'
9+ import DockGroupPopover from ' ./DockGroupPopover.vue'
810import DockIcon from ' ./DockIcon.vue'
911
1012const props = defineProps <{
@@ -13,6 +15,15 @@ const props = defineProps<{
1315 selectedId: string | null
1416}>()
1517
18+ // Vertical rhythm of the rail, in px. Mirrors the Uno classes on the template:
19+ // each button is `w-8 h-8` (32) and children sit in a `gap-0.5` (2) flex column;
20+ // the root has `py1.5` (6+6) padding; dividers are `h-px my0.5` (1 + 2*2).
21+ const ITEM_HEIGHT = 34 // 32 button + 2 gap
22+ const MORE_BUTTON_HEIGHT = 34 // matches a member button + gap
23+ const DIVIDER_HEIGHT = 7 // 5 divider + 2 gap
24+ // Root padding (12) + anchor (32) + gap (2) + anchor divider (5) + gap (2).
25+ const RESERVED_HEIGHT = 53
26+
1627const settings = sharedStateToRef (props .context .docks .settings )
1728
1829// Members split by in-group sub-category so the sidebar can divide sections.
@@ -23,6 +34,35 @@ const memberGroups = computed(() => getGroupMembersGrouped(
2334 { whenContext: props .context .when .context },
2435))
2536
37+ const sidebar = useTemplateRef <HTMLDivElement >(' sidebar' )
38+ const { height : frameHeight } = useElementBounding (sidebar )
39+
40+ const totalItems = computed (() => memberGroups .value .reduce ((acc , [, items ]) => acc + items .length , 0 ))
41+
42+ // How many members the current frame height can hold before folding the rest
43+ // into the show-more popover.
44+ const capacity = computed (() => deriveSidebarCapacity ({
45+ availableHeight: frameHeight .value ,
46+ reservedHeight: RESERVED_HEIGHT ,
47+ itemHeight: ITEM_HEIGHT ,
48+ dividerHeight: DIVIDER_HEIGHT ,
49+ moreButtonHeight: MORE_BUTTON_HEIGHT ,
50+ dividerCount: Math .max (0 , memberGroups .value .filter (([, items ]) => items .length ).length - 1 ),
51+ totalItems: totalItems .value ,
52+ }))
53+
54+ const split = computed (() => docksSplitGroupsWithCapacity (memberGroups .value , capacity .value ))
55+ const visibleGroups = computed (() => split .value .visible )
56+ const overflowGroups = computed (() => split .value .overflow )
57+
58+ const overflowCount = computed (() => overflowGroups .value .reduce ((acc , [, items ]) => acc + items .length , 0 ))
59+ const hasOverflow = computed (() => overflowCount .value > 0 )
60+ const overflowBadge = computed (() => (overflowCount .value > 9 ? ' 9+' : overflowCount .value .toString ()))
61+
62+ // The current selection is hidden inside the show-more popover.
63+ const selectedInOverflow = computed (() =>
64+ !! props .selectedId && overflowGroups .value .some (([, items ]) => items .some (m => m .id === props .selectedId )))
65+
2666function select(id : string ) {
2767 props .context .docks .switchEntry (id )
2868}
@@ -34,10 +74,61 @@ function showTooltip(event: PointerEvent, title: string) {
3474function hideTooltip() {
3575 setFloatingTooltip (null )
3676}
77+
78+ // --- Show more (overflow) flyout ---
79+ const moreButton = useTemplateRef <HTMLButtonElement >(' moreButton' )
80+ const isOverflowPanelVisible = ref (false )
81+ const overflowPanel = useDocksSidebarOverflowPanel ()
82+
83+ function showOverflowPanel() {
84+ if (! moreButton .value )
85+ return
86+ isOverflowPanelVisible .value = true
87+ setDocksSidebarOverflowPanel ({
88+ el: moreButton .value ,
89+ placement: ' right' ,
90+ content : () => h (DockGroupPopover , {
91+ context: props .context ,
92+ group: props .group ,
93+ // Only the members that didn't fit on the rail.
94+ members: overflowGroups .value ,
95+ selectedId: props .selectedId ,
96+ onSelect : (entry : DevToolsDockEntry ) => {
97+ select (entry .id )
98+ hideOverflowPanel ()
99+ },
100+ }),
101+ })
102+ }
103+
104+ function hideOverflowPanel() {
105+ isOverflowPanelVisible .value = false
106+ setDocksSidebarOverflowPanel (null )
107+ }
108+
109+ function toggleOverflowPanel() {
110+ if (isOverflowPanelVisible .value )
111+ hideOverflowPanel ()
112+ else
113+ showOverflowPanel ()
114+ }
115+
116+ // Sync internal visibility from the store on a delay so it doesn't race the
117+ // "click outside" dismissal (same pattern as DockOverflowButton).
118+ watchDebounced (
119+ () => overflowPanel .value ,
120+ (value ) => {
121+ isOverflowPanelVisible .value = value ?.el === moreButton .value
122+ },
123+ { debounce: 1000 },
124+ )
125+
126+ // Highlight the button when the hidden selection lives here, or while open.
127+ const moreButtonActive = computed (() => selectedInOverflow .value || isOverflowPanelVisible .value )
37128 </script >
38129
39130<template >
40- <div class =" vite-devtools-group-sidebar flex flex-col flex-none w-12 h-full border-r border-base of-y-auto of-x-hidden select-none py1.5 items-center gap-0.5" >
131+ <div ref = " sidebar " class =" vite-devtools-group-sidebar flex flex-col flex-none w-12 h-full border-r border-base of-y-hidden of-x-hidden select-none py1.5 items-center gap-0.5" >
41132 <!-- Group anchor -->
42133 <div
43134 class =" flex items-center justify-center w-8 h-8 op60"
@@ -49,7 +140,7 @@ function hideTooltip() {
49140 <div class =" w-8 h-px border-t border-base my0.5" />
50141
51142 <!-- Member icons, grouped by in-group sub-category -->
52- <template v-for =" ([category , members ], idx ) of memberGroups " :key =" category " >
143+ <template v-for =" ([category , members ], idx ) of visibleGroups " :key =" category " >
53144 <!-- Sub-category divider, mirroring the group anchor separator -->
54145 <div v-if =" idx > 0 && members.length" class =" w-8 h-px border-t border-base my0.5" />
55146 <button
@@ -71,5 +162,22 @@ function hideTooltip() {
71162 </div >
72163 </button >
73164 </template >
165+
166+ <!-- Show more: members that don't fit the current frame height -->
167+ <button
168+ v-if =" hasOverflow"
169+ ref =" moreButton"
170+ class =" relative flex items-center justify-center w-8 h-8 rounded-lg transition mt-auto flex-none"
171+ :class =" moreButtonActive ? 'text-primary bg-active' : 'op60 hover:op100 hover:bg-active'"
172+ @pointerenter =" showTooltip($event, 'Show more')"
173+ @pointerleave =" hideTooltip"
174+ @pointerdown =" hideTooltip"
175+ @click =" toggleOverflowPanel"
176+ >
177+ <DockIcon icon="ph:dots-three-circle-duotone " class="w-5 h-5 flex-none" />
178+ <div class =" absolute top-0.5 right-0.5 bg-gray-6 text-white text-0.6em px-0.5 rounded-full shadow leading-none" >
179+ {{ overflowBadge }}
180+ </div >
181+ </button >
74182 </div >
75183</template >
0 commit comments