Skip to content

Commit bff8dc4

Browse files
ChunkyProgrammervallodekommunarrPikachuEXE
authored
Improve accessibility of Channel View (#2984)
* Improve channel info bar * Reduce width of channel search bar * fix sizing * improve channel view accessibility Co-Authored-By: Jason <[email protected]> * Update src/renderer/components/ft-channel-bubble/ft-channel-bubble.js Co-authored-by: PikachuEXE <[email protected]> * Stop space from clicking channel-bubble (links) Co-authored-by: vallode <[email protected]> Co-authored-by: Jason <[email protected]> Co-authored-by: PikachuEXE <[email protected]>
1 parent 156176a commit bff8dc4

File tree

5 files changed

+163
-53
lines changed

5 files changed

+163
-53
lines changed

src/renderer/components/ft-channel-bubble/ft-channel-bubble.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Vue from 'vue'
2+
import { sanitizeForHtmlId } from '../../helpers/accessibility'
23

34
export default Vue.extend({
45
name: 'FtChannelBubble',
@@ -21,8 +22,20 @@ export default Vue.extend({
2122
selected: false
2223
}
2324
},
25+
computed: {
26+
sanitizedId: function() {
27+
return 'channelBubble' + sanitizeForHtmlId(this.channelName)
28+
}
29+
},
2430
methods: {
25-
handleClick: function () {
31+
handleClick: function (event) {
32+
if (event instanceof KeyboardEvent) {
33+
if (event.target.getAttribute('role') === 'link' && event.key !== 'Enter') {
34+
return
35+
}
36+
event.preventDefault()
37+
}
38+
2639
if (this.showSelected) {
2740
this.selected = !this.selected
2841
}

src/renderer/components/ft-channel-bubble/ft-channel-bubble.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
<template>
22
<div
33
class="bubblePadding"
4+
tabindex="0"
5+
:aria-labelledby="sanitizedId"
46
@click="handleClick"
7+
@keydown.space.enter.prevent="handleClick($event)"
58
>
69
<img
710
class="bubble"
@@ -16,7 +19,10 @@
1619
class="icon"
1720
/>
1821
</div>
19-
<div class="channelName">
22+
<div
23+
:id="sanitizedId"
24+
class="channelName"
25+
>
2026
{{ channelName }}
2127
</div>
2228
</div>

src/renderer/views/Channel/Channel.css

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
}
77

88
.channelDetails {
9-
padding: 0 0 16px;
9+
padding: 0;
1010
}
1111

1212
.channelBannerContainer {
@@ -75,38 +75,51 @@
7575

7676
.channelSearch {
7777
width: 220px;
78+
margin-left: auto;
7879
align-self: flex-end;
80+
flex: 1 1 0%;
7981
}
8082

8183
.sortSelect {
82-
align-self: flex-end;
84+
margin-left: auto;
8385
}
8486

8587
.channelInfoTabs {
8688
position: relative;
8789
width: 100%;
88-
margin-top: -16px;
89-
margin-bottom: -13px;
90+
height: auto;
91+
justify-content: unset;
92+
gap: 32px;
93+
padding: .3em 0;
94+
}
95+
96+
.tabs {
97+
display: flex;
98+
flex: 0 1 33%;
9099
}
91100

92101
.tab {
93102
padding: 15px;
94103
font-size: 15px;
95104
cursor: pointer;
105+
flex: 1 1 0%;
96106
align-self: flex-end;
107+
text-align: center;
97108
color: var(--tertiary-text-color);
109+
border-bottom: 3px solid transparent;
98110
}
99111

100-
.selectedTab {
101-
color: var(--primary-text-color);
102-
border-bottom: 3px solid var(--primary-color);
103-
margin-bottom: -3px;
112+
.tab:hover {
104113
font-weight: bold;
105-
box-sizing: border-box;
114+
border-bottom: 3px solid var(--tertiary-text-color);
106115
}
107116

108-
.tab:hover {
117+
.selectedTab,
118+
.selectedTab:hover {
119+
color: var(--primary-text-color);
120+
border-bottom: 3px solid var(--primary-color);
109121
font-weight: bold;
122+
box-sizing: border-box;
110123
}
111124

112125
.aboutTab {
@@ -125,10 +138,7 @@
125138

126139
.channelSearch {
127140
margin-top: 10px;
128-
}
129-
130-
.elementList {
131-
margin-top: 15px;
141+
max-width: 250px;
132142
}
133143

134144
.elementListLoading {
@@ -164,3 +174,15 @@
164174
.channelLineContainer p {
165175
margin: 0;
166176
}
177+
178+
@media only screen and (max-width: 800px) {
179+
.channelInfoTabs {
180+
height: auto;
181+
flex-flow: column-reverse;
182+
}
183+
184+
.channelSearch {
185+
width: 100%;
186+
max-width: none;
187+
}
188+
}

src/renderer/views/Channel/Channel.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ export default Vue.extend({
6868
playlistSelectValues: [
6969
'last',
7070
'newest'
71+
],
72+
tabInfoValues: [
73+
'videos',
74+
'playlists',
75+
'about'
7176
]
7277
}
7378
},
@@ -652,8 +657,34 @@ export default Vue.extend({
652657
}
653658
},
654659

655-
changeTab: function (tab) {
660+
changeTab: function (tab, event) {
661+
if (event instanceof KeyboardEvent) {
662+
// use arrowkeys to navigate
663+
event.preventDefault()
664+
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
665+
const index = this.tabInfoValues.indexOf(tab)
666+
667+
// focus left or right tab with wrap around
668+
tab = (event.key === 'ArrowLeft')
669+
? this.tabInfoValues[(index > 0 ? index : this.tabInfoValues.length) - 1]
670+
: this.tabInfoValues[(index + 1) % this.tabInfoValues.length]
671+
672+
const tabNode = document.getElementById(`${tab}Tab`)
673+
event.target.setAttribute('tabindex', '-1')
674+
tabNode.setAttribute('tabindex', 0)
675+
tabNode.focus({ focusVisible: true })
676+
return
677+
}
678+
}
679+
680+
const currentTabNode = document.querySelector('.tabs > .tab[aria-selected="true"]')
681+
const newTabNode = document.getElementById(`${tab}Tab`)
682+
document.querySelector('.tabs > .tab[tabindex="0"]').setAttribute('tabindex', '-1')
683+
newTabNode.setAttribute('tabindex', '0')
684+
currentTabNode.setAttribute('aria-selected', 'false')
685+
newTabNode.setAttribute('aria-selected', 'true')
656686
this.currentTab = tab
687+
newTabNode.focus({ focusVisible: true })
657688
},
658689

659690
newSearch: function (query) {

src/renderer/views/Channel/Channel.vue

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<img
3131
class="channelThumbnail"
3232
:src="thumbnailUrl"
33+
alt=""
3334
>
3435
<div
3536
class="channelLineContainer"
@@ -74,50 +75,57 @@
7475
class="channelInfoTabs"
7576
>
7677
<div
77-
class="tab"
78-
:class="(currentTab==='videos')?'selectedTab':''"
79-
@click="changeTab('videos')"
78+
class="tabs"
79+
role="tablist"
80+
:aria-label="$t('Channel.Channel Tabs')"
8081
>
81-
{{ $t("Channel.Videos.Videos").toUpperCase() }}
82-
</div>
83-
<div
84-
class="tab"
85-
:class="(currentTab==='playlists')?'selectedTab':''"
86-
@click="changeTab('playlists')"
87-
>
88-
{{ $t("Channel.Playlists.Playlists").toUpperCase() }}
89-
</div>
90-
<div
91-
class="tab"
92-
:class="(currentTab==='about')?'selectedTab':''"
93-
@click="changeTab('about')"
94-
>
95-
{{ $t("Channel.About.About").toUpperCase() }}
82+
<div
83+
id="videosTab"
84+
class="tab"
85+
:class="(currentTab==='videos')?'selectedTab':''"
86+
role="tab"
87+
aria-selected="true"
88+
aria-controls="videoPanel"
89+
tabindex="0"
90+
@click="changeTab('videos')"
91+
@keydown.left.right.enter.space="changeTab('videos', $event)"
92+
>
93+
{{ $t("Channel.Videos.Videos").toUpperCase() }}
94+
</div>
95+
<div
96+
id="playlistsTab"
97+
class="tab"
98+
role="tab"
99+
aria-selected="false"
100+
aria-controls="playlistPanel"
101+
tabindex="-1"
102+
:class="(currentTab==='playlists')?'selectedTab':''"
103+
@click="changeTab('playlists')"
104+
@keydown.left.right.enter.space="changeTab('playlists', $event)"
105+
>
106+
{{ $t("Channel.Playlists.Playlists").toUpperCase() }}
107+
</div>
108+
<div
109+
id="aboutTab"
110+
class="tab"
111+
role="tab"
112+
aria-selected="false"
113+
aria-controls="aboutPanel"
114+
tabindex="-1"
115+
:class="(currentTab==='about')?'selectedTab':''"
116+
@click="changeTab('about')"
117+
@keydown.left.right.enter.space="changeTab('about', $event)"
118+
>
119+
{{ $t("Channel.About.About").toUpperCase() }}
120+
</div>
96121
</div>
122+
97123
<ft-input
98124
:placeholder="$t('Channel.Search Channel')"
99125
:show-clear-text-button="true"
100126
class="channelSearch"
101127
@click="newSearch"
102128
/>
103-
<ft-select
104-
v-show="currentTab === 'videos'"
105-
class="sortSelect"
106-
:value="videoSelectValues[0]"
107-
:select-names="videoSelectNames"
108-
:select-values="videoSelectValues"
109-
:placeholder="$t('Search Filters.Sort By.Sort By')"
110-
@change="videoSortBy = $event"
111-
/>
112-
<ft-select
113-
v-show="currentTab === 'playlists'"
114-
class="sortSelect"
115-
:value="playlistSelectValues[0]"
116-
:select-names="playlistSelectNames"
117-
:select-values="playlistSelectValues"
118-
:placeholder="$t('Search Filters.Sort By.Sort By')"
119-
@change="playlistSortBy = $event"
120-
/>
121129
</ft-flex-box>
122130
</div>
123131
</ft-card>
@@ -127,6 +135,7 @@
127135
>
128136
<div
129137
v-if="currentTab === 'about'"
138+
id="aboutPanel"
130139
class="aboutTab"
131140
>
132141
<h2>
@@ -151,10 +160,29 @@
151160
:channel-name="channel.author || channel.channelName"
152161
:channel-id="channel.channelId"
153162
:channel-thumbnail="channel.authorThumbnails[channel.authorThumbnails.length - 1].url"
163+
role="link"
154164
@click="goToChannel(channel.channelId)"
155165
/>
156166
</ft-flex-box>
157167
</div>
168+
<ft-select
169+
v-show="currentTab === 'videos'"
170+
class="sortSelect"
171+
:value="videoSelectValues[0]"
172+
:select-names="videoSelectNames"
173+
:select-values="videoSelectValues"
174+
:placeholder="$t('Search Filters.Sort By.Sort By')"
175+
@change="videoSortBy = $event"
176+
/>
177+
<ft-select
178+
v-show="currentTab === 'playlists'"
179+
class="sortSelect"
180+
:value="playlistSelectValues[0]"
181+
:select-names="playlistSelectNames"
182+
:select-values="playlistSelectValues"
183+
:placeholder="$t('Search Filters.Sort By.Sort By')"
184+
@change="playlistSortBy = $event"
185+
/>
158186
<ft-loader
159187
v-if="isElementListLoading"
160188
/>
@@ -164,7 +192,10 @@
164192
>
165193
<ft-element-list
166194
v-show="currentTab === 'videos'"
195+
id="videoPanel"
167196
:data="latestVideos"
197+
role="tabpanel"
198+
aria-labelledby="videosTab"
168199
/>
169200
<ft-flex-box
170201
v-if="currentTab === 'videos' && latestVideos.length === 0"
@@ -175,7 +206,10 @@
175206
</ft-flex-box>
176207
<ft-element-list
177208
v-show="currentTab === 'playlists'"
209+
id="playlistPanel"
178210
:data="latestPlaylists"
211+
role="tabpanel"
212+
aria-labelledby="playlistsTab"
179213
/>
180214
<ft-flex-box
181215
v-if="currentTab === 'playlists' && latestPlaylists.length === 0"
@@ -198,7 +232,11 @@
198232
<div
199233
v-if="showFetchMoreButton"
200234
class="getNextPage"
235+
role="button"
236+
tabindex="0"
201237
@click="handleFetchMore"
238+
@keydown.space.prevent="handleFetchMore"
239+
@keydown.enter.prevent="handleFetchMore"
202240
>
203241
<font-awesome-icon :icon="['fas', 'search']" /> {{ $t("Search Filters.Fetch more results") }}
204242
</div>

0 commit comments

Comments
 (0)