mirror of
https://github.com/nextcloud/server.git
synced 2026-06-11 01:30:50 -04:00
Merge pull request #40168 from nextcloud/enh/a11y/user-menu
This commit is contained in:
commit
e6a2985e4d
5 changed files with 146 additions and 78 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -108,74 +180,69 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
&__nav {
|
||||
ul {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
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);
|
||||
color: var(--color-main-text);
|
||||
padding: 10px 8px;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&:deep {
|
||||
li {
|
||||
a,
|
||||
button {
|
||||
border-radius: 6px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: var(--header-menu-item-height);
|
||||
&: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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
dist/core-main.js
vendored
4
dist/core-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-main.js.map
vendored
2
dist/core-main.js.map
vendored
File diff suppressed because one or more lines are too long
|
|
@ -49,7 +49,7 @@ class SettingsMenuContext implements Context, ActorAwareInterface {
|
|||
* @return Locator
|
||||
*/
|
||||
public static function settingsMenu() {
|
||||
return Locator::forThe()->css(".user-menu__nav")->
|
||||
return Locator::forThe()->css("ul")->
|
||||
descendantOf(self::settingsSectionInHeader())->
|
||||
describedAs("Settings menu");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue