Skip to content

Commit

Permalink
Merge pull request #40168 from nextcloud/enh/a11y/user-menu
Browse files Browse the repository at this point in the history
  • Loading branch information
Pytal authored Oct 16, 2023
2 parents b914916 + 48e02be commit e6a2985
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 80 deletions.
3 changes: 2 additions & 1 deletion core/src/components/AppMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
-->

<template>
<nav class="app-menu">
<nav class="app-menu"
:aria-label="t('core', 'Applications menu')">
<ul class="app-menu-main">
<li v-for="app in mainAppList"
:key="app.id"
Expand Down
217 changes: 142 additions & 75 deletions core/src/views/UserMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,54 @@
<template>
<NcHeaderMenu id="user-menu"
class="user-menu"
:aria-label="t('core', 'Open settings menu')">
is-nav
:aria-label="t('core', 'Settings menu')"
:description="avatarDescription">
<template #trigger>
<NcAvatar class="user-menu__avatar"
<NcAvatar v-if="!isLoadingUserStatus"
class="user-menu__avatar"
:disable-menu="true"
:disable-tooltip="true"
:user="userId" />
:user="userId"
:preloaded-user-status="userStatus" />
</template>
<nav class="user-menu__nav"
:aria-label="t('core', 'Settings menu')">
<ul>
<UserMenuEntry v-for="entry in settingsNavEntries"
v-bind="entry"
:key="entry.id" />
</ul>
</nav>
<ul>
<UserMenuEntry v-for="entry in settingsNavEntries"
v-bind="entry"
:key="entry.id" />
</ul>
</NcHeaderMenu>
</template>

<script>
import { emit } from '@nextcloud/event-bus'
import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import { emit, subscribe } from '@nextcloud/event-bus'
import { loadState } from '@nextcloud/initial-state'
import { generateOcsUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import { getCapabilities } from '@nextcloud/capabilities'

import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
import NcHeaderMenu from '@nextcloud/vue/dist/Components/NcHeaderMenu.js'

import { getAllStatusOptions } from '../../../apps/user_status/src/services/statusOptionsService.js'
import UserMenuEntry from '../components/UserMenu/UserMenuEntry.vue'

import logger from '../logger.js'

const settingsNavEntries = loadState('core', 'settingsNavEntries', [])

const translateStatus = (status) => {
const statusMap = Object.fromEntries(
getAllStatusOptions()
.map(({ type, label }) => [type, label]),
)
if (statusMap[status]) {
return statusMap[status]
}
return status
}

export default {
name: 'UserMenu',

Expand All @@ -65,13 +83,67 @@ export default {
data() {
return {
settingsNavEntries,
displayName: getCurrentUser()?.displayName,
userId: getCurrentUser()?.uid,
isLoadingUserStatus: true,
userStatus: {
status: null,
icon: null,
message: null,
},
}
},

computed: {
translatedUserStatus() {
return {
...this.userStatus,
status: translateStatus(this.userStatus.status),
}
},

avatarDescription() {
const description = [
t('core', 'Avatar of {displayName}', { displayName: this.displayName }),
...Object.values(this.translatedUserStatus).filter(Boolean),
].join(' — ')
return description
},
},

async created() {
if (!getCapabilities()?.user_status?.enabled) {
this.isLoadingUserStatus = false
return
}

const url = generateOcsUrl('/apps/user_status/api/v1/user_status')
try {
const response = await axios.get(url)
const { status, icon, message } = response.data.ocs.data
this.userStatus = { status, icon, message }
} catch (e) {
logger.error('Failed to load user status')
}
this.isLoadingUserStatus = false
},

mounted() {
subscribe('user_status:status.updated', this.handleUserStatusUpdated)
emit('core:user-menu:mounted')
},

methods: {
handleUserStatusUpdated(state) {
if (this.userId === state.userId) {
this.userStatus = {
status: state.status,
icon: state.icon,
message: state.message,
}
}
},
},
}
</script>

Expand Down Expand Up @@ -108,74 +180,69 @@ export default {
}
}

&__nav {
ul {
display: flex;
width: 100%;

ul {
display: flex;
flex-direction: column;
gap: 2px;

&:deep {
li {
a,
button {
border-radius: 6px;
display: inline-flex;
align-items: center;
height: var(--header-menu-item-height);
flex-direction: column;
gap: 2px;

&:deep {
li {
a,
button {
border-radius: 6px;
display: inline-flex;
align-items: center;
height: var(--header-menu-item-height);
color: var(--color-main-text);
padding: 10px 8px;
box-sizing: border-box;
white-space: nowrap;
position: relative;
width: 100%;

&:hover {
background-color: var(--color-background-hover);
}

&:focus-visible {
background-color: var(--color-background-hover) !important;
box-shadow: inset 0 0 0 2px var(--color-primary-element) !important;
outline: none !important;
}

&:active,
&.active {
background-color: var(--color-primary-element);
color: var(--color-primary-element-text);
}

span {
padding-bottom: 0;
color: var(--color-main-text);
padding: 10px 8px;
box-sizing: border-box;
white-space: nowrap;
position: relative;
width: 100%;

&:hover {
background-color: var(--color-background-hover);
}

&:focus-visible {
background-color: var(--color-background-hover) !important;
box-shadow: inset 0 0 0 2px var(--color-primary-element) !important;
outline: none !important;
}

&:active,
&.active {
background-color: var(--color-primary-element);
color: var(--color-primary-element-text);
}

span {
padding-bottom: 0;
color: var(--color-main-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 110px;
}

img {
width: 16px;
height: 16px;
margin-right: 10px;
}

img,
svg {
filter: var(--background-invert-if-dark);
}
overflow: hidden;
text-overflow: ellipsis;
max-width: 110px;
}

// Override global button styles
button {
background-color: transparent;
border: none;
font-weight: normal;
margin: 0;
img {
width: 16px;
height: 16px;
margin-right: 10px;
}

img,
svg {
filter: var(--background-invert-if-dark);
}
}

// Override global button styles
button {
background-color: transparent;
border: none;
font-weight: normal;
margin: 0;
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions dist/core-main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/core-main.js.map

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static function settingsMenuButton() {
* @return Locator
*/
public static function settingsMenu() {
return Locator::forThe()->css(".user-menu__nav")->
return Locator::forThe()->css("ul")->
descendantOf(self::settingsSectionInHeader())->
describedAs("Settings menu");
}
Expand Down

0 comments on commit e6a2985

Please sign in to comment.