4949 v-if =" currentApp "
5050 class="app-menu__current-app"
5151 variant="tertiary-no-background"
52- :aria-label =" t ( ' core ' , ' Open apps menu ' ) "
52+ :aria-label =" currentAppLabel "
5353 aria-haspopup="menu"
5454 :aria-expanded =" opened ? ' true' : ' false' "
5555 @click =" onTriggerClick (' currentApp' )" >
5656 <template #icon >
5757 <img
5858 class =" app-menu__current-app-icon"
59+ :class =" { 'app-menu__current-app-icon--settings': currentApp.type === 'settings' }"
5960 :src =" currentApp.icon"
6061 alt =" "
6162 aria-hidden =" true" >
@@ -82,6 +83,9 @@ import IconDotsGrid from 'vue-material-design-icons/DotsGrid.vue'
8283import AppItem from ' ./AppItem.vue'
8384import logger from ' ../logger.js'
8485
86+ // Settings IDs that represent actions, not navigable pages.
87+ const SETTINGS_ACTION_IDS = new Set ([' logout' ])
88+
8589export default defineComponent ({
8690 name: ' AppMenu' ,
8791
@@ -103,8 +107,12 @@ export default defineComponent({
103107
104108 data() {
105109 const appList = loadState <INavigationEntry []>(' core' , ' apps' , [])
110+ // Record<id, entry>, not an array: PHP ships getAll('settings') without
111+ // array_values(). Matches AccountMenu.vue's usage.
112+ const settingsList = loadState <Record <string , INavigationEntry >>(' core' , ' settingsNavEntries' , {})
106113 return {
107114 appList ,
115+ settingsList ,
108116 isAdmin: getCurrentUser ()?.isAdmin ?? false ,
109117 // Roving tabindex: only this tile has tabindex=0; arrow keys move it.
110118 focusedIndex: 0 ,
@@ -146,7 +154,18 @@ export default defineComponent({
146154
147155 computed: {
148156 currentApp(): INavigationEntry | undefined {
157+ // Fall back to the active settings entry on admin pages where no
158+ // app is active.
149159 return this .appList .find ((app ) => app .active )
160+ ?? Object .values (this .settingsList ).find ((entry ) => entry .active && ! SETTINGS_ACTION_IDS .has (entry .id ))
161+ },
162+
163+ // aria-label overrides the inner span text, so the section name
164+ // has to be duplicated here for screen readers.
165+ currentAppLabel(): string {
166+ return this .currentApp
167+ ? t (' core' , ' Open apps menu, currently in {app}' , { app: this .currentApp .name })
168+ : t (' core' , ' Open apps menu' )
150169 },
151170
152171 // Stable-ordered list that focusedIndex indexes into. The trailing
@@ -374,6 +393,12 @@ export default defineComponent({
374393 // Theme-aware inversion + vertical alpha fade via --header-menu-icon-mask.
375394 filter : var (--background-image-invert-if-bright );
376395 mask : var (--header-menu-icon-mask );
396+
397+ // Settings icons ship dark (designed for the white settings sidebar);
398+ // force-white so they read against the themed header.
399+ & --settings {
400+ filter : brightness (0 ) invert (1 );
401+ }
377402 }
378403
379404 & __current-app-name {
@@ -386,16 +411,20 @@ export default defineComponent({
386411 & __popover {
387412 max-width : calc (100vw - var (--default-grid-baseline ) * 4 );
388413 background-color : var (--color-main-background );
414+ // Padding on the popover (not the grid) so the top-row hover sits
415+ // concentrically inside the rounded corners.
416+ padding : calc (var (--default-grid-baseline ) * 3 ) calc (var (--default-grid-baseline ) * 2 );
389417 }
390418
391419 & __grid {
392420 --app-item-col-width : 69px ;
393421 --app-item-row-height : 64px ;
394422 --app-menu-rows-visible : 6 ;
395- padding : calc (var (--default-grid-baseline ) * 3 ) calc (var (--default-grid-baseline ) * 2 );
396423 display : grid ;
397424 grid-template-columns : repeat (4 , var (--app-item-col-width ));
398425 grid-auto-rows : minmax (var (--app-item-row-height ), max-content );
426+ // + baseline * 5: peek-row hint so users see that content continues
427+ // below the fold.
399428 max-height : calc (var (--app-item-row-height ) * var (--app-menu-rows-visible ) + var (--default-grid-baseline ) * 5 );
400429 overflow-y : auto ;
401430
0 commit comments