Skip to content

Commit 006527d

Browse files
authored
Merge pull request #211 from bcgov/SC3687
Add tab navigation and aria labels for permissions model
2 parents 348fa6c + 04d6c23 commit 006527d

File tree

8 files changed

+76
-15
lines changed

8 files changed

+76
-15
lines changed

frontend/src/components/bucket/BucketPermission.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ onBeforeMount(async () => {
7575
<Button
7676
class="mt-1 mb-4"
7777
@click="showSearchUsers = true"
78+
@keyup.enter="showSearchUsers = true"
7879
>
7980
<font-awesome-icon
8081
icon="fa-solid fa-user-plus"
@@ -99,6 +100,7 @@ onBeforeMount(async () => {
99100
:rows-per-page-options="[10, 20, 50]"
100101
sort-field="fullName"
101102
:sort-order="1"
103+
aria-label="Bucket Permissions"
102104
>
103105
<template #empty>
104106
<div class="flex justify-content-center">
@@ -108,6 +110,7 @@ onBeforeMount(async () => {
108110
<Column
109111
field="fullName"
110112
header="Name"
113+
aria-labelledby="upload_checkbox"
111114
/>
112115
<Column
113116
field="idpName"
@@ -122,6 +125,7 @@ onBeforeMount(async () => {
122125
<Checkbox
123126
v-model="data.create"
124127
input-id="create"
128+
aria-label="upload"
125129
:binary="true"
126130
@update:model-value="(value: boolean) => updateBucketPermission(value, data.userId, Permissions.CREATE)"
127131
/>
@@ -136,6 +140,7 @@ onBeforeMount(async () => {
136140
<Checkbox
137141
v-model="data.read"
138142
input-id="read"
143+
aria-label="Read"
139144
:binary="true"
140145
@update:model-value="(value: boolean) => updateBucketPermission(value, data.userId, Permissions.READ)"
141146
/>
@@ -150,6 +155,7 @@ onBeforeMount(async () => {
150155
<Checkbox
151156
v-model="data.update"
152157
input-id="update"
158+
aria-label="Update"
153159
:binary="true"
154160
@update:model-value="(value: boolean) => updateBucketPermission(value, data.userId, Permissions.UPDATE)"
155161
/>
@@ -164,6 +170,7 @@ onBeforeMount(async () => {
164170
<Checkbox
165171
v-model="data.delete"
166172
input-id="delete"
173+
aria-label="delete"
167174
:binary="true"
168175
@update:model-value="(value: boolean) => updateBucketPermission(value, data.userId, Permissions.DELETE)"
169176
/>
@@ -189,6 +196,7 @@ onBeforeMount(async () => {
189196
<Button
190197
class="p-button-lg p-button-text"
191198
severity="danger"
199+
aria-label="remove"
192200
@click="removeBucketUser(data.userId)"
193201
>
194202
<font-awesome-icon icon="fa-solid fa-user-xmark" />

frontend/src/components/bucket/BucketTable.vue

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { BucketChildConfig, BucketPermission, BucketTableBucketName } from '@/co
66
import { Spinner } from '@/components/layout';
77
import { SyncButton, ShareButton } from '@/components/common';
88
import { Button, Column, Dialog, TreeTable, useConfirm } from '@/lib/primevue';
9-
import { useAppStore, useAuthStore, useBucketStore, usePermissionStore } from '@/store';
9+
import { useAppStore, useAuthStore, useBucketStore, useNavStore, usePermissionStore } from '@/store';
1010
import { DELIMITER, Permissions } from '@/utils/constants';
11-
import { getBucketPath, joinPath } from '@/utils/utils';
11+
import { getBucketPath, joinPath, onDialogHide } from '@/utils/utils';
1212
1313
import type { TreeTableExpandedKeys } from 'primevue/treetable';
1414
import type { Ref } from 'vue';
@@ -20,6 +20,7 @@ const permissionStore = usePermissionStore();
2020
const { getIsLoading } = storeToRefs(useAppStore());
2121
const { getUserId } = storeToRefs(useAuthStore());
2222
const { getBuckets } = storeToRefs(bucketStore);
23+
const { focusedElement } = storeToRefs(useNavStore());
2324
2425
// State
2526
const expandedKeys: Ref<TreeTableExpandedKeys> = ref({});
@@ -58,6 +59,7 @@ const showPermissions = async (bucketId: string, bucketName: string) => {
5859
permissionsVisible.value = true;
5960
permissionsBucketId.value = bucketId;
6061
permissionBucketName.value = bucketName;
62+
focusedElement.value = document.activeElement;
6163
};
6264
6365
const confirmDeleteBucket = (bucketId: string) => {
@@ -313,6 +315,7 @@ watch(getBuckets, () => {
313315
</Button>
314316
<Button
315317
v-if="permissionStore.isBucketActionAllowed(node.data.bucketId, getUserId, Permissions.MANAGE)"
318+
id="folder_permissions"
316319
v-tooltip.bottom="'Folder permissions'"
317320
class="p-button-lg p-button-text"
318321
aria-label="Folder permissions"
@@ -353,21 +356,33 @@ watch(getBuckets, () => {
353356

354357
<!-- eslint-disable vue/no-v-model-argument -->
355358
<Dialog
359+
id="permissions_dialog"
356360
v-model:visible="permissionsVisible"
357361
:draggable="false"
358362
:modal="true"
359363
class="bcbox-info-dialog"
364+
aria-labelledby="permissions_label"
365+
aria-describedby="permissions_desc"
366+
@after-hide="onDialogHide"
360367
>
361368
<!-- eslint-enable vue/no-v-model-argument -->
362369
<template #header>
363370
<font-awesome-icon
364371
icon="fas fa-users"
365372
fixed-width
366373
/>
367-
<span class="p-dialog-title">Folder permissions</span>
374+
<span
375+
id="permissions_label"
376+
class="p-dialog-title"
377+
>
378+
Folder permissions
379+
</span>
368380
</template>
369381

370-
<h3 class="bcbox-info-dialog-subhead">
382+
<h3
383+
id="permissions_desc"
384+
class="bcbox-info-dialog-subhead"
385+
>
371386
{{ permissionBucketName }}
372387
</h3>
373388

frontend/src/components/form/SearchUsers.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ onMounted(() => {
114114

115115
<template>
116116
<div>
117-
<div v-if="getConfig.idpList.length <= 3">
118-
<div
117+
<ul v-if="getConfig.idpList.length <= 3">
118+
<li
119119
v-for="idp of getConfig.idpList"
120120
:key="idp.idp"
121121
class="field-radiobutton mt-1"
@@ -128,8 +128,8 @@ onMounted(() => {
128128
@click="onReset"
129129
/>
130130
<label :for="idp.idp">{{ idp.name }}</label>
131-
</div>
132-
</div>
131+
</li>
132+
</ul>
133133
<div v-else>
134134
<Dropdown
135135
v-model="selectedIDP"

frontend/src/components/object/ObjectPermission.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ onBeforeMount(() => {
7878
<Button
7979
class="mt-1 mb-4"
8080
@click="showSearchUsers = true"
81+
@keyup.enter="showSearchUsers = true"
8182
>
8283
<font-awesome-icon
8384
icon="fa-solid fa-user-plus"
@@ -104,6 +105,7 @@ onBeforeMount(() => {
104105
current-page-report-template="{first}-{last} of {totalRecords}"
105106
:rows-per-page-options="[10, 20, 50]"
106107
sort-field="fullName"
108+
aria-label="File Permissions"
107109
:sort-order="1"
108110
>
109111
<template #empty>
@@ -146,6 +148,7 @@ onBeforeMount(() => {
146148
<Checkbox
147149
v-model="data.update"
148150
input-id="update"
151+
aria-label="update"
149152
:binary="true"
150153
@update:model-value="(value: boolean) => updateObjectPermission(value, data.userId, Permissions.UPDATE)"
151154
/>
@@ -160,6 +163,7 @@ onBeforeMount(() => {
160163
<Checkbox
161164
v-model="data.delete"
162165
input-id="delete"
166+
aria-label="delete"
163167
:binary="true"
164168
@update:model-value="(value: boolean) => updateObjectPermission(value, data.userId, Permissions.DELETE)"
165169
/>
@@ -174,6 +178,7 @@ onBeforeMount(() => {
174178
<Checkbox
175179
v-model="data.manage"
176180
input-id="manage"
181+
aria-label="manage"
177182
:binary="true"
178183
@update:model-value="(value: boolean) => updateObjectPermission(value, data.userId, Permissions.MANAGE)"
179184
/>
@@ -184,7 +189,9 @@ onBeforeMount(() => {
184189
<Button
185190
class="p-button-lg p-button-text"
186191
severity="danger"
192+
aria-label="remove"
187193
@click="removeObjectUser(data.userId)"
194+
@keyup.enter="removeObjectUser(data.userId)"
188195
>
189196
<font-awesome-icon icon="fa-solid fa-user-xmark" />
190197
</Button>

frontend/src/components/object/ObjectPublicToggle.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ watch(props, () => {
6666
<template>
6767
<InputSwitch
6868
v-model="isPublic"
69+
aria-label="Toggle to make public"
6970
:disabled="
7071
!(
7172
usePermissionStore().isUserElevatedRights() &&

frontend/src/components/object/ObjectTable.vue

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import {
1212
} from '@/components/object';
1313
import { SyncButton, ShareButton } from '@/components/common';
1414
import { Button, Column, DataTable, Dialog, InputText, useToast } from '@/lib/primevue';
15-
import { useAuthStore, useObjectStore, usePermissionStore } from '@/store';
15+
import { useAuthStore, useObjectStore, useNavStore, usePermissionStore } from '@/store';
1616
import { Permissions, RouteNames } from '@/utils/constants';
17+
import { onDialogHide } from '@/utils/utils';
1718
import { ButtonMode } from '@/utils/enums';
1819
import { formatDateLong } from '@/utils/formatters';
1920
import { objectService } from '@/services';
@@ -50,6 +51,7 @@ const emit = defineEmits(['show-object-info']);
5051
const objectStore = useObjectStore();
5152
const permissionStore = usePermissionStore();
5253
const { getUserId } = storeToRefs(useAuthStore());
54+
const { focusedElement } = storeToRefs(useNavStore());
5355
5456
// State
5557
const permissionsVisible = ref(false);
@@ -83,6 +85,7 @@ async function showPermissions(objectId: string) {
8385
permissionsVisible.value = true;
8486
permissionsObjectId.value = objectId;
8587
permissionsObjectName.value = objectStore.getObject(objectId)?.name;
88+
focusedElement.value = document.activeElement;
8689
}
8790
8891
onMounted(() => {
@@ -339,6 +342,7 @@ const selectedFilters = (payload: any) => {
339342
v-if="
340343
permissionStore.isObjectActionAllowed(data.id, getUserId, Permissions.MANAGE, props.bucketId as string)
341344
"
345+
id="file_permissions"
342346
v-tooltip.bottom="'File permissions'"
343347
class="p-button-lg p-button-text"
344348
aria-label="File permissions"
@@ -376,21 +380,33 @@ const selectedFilters = (payload: any) => {
376380

377381
<!-- eslint-disable vue/no-v-model-argument -->
378382
<Dialog
383+
id="permissions_dialog"
379384
v-model:visible="permissionsVisible"
380385
:draggable="false"
381386
:modal="true"
382387
class="bcbox-info-dialog"
388+
aria-labelledby="permissions_label"
389+
aria-describedby="permissions_desc"
390+
@after-hide="onDialogHide"
383391
>
384392
<!-- eslint-enable vue/no-v-model-argument -->
385393
<template #header>
386394
<font-awesome-icon
387395
icon="fas fa-users"
388396
fixed-width
389397
/>
390-
<span class="p-dialog-title">File Permissions</span>
398+
<span
399+
id="permissions_label"
400+
class="p-dialog-title"
401+
>
402+
File Permissions
403+
</span>
391404
</template>
392405

393-
<h3 class="bcbox-info-dialog-subhead">
406+
<h3
407+
id="permissions_desc"
408+
class="bcbox-info-dialog-subhead"
409+
>
394410
{{ permissionsObjectName }}
395411
</h3>
396412

frontend/src/store/navStore.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { RouteLocationNormalized } from 'vue-router';
77
export type NavStoreState = {
88
home: Ref<Object>;
99
items: Ref<Array<any>>;
10+
focusedElement: Ref<any>;
1011
};
1112

1213
export const useNavStore = defineStore('nav', () => {
@@ -16,7 +17,8 @@ export const useNavStore = defineStore('nav', () => {
1617
label: 'Home',
1718
to: '/'
1819
}),
19-
items: ref([])
20+
items: ref([]),
21+
focusedElement: ref(null)
2022
};
2123

2224
// Getters

frontend/src/utils/utils.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import { storeToRefs } from 'pinia';
2+
13
import { DELIMITER } from '@/utils/constants';
24
import ConfigService from '@/services/configService';
35
import { ExcludeTypes } from '@/utils/enums';
46
import type { Bucket } from '@/types';
7+
import { useNavStore } from '@/store';
58

69
/**
710
* @function differential
@@ -126,9 +129,7 @@ export function setDispositionHeader(filename: string) {
126129
* @returns {string} full canonical url or path to bucket
127130
*/
128131
export function getBucketPath(bucket: Bucket, justPath = false): string {
129-
return justPath ?
130-
`${bucket.bucket}/${bucket.key}` :
131-
`${bucket.endpoint}/${bucket.bucket}/${bucket.key}`;
132+
return justPath ? `${bucket.bucket}/${bucket.key}` : `${bucket.endpoint}/${bucket.bucket}/${bucket.key}`;
132133
}
133134

134135
/**
@@ -141,3 +142,14 @@ export function getLastSegment(path: string) {
141142
const p = path.replace(/\/+$/, '');
142143
return p.slice(p.lastIndexOf('/') + 1);
143144
}
145+
146+
/**
147+
* @function onDialogHide
148+
* Return focus to the button which trigger model visibility
149+
* @type {focusedElement} stores dom element which needs to be focused
150+
*/
151+
export function onDialogHide() {
152+
const { focusedElement } = storeToRefs(useNavStore());
153+
focusedElement.value?.focus();
154+
focusedElement.value = null;
155+
}

0 commit comments

Comments
 (0)