Merge pull request #1935 from joachimesque/accessibility/fixes

[Accessibility] Fixes
This commit is contained in:
Mouse Reeve 2022-02-17 16:50:32 -08:00 committed by GitHub
commit 561eaeaf54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 251 additions and 105 deletions

View file

@ -301,6 +301,159 @@ details.dropdown .dropdown-menu a:focus-visible {
} }
} }
/** Bookwyrm Tabs
******************************************************************************/
.bw-tabs {
-webkit-overflow-scrolling:touch;
-webkit-touch-callout:none;
position: relative;
align-items:center;
display:flex;
font-size:1rem;
justify-content:flex-start;
overflow-x:auto;
overflow-y:hidden;
user-select:none;
white-space:nowrap
}
.bw-tabs::before {
border-bottom-color:#dbdbdb;
border-bottom-style:solid;
border-bottom-width:1px;
bottom: 0;
content:"";
position: absolute;
width: 100%;
}
.bw-tabs:not(:last-child) {
margin-bottom:1.5rem
}
.bw-tabs a {
align-items:center;
border-bottom-color:#dbdbdb;
border-bottom-style:solid;
border-bottom-width:1px;
color:#4a4a4a;
display:flex;
justify-content:center;
margin-bottom:-1px;
padding:.5em 1em;
position: relative;
}
.bw-tabs a:hover {
border-bottom-color:transparent;
color:#363636
}
.bw-tabs a.is-active {
border-bottom-color:transparent;
color:#3273dc
}
.bw-tabs.is-left {
padding-right:.75em
}
.bw-tabs.is-center {
flex:none;
justify-content:center;
padding-left:.75em;
padding-right:.75em
}
.bw-tabs.is-right {
justify-content:flex-end;
padding-left:.75em
}
.bw-tabs .icon:first-child {
margin-right:.5em
}
.bw-tabs .icon:last-child {
margin-left:.5em
}
.bw-tabs.is-centered {
justify-content:center
}
.bw-tabs.is-right {
justify-content:flex-end
}
.bw-tabs.is-boxed a {
border:1px solid transparent;
border-radius:4px 4px 0 0
}
.bw-tabs.is-boxed a:hover {
background-color:#f5f5f5;
border-bottom-color:#dbdbdb
}
.bw-tabs.is-boxed a.is-active {
background-color:#fff;
border-color:#dbdbdb;
border-bottom-color:#fff!important
}
.bw-tabs.is-fullwidth a {
flex-grow:1;
flex-shrink:0
}
.bw-tabs.is-toggle a {
border-color:#dbdbdb;
border-style:solid;
border-width:1px;
margin-bottom:0;
position:relative
}
.bw-tabs.is-toggle a:hover {
background-color:#f5f5f5;
border-color:#b5b5b5;
z-index:2
}
.bw-tabs.is-toggle a+a {
margin-left:-1px
}
.bw-tabs.is-toggle a:first-child {
border-top-left-radius:4px;
border-bottom-left-radius:4px
}
.bw-tabs.is-toggle a:last-child {
border-top-right-radius:4px;
border-bottom-right-radius:4px
}
.bw-tabs.is-toggle a.is-active {
background-color:#3273dc;
border-color:#3273dc;
color:#fff;
z-index:1
}
.bw-tabs.is-toggle {
border-bottom:none
}
.bw-tabs.is-toggle.is-toggle-rounded a:first-child {
border-bottom-left-radius:290486px;
border-top-left-radius:290486px;
padding-left:1.25em
}
.bw-tabs.is-toggle.is-toggle-rounded a:last-child {
border-bottom-right-radius:290486px;
border-top-right-radius:290486px;
padding-right:1.25em
}
.bw-tabs.is-small {
font-size:.75rem
}
.bw-tabs.is-medium {
font-size:1.25rem
}
.bw-tabs.is-large {
font-size:1.5rem
}
.bw-tabs.has-aside-text a {
margin-top: 1.5rem;
}
.bw-tabs a .aside-text {
position: absolute;
top: calc(-.75rem - .75rem);
left: 0;
color: #4a4a4a;
}
/** Details panel /** Details panel
******************************************************************************/ ******************************************************************************/

View file

@ -11,17 +11,17 @@ class TabGroup {
this.container = container; this.container = container;
this.tablist = this.container.querySelector('[role="tablist"]'); this.tablist = this.container.querySelector('[role="tablist"]');
this.buttons = this.tablist.querySelectorAll('[role="tab"]'); this.tabs = this.tablist.querySelectorAll('[role="tab"]');
this.panels = this.container.querySelectorAll(':scope > [role="tabpanel"]'); this.panels = this.container.querySelectorAll(':scope > [role="tabpanel"]');
this.delay = this.determineDelay(); this.delay = this.determineDelay();
if(!this.tablist || !this.buttons.length || !this.panels.length) { if(!this.tablist || !this.tabs.length || !this.panels.length) {
return; return;
} }
this.keys = this.keys(); this.keys = this.keys();
this.direction = this.direction(); this.direction = this.direction();
this.initButtons(); this.initTabs();
this.initPanels(); this.initPanels();
} }
@ -46,17 +46,21 @@ class TabGroup {
}; };
} }
initButtons() { initTabs() {
let count = 0; let count = 0;
for(let button of this.buttons) { for(let tab of this.tabs) {
let isSelected = button.getAttribute("aria-selected") === "true"; let isSelected = tab.getAttribute("aria-selected") === "true";
button.setAttribute("tabindex", isSelected ? "0" : "-1"); tab.setAttribute("tabindex", isSelected ? "0" : "-1");
button.addEventListener('click', this.clickEventListener.bind(this)); tab.addEventListener('click', this.clickEventListener.bind(this));
button.addEventListener('keydown', this.keydownEventListener.bind(this)); tab.addEventListener('keydown', this.keydownEventListener.bind(this));
button.addEventListener('keyup', this.keyupEventListener.bind(this)); tab.addEventListener('keyup', this.keyupEventListener.bind(this));
button.index = count++; if (isSelected) {
tab.scrollIntoView();
}
tab.index = count++;
} }
} }
@ -73,11 +77,11 @@ class TabGroup {
} }
clickEventListener(event) { clickEventListener(event) {
let button = event.target.closest('a'); let tab = event.target.closest('[role="tab"]');
event.preventDefault(); event.preventDefault();
this.activateTab(button, false); this.activateTab(tab, false);
} }
// Handle keydown on tabs // Handle keydown on tabs
@ -88,12 +92,12 @@ class TabGroup {
case this.keys.end: case this.keys.end:
event.preventDefault(); event.preventDefault();
// Activate last tab // Activate last tab
this.activateTab(this.buttons[this.buttons.length - 1]); this.activateTab(this.tabs[this.tabs.length - 1]);
break; break;
case this.keys.home: case this.keys.home:
event.preventDefault(); event.preventDefault();
// Activate first tab // Activate first tab
this.activateTab(this.buttons[0]); this.activateTab(this.tabs[0]);
break; break;
// Up and down are in keydown // Up and down are in keydown
@ -147,15 +151,15 @@ class TabGroup {
switchTabOnArrowPress(event) { switchTabOnArrowPress(event) {
var pressed = event.keyCode; var pressed = event.keyCode;
for (let button of this.buttons) { for (let tab of this.tabs) {
button.addEventListener('focus', this.focusEventHandler.bind(this)); tab.addEventListener('focus', this.focusEventHandler.bind(this));
} }
if (this.direction[pressed]) { if (this.direction[pressed]) {
var target = event.target; var target = event.target;
if (target.index !== undefined) { if (target.index !== undefined) {
if (this.buttons[target.index + this.direction[pressed]]) { if (this.tabs[target.index + this.direction[pressed]]) {
this.buttons[target.index + this.direction[pressed]].focus(); this.tabs[target.index + this.direction[pressed]].focus();
} }
else if (pressed === this.keys.left || pressed === this.keys.up) { else if (pressed === this.keys.left || pressed === this.keys.up) {
this.focusLastTab(); this.focusLastTab();
@ -184,8 +188,8 @@ class TabGroup {
// Set the tab as selected // Set the tab as selected
tab.setAttribute('aria-selected', 'true'); tab.setAttribute('aria-selected', 'true');
// Give the tab parent an is-active class // Give the tab is-active class
tab.parentNode.classList.add('is-active'); tab.classList.add('is-active');
// Get the value of aria-controls (which is an ID) // Get the value of aria-controls (which is an ID)
var controls = tab.getAttribute('aria-controls'); var controls = tab.getAttribute('aria-controls');
@ -201,11 +205,11 @@ class TabGroup {
// Deactivate all tabs and tab panels // Deactivate all tabs and tab panels
deactivateTabs() { deactivateTabs() {
for (let button of this.buttons) { for (let tab of this.tabs) {
button.parentNode.classList.remove('is-active'); tab.classList.remove('is-active');
button.setAttribute('tabindex', '-1'); tab.setAttribute('tabindex', '-1');
button.setAttribute('aria-selected', 'false'); tab.setAttribute('aria-selected', 'false');
button.removeEventListener('focus', this.focusEventHandler.bind(this)); tab.removeEventListener('focus', this.focusEventHandler.bind(this));
} }
for (let panel of this.panels) { for (let panel of this.panels) {
@ -214,11 +218,11 @@ class TabGroup {
} }
focusFirstTab() { focusFirstTab() {
this.buttons[0].focus(); this.tabs[0].focus();
} }
focusLastTab() { focusLastTab() {
this.buttons[this.buttons.length - 1].focus(); this.tabs[this.tabs.length - 1].focus();
} }
// Determine whether there should be a delay // Determine whether there should be a delay

View file

@ -30,7 +30,7 @@
<div class="column mt-3-mobile ml-3-tablet"> <div class="column mt-3-mobile ml-3-tablet">
<div class="media block mb-2"> <div class="media block mb-2">
<figure class="media-left" aria-hidden="true"> <figure class="media-left" aria-hidden="true">
<a class="image is-48x48" href="{{ status.user.local_path }}"> <a class="image is-48x48" href="{{ status.user.local_path }}" tabindex="-1">
{% include 'snippets/avatar.html' with user=status.user ariaHide="true" medium="true" %} {% include 'snippets/avatar.html' with user=status.user ariaHide="true" medium="true" %}
</a> </a>
</figure> </figure>

View file

@ -15,7 +15,7 @@
<div class="media block mb-2"> <div class="media block mb-2">
<figure class="media-left" aria-hidden="true"> <figure class="media-left" aria-hidden="true">
<a class="image is-48x48" href="{{ status.user.local_path }}"> <a class="image is-48x48" href="{{ status.user.local_path }}" tabindex="-1">
{% include 'snippets/avatar.html' with user=status.user ariaHide="true" medium="true" %} {% include 'snippets/avatar.html' with user=status.user ariaHide="true" medium="true" %}
</a> </a>
</figure> </figure>

View file

@ -9,37 +9,30 @@
{% else %} {% else %}
{% with active_book=request.GET.book %} {% with active_book=request.GET.book %}
<div class="tab-group"> <div class="tab-group">
<div class="tabs is-small"> <div class="bw-tabs is-small is-toggle has-aside-text" role="tablist">
<ul role="tablist"> {% for shelf in suggested_books %}
{% for shelf in suggested_books %} {% if shelf.books %}
{% if shelf.books %} {% with shelf_counter=forloop.counter %}
{% with shelf_counter=forloop.counter %} {% for book in shelf.books %}
<li> <a
<p> class="{% if active_book == book.id|stringformat:'d' %}is-active{% elif not active_book and shelf_counter == 1 and forloop.first %}is-active{% endif %}"
href="{{ request.path }}?book={{ book.id }}"
id="tab_book_{{ book.id }}"
role="tab"
aria-label="{{ book.title }}"
aria-selected="{% if active_book == book.id|stringformat:'d' %}true{% elif not active_book and shelf_counter == 1 and forloop.first %}true{% else %}false{% endif %}"
aria-controls="book_{{ book.id }}">
{% if forloop.first %}
<span class="aside-text">
{% include "snippets/translated_shelf_name.html" with shelf=shelf %} {% include "snippets/translated_shelf_name.html" with shelf=shelf %}
</p> </span>
<div class="tabs is-small is-toggle"> {% endif %}
<ul> {% include 'snippets/book_cover.html' with book=book cover_class='is-h-m' %}
{% for book in shelf.books %} </a>
<li class="{% if active_book == book.id|stringformat:'d' %}is-active{% elif not active_book and shelf_counter == 1 and forloop.first %}is-active{% endif %}">
<a
href="{{ request.path }}?book={{ book.id }}"
id="tab_book_{{ book.id }}"
role="tab"
aria-label="{{ book.title }}"
aria-selected="{% if active_book == book.id|stringformat:'d' %}true{% elif shelf_counter == 1 and forloop.first %}true{% else %}false{% endif %}"
aria-controls="book_{{ book.id }}">
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m' %}
</a>
</li>
{% endfor %}
</ul>
</div>
</li>
{% endwith %}
{% endif %}
{% endfor %} {% endfor %}
</ul> {% endwith %}
{% endif %}
{% endfor %}
</div> </div>
{% for shelf in suggested_books %} {% for shelf in suggested_books %}
{% with shelf_counter=forloop.counter %} {% with shelf_counter=forloop.counter %}

View file

@ -207,12 +207,12 @@
</div> </div>
{% endif %} {% endif %}
<div class="section is-flex-grow-1"> <main class="section is-flex-grow-1">
<div class="container"> <div class="container">
{% block content %} {% block content %}
{% endblock %} {% endblock %}
</div> </div>
</div> </main>
<div role="region" aria-live="polite" id="live-messages"> <div role="region" aria-live="polite" id="live-messages">
<p id="status-success-message" class="live-message is-sr-only is-hidden">{% trans "Successfully posted status" %}</p> <p id="status-success-message" class="live-message is-sr-only is-hidden">{% trans "Successfully posted status" %}</p>

View file

@ -92,7 +92,7 @@
<summary> <summary>
<span role="heading" aria-level="3"> <span role="heading" aria-level="3">
{% trans "Edit notes" %} {% trans "Edit notes" %}
<span class="details-close icon icon-pencil" aria-hidden></span> <span class="details-close icon icon-pencil" aria-hidden="true"></span>
</span> </span>
</summary> </summary>
{% include "lists/edit_item_form.html" with book=item.book %} {% include "lists/edit_item_form.html" with book=item.book %}
@ -107,7 +107,7 @@
<summary> <summary>
<span role="heading" aria-level="3"> <span role="heading" aria-level="3">
{% trans "Add notes" %} {% trans "Add notes" %}
<span class="details-close icon icon-plus" aria-hidden></span> <span class="details-close icon icon-plus" aria-hidden="true"></span>
</span> </span>
</summary> </summary>
{% include "lists/edit_item_form.html" with book=item.book %} {% include "lists/edit_item_form.html" with book=item.book %}

View file

@ -45,7 +45,7 @@
<a href="{{ result_set.connector.base_url }}" target="_blank">{{ result_set.connector.name|default:result_set.connector.identifier }}</a> <a href="{{ result_set.connector.base_url }}" target="_blank">{{ result_set.connector.name|default:result_set.connector.identifier }}</a>
</span> </span>
<span class="details-close icon icon-x" aria-hidden></span> <span class="details-close icon icon-x" aria-hidden="true"></span>
</summary> </summary>
{% endif %} {% endif %}

View file

@ -53,7 +53,7 @@
{% trans "View links" %} {% trans "View links" %}
({{ domain.links.count }}) ({{ domain.links.count }})
</span> </span>
<span class="details-close icon icon-x" aria-hidden></span> <span class="details-close icon icon-x" aria-hidden="true"></span>
</summary> </summary>
<div class="table-container mt-4"> <div class="table-container mt-4">

View file

@ -21,7 +21,7 @@
<details class="details-panel box"> <details class="details-panel box">
<summary> <summary>
<span class="title is-4">{% trans "Message reporter" %}</span> <span class="title is-4">{% trans "Message reporter" %}</span>
<span class="details-close icon icon-x" aria-hidden></span> <span class="details-close icon icon-x" aria-hidden="true"></span>
</summary> </summary>
<div class="box"> <div class="box">
{% trans "Update on your report:" as dm_template %} {% trans "Update on your report:" as dm_template %}

View file

@ -4,42 +4,37 @@
{% with status_type=request.GET.status_type %} {% with status_type=request.GET.status_type %}
<div class="tab-group"> <div class="tab-group">
<div class="tabs is-boxed" role="tablist"> <div class="bw-tabs is-boxed" role="tablist">
<ul> <a
<li class="{% if status_type == 'review' or not status_type %}is-active{% endif %}"> class="{% if status_type == 'review' or not status_type %}is-active{% endif %}"
<a role="tab"
href="{{ request.path }}?status_type=review&book={{ book.id }}" href="{{ request.path }}?status_type=review&book={{ book.id }}"
id="tab_review_{{ book.id }}" id="tab_review_{{ book.id }}"
role="tab" aria-selected="{% if status_type == 'review' or not status_type %}true{% else %}false{% endif %}"
aria-selected="{% if status_type == 'review' or not status_type %}true{% else %}false{% endif %}" aria-controls="review_{{ book.id }}"
aria-controls="review_{{ book.id }}" data-category="tab-option-{{ book.id }}">
data-category="tab-option-{{ book.id }}"> {% trans "Review" %}
{% trans "Review" %} </a>
</a> <a
</li> class="{% if status_type == 'comment' %}is-active{% endif %}"
<li class="{% if status_type == 'comment' %}is-active{% endif %}"> role="tab"
<a href="{{ request.path }}?status_type=comment&book={{ book.id}}"
href="{{ request.path }}?status_type=comment&book={{ book.id}}" id="tab_comment_{{ book.id }}"
id="tab_comment_{{ book.id }}" aria-selected="{% if status_type == 'comment' %}true{% else %}false{% endif %}"
role="tab" aria-controls="comment_{{ book.id}}"
aria-selected="{% if status_type == 'comment' %}true{% else %}false{% endif %}" data-category="tab-option-{{ book.id }}">
aria-controls="comment_{{ book.id}}" {% trans "Comment" %}
data-category="tab-option-{{ book.id }}"> </a>
{% trans "Comment" %} <a
</a> class="{% if status_type == 'quote' %}is-active{% endif %}"
</li> role="tab"
<li class="{% if status_type == 'quote' %}is-active{% endif %}"> href="{{ request.path }}?status_type=quote&book={{ book.id }}"
<a id="tab_quote_{{ book.id }}"
href="{{ request.path }}?status_type=quote&book={{ book.id }}" aria-selected="{% if status_type == 'quote' %}true{% else %}false{% endif %}"
id="tab_quote_{{ book.id }}" aria-controls="quote_{{ book.id }}"
role="tab" data-category="tab-option-{{ book.id }}">
aria-selected="{% if status_type == 'quote' %}true{% else %}false{% endif %}" {% trans "Quote" %}
aria-controls="quote_{{ book.id }}" </a>
data-category="tab-option-{{ book.id }}">
{% trans "Quote" %}
</a>
</li>
</ul>
</div> </div>
<div class="tab-option-{{ book.id }}" id="review_{{ book.id }}" role="tabpanel" aria-labelledby="tab_review_{{ book.id }}" {% if status_type and status_type != "review" %}hidden{% endif %}> <div class="tab-option-{{ book.id }}" id="review_{{ book.id }}" role="tabpanel" aria-labelledby="tab_review_{{ book.id }}" {% if status_type and status_type != "review" %}hidden{% endif %}>

View file

@ -22,7 +22,7 @@
</span> </span>
{% endif %} {% endif %}
<span class="details-close icon icon-x is-{{ size|default:'normal' }}" aria-hidden></span> <span class="details-close icon icon-x is-{{ size|default:'normal' }}" aria-hidden="true"></span>
</summary> </summary>
<div class="mt-3"> <div class="mt-3">

View file

@ -5,10 +5,10 @@
{% with next_shelf_identifier=active_shelf.shelf.identifier|next_shelf %} {% with next_shelf_identifier=active_shelf.shelf.identifier|next_shelf %}
{% for shelf in shelves %} {% for shelf in shelves %}
{% if next_shelf_identifier != shelf.identifier %}
{% comparison_bool shelf.identifier active_shelf.shelf.identifier as is_current %} {% comparison_bool shelf.identifier active_shelf.shelf.identifier as is_current %}
<li role="menuitem" class="dropdown-item p-0"> <li role="menuitem" class="dropdown-item p-0">
<div <div
class="{% if next_shelf_identifier == shelf.identifier %}is-hidden{% endif %}"
data-shelf-dropdown-identifier="{{ shelf.identifier }}" data-shelf-dropdown-identifier="{{ shelf.identifier }}"
data-shelf-next="{{ shelf.identifier|next_shelf }}" data-shelf-next="{{ shelf.identifier|next_shelf }}"
> >
@ -46,6 +46,7 @@
{% endif %} {% endif %}
</div> </div>
</li> </li>
{% endif %}
{% endfor %} {% endfor %}
{% if readthrough and active_shelf.shelf.identifier != 'read' %} {% if readthrough and active_shelf.shelf.identifier != 'read' %}

View file

@ -5,7 +5,7 @@
<div class="media"> <div class="media">
<figure class="media-left" aria-hidden="true"> <figure class="media-left" aria-hidden="true">
<a class="image is-48x48" href="{{ status.user.local_path }}"> <a class="image is-48x48" href="{{ status.user.local_path }}" tabindex="-1">
{% include 'snippets/avatar.html' with user=status.user ariaHide="true" medium="true" %} {% include 'snippets/avatar.html' with user=status.user ariaHide="true" medium="true" %}
</a> </a>
</figure> </figure>