Skip to content

Commit

Permalink
Merge pull request #40719 from nextcloud/enh/a11y/semantic-user-table
Browse files Browse the repository at this point in the history
  • Loading branch information
Pytal committed Oct 6, 2023
2 parents 4016c7a + 091b3e0 commit 649990e
Show file tree
Hide file tree
Showing 18 changed files with 393 additions and 279 deletions.
113 changes: 22 additions & 91 deletions apps/settings/src/components/UserList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,51 +41,44 @@
</template>
</NcEmptyContent>

<RecycleScroller v-else
ref="scroller"
class="user-list"
<VirtualList v-else
:data-component="UserRow"
:data-sources="filteredUsers"
data-key="id"
:item-height="rowHeight"
:style="style"
:items="filteredUsers"
key-field="id"
role="table"
list-tag="tbody"
list-class="user-list__body"
item-tag="tr"
item-class="user-list__row"
:item-size="rowHeight"
@hook:mounted="handleMounted"
:extra-props="{
users,
settings,
hasObfuscated,
groups,
subAdminsGroups,
quotaOptions,
languages,
externalActions,
}"
@scroll-end="handleScrollEnd">
<template #before>
<caption class="hidden-visually">
{{ t('settings', 'List of users. This list is not fully rendered for performance reasons. The users will be rendered as you navigate through the list.') }}
</caption>
<UserListHeader :has-obfuscated="hasObfuscated" />
</template>

<template #default="{ item: user }">
<UserRow :user="user"
:users="users"
:settings="settings"
:has-obfuscated="hasObfuscated"
:groups="groups"
:sub-admins-groups="subAdminsGroups"
:quota-options="quotaOptions"
:languages="languages"
:external-actions="externalActions" />
<template #header>
<UserListHeader :has-obfuscated="hasObfuscated" />
</template>

<template #after>
<template #footer>
<UserListFooter :loading="loading.users"
:filtered-users="filteredUsers" />
</template>
</RecycleScroller>
</VirtualList>
</Fragment>
</template>

<script>
import Vue from 'vue'
import { Fragment } from 'vue-frag'
import { RecycleScroller } from 'vue-virtual-scroller'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
Expand All @@ -94,6 +87,7 @@ import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import { showError } from '@nextcloud/dialogs'
import VirtualList from './Users/VirtualList.vue'
import NewUserModal from './Users/NewUserModal.vue'
import UserListFooter from './Users/UserListFooter.vue'
import UserListHeader from './Users/UserListHeader.vue'
Expand Down Expand Up @@ -128,10 +122,9 @@ export default {
NcIconSvgWrapper,
NcLoadingIcon,
NewUserModal,
RecycleScroller,
UserListFooter,
UserListHeader,
UserRow,
VirtualList,
},
props: {
Expand All @@ -147,6 +140,7 @@ export default {
data() {
return {
UserRow,
loading: {
all: false,
groups: false,
Expand Down Expand Up @@ -295,16 +289,6 @@ export default {
},
methods: {
async handleMounted() {
// Add proper semantics to the recycle scroller slots
const header = this.$refs.scroller.$refs.before
const footer = this.$refs.scroller.$refs.after
header.classList.add('user-list__header')
header.setAttribute('role', 'rowgroup')
footer.classList.add('user-list__footer')
footer.setAttribute('role', 'rowgroup')
},
async handleScrollEnd() {
await this.loadUsers()
},
Expand Down Expand Up @@ -414,57 +398,4 @@ export default {
}
}
}
.user-list {
--avatar-cell-width: 48px;
--cell-padding: 7px;
--cell-width: 200px;
--cell-min-width: calc(var(--cell-width) - (2 * var(--cell-padding)));
display: block;
overflow: auto;
height: 100%;
:deep {
.user-list {
&__body {
display: flex;
flex-direction: column;
width: 100%;
// Necessary for virtual scrolling absolute
position: relative;
margin-top: var(--row-height);
}
&__row {
@include row;
border-bottom: 1px solid var(--color-border);
&:hover {
background-color: var(--color-background-hover);
.row__cell:not(.row__cell--actions) {
background-color: var(--color-background-hover);
}
}
}
}
.vue-recycle-scroller__slot {
&.user-list__header,
&.user-list__footer {
position: sticky;
}
&.user-list__header {
top: 0;
z-index: 10;
}
&.user-list__footer {
left: 0;
}
}
}
}
</style>
2 changes: 2 additions & 0 deletions apps/settings/src/components/Users/UserListFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,15 @@ export default Vue.extend({
&--loading {
left: 0;
min-width: var(--avatar-cell-width);
width: var(--avatar-cell-width);
align-items: center;
padding: 0;
}
&--count {
left: var(--avatar-cell-width);
min-width: var(--cell-width);
width: var(--cell-width);
}
}
Expand Down
2 changes: 1 addition & 1 deletion apps/settings/src/components/Users/UserListHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
scope="col">
<span>{{ t('settings', 'Last login') }}</span>
</th>
<th class="header__cell header__cell--large"
<th class="header__cell header__cell--large header__cell--fill"
scope="col">
<!-- TRANSLATORS This string describes a manager in the context of an organization -->
<span>{{ t('settings', 'Manager') }}</span>
Expand Down
Loading

0 comments on commit 649990e

Please sign in to comment.