mirror of
https://github.com/FreeTubeApp/FreeTube
synced 2024-12-14 05:29:27 +01:00
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 <84899178+jasonhenriquez@users.noreply.github.com> * Update src/renderer/components/ft-channel-bubble/ft-channel-bubble.js Co-authored-by: PikachuEXE <pikachuexe@gmail.com> * Stop space from clicking channel-bubble (links) Co-authored-by: vallode <18506096+vallode@users.noreply.github.com> Co-authored-by: Jason <84899178+jasonhenriquez@users.noreply.github.com> Co-authored-by: PikachuEXE <pikachuexe@gmail.com>
This commit is contained in:
parent
156176aca8
commit
bff8dc4326
@ -1,4 +1,5 @@
|
||||
import Vue from 'vue'
|
||||
import { sanitizeForHtmlId } from '../../helpers/accessibility'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'FtChannelBubble',
|
||||
@ -21,8 +22,20 @@ export default Vue.extend({
|
||||
selected: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sanitizedId: function() {
|
||||
return 'channelBubble' + sanitizeForHtmlId(this.channelName)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick: function () {
|
||||
handleClick: function (event) {
|
||||
if (event instanceof KeyboardEvent) {
|
||||
if (event.target.getAttribute('role') === 'link' && event.key !== 'Enter') {
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
if (this.showSelected) {
|
||||
this.selected = !this.selected
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div
|
||||
class="bubblePadding"
|
||||
tabindex="0"
|
||||
:aria-labelledby="sanitizedId"
|
||||
@click="handleClick"
|
||||
@keydown.space.enter.prevent="handleClick($event)"
|
||||
>
|
||||
<img
|
||||
class="bubble"
|
||||
@ -16,7 +19,10 @@
|
||||
class="icon"
|
||||
/>
|
||||
</div>
|
||||
<div class="channelName">
|
||||
<div
|
||||
:id="sanitizedId"
|
||||
class="channelName"
|
||||
>
|
||||
{{ channelName }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,7 +6,7 @@
|
||||
}
|
||||
|
||||
.channelDetails {
|
||||
padding: 0 0 16px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.channelBannerContainer {
|
||||
@ -75,38 +75,51 @@
|
||||
|
||||
.channelSearch {
|
||||
width: 220px;
|
||||
margin-left: auto;
|
||||
align-self: flex-end;
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
|
||||
.sortSelect {
|
||||
align-self: flex-end;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.channelInfoTabs {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-top: -16px;
|
||||
margin-bottom: -13px;
|
||||
height: auto;
|
||||
justify-content: unset;
|
||||
gap: 32px;
|
||||
padding: .3em 0;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex: 0 1 33%;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 15px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
flex: 1 1 0%;
|
||||
align-self: flex-end;
|
||||
text-align: center;
|
||||
color: var(--tertiary-text-color);
|
||||
}
|
||||
|
||||
.selectedTab {
|
||||
color: var(--primary-text-color);
|
||||
border-bottom: 3px solid var(--primary-color);
|
||||
margin-bottom: -3px;
|
||||
font-weight: bold;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 3px solid transparent;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
font-weight: bold;
|
||||
border-bottom: 3px solid var(--tertiary-text-color);
|
||||
}
|
||||
|
||||
.selectedTab,
|
||||
.selectedTab:hover {
|
||||
color: var(--primary-text-color);
|
||||
border-bottom: 3px solid var(--primary-color);
|
||||
font-weight: bold;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.aboutTab {
|
||||
@ -125,10 +138,7 @@
|
||||
|
||||
.channelSearch {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.elementList {
|
||||
margin-top: 15px;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.elementListLoading {
|
||||
@ -164,3 +174,15 @@
|
||||
.channelLineContainer p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.channelInfoTabs {
|
||||
height: auto;
|
||||
flex-flow: column-reverse;
|
||||
}
|
||||
|
||||
.channelSearch {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
@ -68,6 +68,11 @@ export default Vue.extend({
|
||||
playlistSelectValues: [
|
||||
'last',
|
||||
'newest'
|
||||
],
|
||||
tabInfoValues: [
|
||||
'videos',
|
||||
'playlists',
|
||||
'about'
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -652,8 +657,34 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
|
||||
changeTab: function (tab) {
|
||||
changeTab: function (tab, event) {
|
||||
if (event instanceof KeyboardEvent) {
|
||||
// use arrowkeys to navigate
|
||||
event.preventDefault()
|
||||
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
|
||||
const index = this.tabInfoValues.indexOf(tab)
|
||||
|
||||
// focus left or right tab with wrap around
|
||||
tab = (event.key === 'ArrowLeft')
|
||||
? this.tabInfoValues[(index > 0 ? index : this.tabInfoValues.length) - 1]
|
||||
: this.tabInfoValues[(index + 1) % this.tabInfoValues.length]
|
||||
|
||||
const tabNode = document.getElementById(`${tab}Tab`)
|
||||
event.target.setAttribute('tabindex', '-1')
|
||||
tabNode.setAttribute('tabindex', 0)
|
||||
tabNode.focus({ focusVisible: true })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const currentTabNode = document.querySelector('.tabs > .tab[aria-selected="true"]')
|
||||
const newTabNode = document.getElementById(`${tab}Tab`)
|
||||
document.querySelector('.tabs > .tab[tabindex="0"]').setAttribute('tabindex', '-1')
|
||||
newTabNode.setAttribute('tabindex', '0')
|
||||
currentTabNode.setAttribute('aria-selected', 'false')
|
||||
newTabNode.setAttribute('aria-selected', 'true')
|
||||
this.currentTab = tab
|
||||
newTabNode.focus({ focusVisible: true })
|
||||
},
|
||||
|
||||
newSearch: function (query) {
|
||||
|
@ -30,6 +30,7 @@
|
||||
<img
|
||||
class="channelThumbnail"
|
||||
:src="thumbnailUrl"
|
||||
alt=""
|
||||
>
|
||||
<div
|
||||
class="channelLineContainer"
|
||||
@ -74,50 +75,57 @@
|
||||
class="channelInfoTabs"
|
||||
>
|
||||
<div
|
||||
class="tab"
|
||||
:class="(currentTab==='videos')?'selectedTab':''"
|
||||
@click="changeTab('videos')"
|
||||
class="tabs"
|
||||
role="tablist"
|
||||
:aria-label="$t('Channel.Channel Tabs')"
|
||||
>
|
||||
{{ $t("Channel.Videos.Videos").toUpperCase() }}
|
||||
</div>
|
||||
<div
|
||||
class="tab"
|
||||
:class="(currentTab==='playlists')?'selectedTab':''"
|
||||
@click="changeTab('playlists')"
|
||||
>
|
||||
{{ $t("Channel.Playlists.Playlists").toUpperCase() }}
|
||||
</div>
|
||||
<div
|
||||
class="tab"
|
||||
:class="(currentTab==='about')?'selectedTab':''"
|
||||
@click="changeTab('about')"
|
||||
>
|
||||
{{ $t("Channel.About.About").toUpperCase() }}
|
||||
<div
|
||||
id="videosTab"
|
||||
class="tab"
|
||||
:class="(currentTab==='videos')?'selectedTab':''"
|
||||
role="tab"
|
||||
aria-selected="true"
|
||||
aria-controls="videoPanel"
|
||||
tabindex="0"
|
||||
@click="changeTab('videos')"
|
||||
@keydown.left.right.enter.space="changeTab('videos', $event)"
|
||||
>
|
||||
{{ $t("Channel.Videos.Videos").toUpperCase() }}
|
||||
</div>
|
||||
<div
|
||||
id="playlistsTab"
|
||||
class="tab"
|
||||
role="tab"
|
||||
aria-selected="false"
|
||||
aria-controls="playlistPanel"
|
||||
tabindex="-1"
|
||||
:class="(currentTab==='playlists')?'selectedTab':''"
|
||||
@click="changeTab('playlists')"
|
||||
@keydown.left.right.enter.space="changeTab('playlists', $event)"
|
||||
>
|
||||
{{ $t("Channel.Playlists.Playlists").toUpperCase() }}
|
||||
</div>
|
||||
<div
|
||||
id="aboutTab"
|
||||
class="tab"
|
||||
role="tab"
|
||||
aria-selected="false"
|
||||
aria-controls="aboutPanel"
|
||||
tabindex="-1"
|
||||
:class="(currentTab==='about')?'selectedTab':''"
|
||||
@click="changeTab('about')"
|
||||
@keydown.left.right.enter.space="changeTab('about', $event)"
|
||||
>
|
||||
{{ $t("Channel.About.About").toUpperCase() }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ft-input
|
||||
:placeholder="$t('Channel.Search Channel')"
|
||||
:show-clear-text-button="true"
|
||||
class="channelSearch"
|
||||
@click="newSearch"
|
||||
/>
|
||||
<ft-select
|
||||
v-show="currentTab === 'videos'"
|
||||
class="sortSelect"
|
||||
:value="videoSelectValues[0]"
|
||||
:select-names="videoSelectNames"
|
||||
:select-values="videoSelectValues"
|
||||
:placeholder="$t('Search Filters.Sort By.Sort By')"
|
||||
@change="videoSortBy = $event"
|
||||
/>
|
||||
<ft-select
|
||||
v-show="currentTab === 'playlists'"
|
||||
class="sortSelect"
|
||||
:value="playlistSelectValues[0]"
|
||||
:select-names="playlistSelectNames"
|
||||
:select-values="playlistSelectValues"
|
||||
:placeholder="$t('Search Filters.Sort By.Sort By')"
|
||||
@change="playlistSortBy = $event"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
</div>
|
||||
</ft-card>
|
||||
@ -127,6 +135,7 @@
|
||||
>
|
||||
<div
|
||||
v-if="currentTab === 'about'"
|
||||
id="aboutPanel"
|
||||
class="aboutTab"
|
||||
>
|
||||
<h2>
|
||||
@ -151,10 +160,29 @@
|
||||
:channel-name="channel.author || channel.channelName"
|
||||
:channel-id="channel.channelId"
|
||||
:channel-thumbnail="channel.authorThumbnails[channel.authorThumbnails.length - 1].url"
|
||||
role="link"
|
||||
@click="goToChannel(channel.channelId)"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
</div>
|
||||
<ft-select
|
||||
v-show="currentTab === 'videos'"
|
||||
class="sortSelect"
|
||||
:value="videoSelectValues[0]"
|
||||
:select-names="videoSelectNames"
|
||||
:select-values="videoSelectValues"
|
||||
:placeholder="$t('Search Filters.Sort By.Sort By')"
|
||||
@change="videoSortBy = $event"
|
||||
/>
|
||||
<ft-select
|
||||
v-show="currentTab === 'playlists'"
|
||||
class="sortSelect"
|
||||
:value="playlistSelectValues[0]"
|
||||
:select-names="playlistSelectNames"
|
||||
:select-values="playlistSelectValues"
|
||||
:placeholder="$t('Search Filters.Sort By.Sort By')"
|
||||
@change="playlistSortBy = $event"
|
||||
/>
|
||||
<ft-loader
|
||||
v-if="isElementListLoading"
|
||||
/>
|
||||
@ -164,7 +192,10 @@
|
||||
>
|
||||
<ft-element-list
|
||||
v-show="currentTab === 'videos'"
|
||||
id="videoPanel"
|
||||
:data="latestVideos"
|
||||
role="tabpanel"
|
||||
aria-labelledby="videosTab"
|
||||
/>
|
||||
<ft-flex-box
|
||||
v-if="currentTab === 'videos' && latestVideos.length === 0"
|
||||
@ -175,7 +206,10 @@
|
||||
</ft-flex-box>
|
||||
<ft-element-list
|
||||
v-show="currentTab === 'playlists'"
|
||||
id="playlistPanel"
|
||||
:data="latestPlaylists"
|
||||
role="tabpanel"
|
||||
aria-labelledby="playlistsTab"
|
||||
/>
|
||||
<ft-flex-box
|
||||
v-if="currentTab === 'playlists' && latestPlaylists.length === 0"
|
||||
@ -198,7 +232,11 @@
|
||||
<div
|
||||
v-if="showFetchMoreButton"
|
||||
class="getNextPage"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="handleFetchMore"
|
||||
@keydown.space.prevent="handleFetchMore"
|
||||
@keydown.enter.prevent="handleFetchMore"
|
||||
>
|
||||
<font-awesome-icon :icon="['fas', 'search']" /> {{ $t("Search Filters.Fetch more results") }}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user