Improved header element accessibility when at mobile sizes
Intended to fix issues raised in #2681. Changes up the tri-layout tabs, and the main header menu toggle, to be buttons while adding better text and keyboard controls. Updated the component format of a few elements along the way.
This commit is contained in:
parent
60ffe6a993
commit
9df4dee1b2
|
@ -12,6 +12,7 @@ class DropDown {
|
||||||
this.menu = this.$refs.menu;
|
this.menu = this.$refs.menu;
|
||||||
this.toggle = this.$refs.toggle;
|
this.toggle = this.$refs.toggle;
|
||||||
this.moveMenu = this.$opts.moveMenu;
|
this.moveMenu = this.$opts.moveMenu;
|
||||||
|
this.bubbleEscapes = this.$opts.bubbleEscapes === 'true';
|
||||||
|
|
||||||
this.direction = (document.dir === 'rtl') ? 'right' : 'left';
|
this.direction = (document.dir === 'rtl') ? 'right' : 'left';
|
||||||
this.body = document.body;
|
this.body = document.body;
|
||||||
|
@ -137,7 +138,9 @@ class DropDown {
|
||||||
} else if (event.key === 'Escape') {
|
} else if (event.key === 'Escape') {
|
||||||
this.hide();
|
this.hide();
|
||||||
this.toggle.focus();
|
this.toggle.focus();
|
||||||
event.stopPropagation();
|
if (!this.bubbleEscapes) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.container.addEventListener('keydown', keyboardNavigation);
|
this.container.addEventListener('keydown', keyboardNavigation);
|
||||||
|
|
|
@ -1,31 +1,41 @@
|
||||||
|
|
||||||
class HeaderMobileToggle {
|
class HeaderMobileToggle {
|
||||||
|
|
||||||
constructor(elem) {
|
setup() {
|
||||||
this.elem = elem;
|
this.elem = this.$el;
|
||||||
this.toggleButton = elem.querySelector('.mobile-menu-toggle');
|
this.toggleButton = this.$refs.toggle;
|
||||||
this.menu = elem.querySelector('.header-links');
|
this.menu = this.$refs.menu;
|
||||||
this.open = false;
|
|
||||||
|
|
||||||
|
this.open = false;
|
||||||
this.toggleButton.addEventListener('click', this.onToggle.bind(this));
|
this.toggleButton.addEventListener('click', this.onToggle.bind(this));
|
||||||
this.onWindowClick = this.onWindowClick.bind(this);
|
this.onWindowClick = this.onWindowClick.bind(this);
|
||||||
|
this.onKeyDown = this.onKeyDown.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
onToggle(event) {
|
onToggle(event) {
|
||||||
this.open = !this.open;
|
this.open = !this.open;
|
||||||
this.menu.classList.toggle('show', this.open);
|
this.menu.classList.toggle('show', this.open);
|
||||||
|
this.toggleButton.setAttribute('aria-expanded', this.open ? 'true' : 'false');
|
||||||
if (this.open) {
|
if (this.open) {
|
||||||
|
this.elem.addEventListener('keydown', this.onKeyDown);
|
||||||
window.addEventListener('click', this.onWindowClick)
|
window.addEventListener('click', this.onWindowClick)
|
||||||
} else {
|
} else {
|
||||||
|
this.elem.removeEventListener('keydown', this.onKeyDown);
|
||||||
window.removeEventListener('click', this.onWindowClick)
|
window.removeEventListener('click', this.onWindowClick)
|
||||||
}
|
}
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onKeyDown(event) {
|
||||||
|
if (event.code === 'Escape') {
|
||||||
|
this.onToggle(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onWindowClick(event) {
|
onWindowClick(event) {
|
||||||
this.onToggle(event);
|
this.onToggle(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = HeaderMobileToggle;
|
export default HeaderMobileToggle;
|
|
@ -1,8 +1,9 @@
|
||||||
|
|
||||||
class TriLayout {
|
class TriLayout {
|
||||||
|
|
||||||
constructor(elem) {
|
setup() {
|
||||||
this.elem = elem;
|
this.container = this.$refs.container;
|
||||||
|
this.tabs = this.$manyRefs.tab;
|
||||||
|
|
||||||
this.lastLayoutType = 'none';
|
this.lastLayoutType = 'none';
|
||||||
this.onDestroy = null;
|
this.onDestroy = null;
|
||||||
|
@ -43,13 +44,12 @@ class TriLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
setupMobile() {
|
setupMobile() {
|
||||||
const layoutTabs = document.querySelectorAll('[tri-layout-mobile-tab]');
|
for (const tab of this.tabs) {
|
||||||
for (let tab of layoutTabs) {
|
|
||||||
tab.addEventListener('click', this.mobileTabClick);
|
tab.addEventListener('click', this.mobileTabClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onDestroy = () => {
|
this.onDestroy = () => {
|
||||||
for (let tab of layoutTabs) {
|
for (const tab of this.tabs) {
|
||||||
tab.removeEventListener('click', this.mobileTabClick);
|
tab.removeEventListener('click', this.mobileTabClick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ class TriLayout {
|
||||||
* @param event
|
* @param event
|
||||||
*/
|
*/
|
||||||
mobileTabClick(event) {
|
mobileTabClick(event) {
|
||||||
const tab = event.target.getAttribute('tri-layout-mobile-tab');
|
const tab = event.target.dataset.tab;
|
||||||
this.showTab(tab);
|
this.showTab(tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,21 +79,21 @@ class TriLayout {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the given tab
|
* Show the given tab
|
||||||
* @param tabName
|
* @param {String} tabName
|
||||||
|
* @param {Boolean }scroll
|
||||||
*/
|
*/
|
||||||
showTab(tabName, scroll = true) {
|
showTab(tabName, scroll = true) {
|
||||||
this.scrollCache[this.lastTabShown] = document.documentElement.scrollTop;
|
this.scrollCache[this.lastTabShown] = document.documentElement.scrollTop;
|
||||||
|
|
||||||
// Set tab status
|
// Set tab status
|
||||||
const tabs = document.querySelectorAll('.tri-layout-mobile-tab');
|
for (const tab of this.tabs) {
|
||||||
for (let tab of tabs) {
|
const isActive = (tab.dataset.tab === tabName);
|
||||||
const isActive = (tab.getAttribute('tri-layout-mobile-tab') === tabName);
|
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
||||||
tab.classList.toggle('active', isActive);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle section
|
// Toggle section
|
||||||
const showInfo = (tabName === 'info');
|
const showInfo = (tabName === 'info');
|
||||||
this.elem.classList.toggle('show-info', showInfo);
|
this.container.classList.toggle('show-info', showInfo);
|
||||||
|
|
||||||
// Set the scroll position from cache
|
// Set the scroll position from cache
|
||||||
if (scroll) {
|
if (scroll) {
|
||||||
|
|
|
@ -65,6 +65,7 @@ return [
|
||||||
'breadcrumb' => 'Breadcrumb',
|
'breadcrumb' => 'Breadcrumb',
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
|
'header_menu_expand' => 'Expand Header Menu',
|
||||||
'profile_menu' => 'Profile Menu',
|
'profile_menu' => 'Profile Menu',
|
||||||
'view_profile' => 'View Profile',
|
'view_profile' => 'View Profile',
|
||||||
'edit_profile' => 'Edit Profile',
|
'edit_profile' => 'Edit Profile',
|
||||||
|
@ -73,7 +74,9 @@ return [
|
||||||
|
|
||||||
// Layout tabs
|
// Layout tabs
|
||||||
'tab_info' => 'Info',
|
'tab_info' => 'Info',
|
||||||
|
'tab_info_label' => 'Tab: Show Secondary Information',
|
||||||
'tab_content' => 'Content',
|
'tab_content' => 'Content',
|
||||||
|
'tab_content_label' => 'Tab: Show Primary Content',
|
||||||
|
|
||||||
// Email Content
|
// Email Content
|
||||||
'email_action_help' => 'If you’re having trouble clicking the ":actionText" button, copy and paste the URL below into your web browser:',
|
'email_action_help' => 'If you’re having trouble clicking the ":actionText" button, copy and paste the URL below into your web browser:',
|
||||||
|
|
|
@ -191,6 +191,11 @@ header .search-box {
|
||||||
@include lightDark(color, #000, #fff);
|
@include lightDark(color, #000, #fff);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
&:focus {
|
||||||
|
@include lightDark(background-color, #eee, #333);
|
||||||
|
outline-color: var(--color-primary);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
header .dropdown-container {
|
header .dropdown-container {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -225,7 +230,7 @@ header .search-box {
|
||||||
&:first-child {
|
&:first-child {
|
||||||
border-inline-end: 1px solid #DDD;
|
border-inline-end: 1px solid #DDD;
|
||||||
}
|
}
|
||||||
&.active {
|
&[aria-selected="true"] {
|
||||||
border-bottom-color: currentColor;
|
border-bottom-color: currentColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
@include('partials.notifications')
|
@include('partials.notifications')
|
||||||
@include('common.header')
|
@include('common.header')
|
||||||
|
|
||||||
<div id="content" class="block">
|
<div id="content" components="@yield('content-components')" class="block">
|
||||||
@yield('content')
|
@yield('content')
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<header id="header" header-mobile-toggle class="primary-background">
|
<header id="header" component="header-mobile-toggle" class="primary-background">
|
||||||
<div class="grid mx-l">
|
<div class="grid mx-l">
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -10,7 +10,11 @@
|
||||||
<span class="logo-text">{{ setting('app-name') }}</span>
|
<span class="logo-text">{{ setting('app-name') }}</span>
|
||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
<div class="mobile-menu-toggle hide-over-l">@icon('more')</div>
|
<button type="button"
|
||||||
|
refs="header-mobile-toggle@toggle"
|
||||||
|
title="{{ trans('common.header_menu_expand') }}"
|
||||||
|
aria-expanded="false"
|
||||||
|
class="mobile-menu-toggle hide-over-l">@icon('more')</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-container-row justify-center hide-under-l">
|
<div class="flex-container-row justify-center hide-under-l">
|
||||||
|
@ -25,7 +29,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<nav class="header-links">
|
<nav refs="header-mobile-toggle@menu" class="header-links">
|
||||||
<div class="links text-center">
|
<div class="links text-center">
|
||||||
@if (hasAppAccess())
|
@if (hasAppAccess())
|
||||||
<a class="hide-over-l" href="{{ url('/search') }}">@icon('search'){{ trans('common.search') }}</a>
|
<a class="hide-over-l" href="{{ url('/search') }}">@icon('search'){{ trans('common.search') }}</a>
|
||||||
|
@ -50,7 +54,7 @@
|
||||||
</div>
|
</div>
|
||||||
@if(signedInUser())
|
@if(signedInUser())
|
||||||
<?php $currentUser = user(); ?>
|
<?php $currentUser = user(); ?>
|
||||||
<div class="dropdown-container" component="dropdown">
|
<div class="dropdown-container" component="dropdown" option:dropdown:bubble-escapes="true">
|
||||||
<span class="user-name py-s hide-under-l" refs="dropdown@toggle"
|
<span class="user-name py-s hide-under-l" refs="dropdown@toggle"
|
||||||
aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.profile_menu') }}" tabindex="0">
|
aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.profile_menu') }}" tabindex="0">
|
||||||
<img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
|
<img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
|
||||||
|
|
|
@ -1,21 +1,31 @@
|
||||||
@extends('base')
|
@extends('base')
|
||||||
|
|
||||||
@section('body-class', 'tri-layout')
|
@section('body-class', 'tri-layout')
|
||||||
|
@section('content-components', 'tri-layout')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
<div class="tri-layout-mobile-tabs text-primary print-hidden">
|
<div class="tri-layout-mobile-tabs print-hidden">
|
||||||
<div class="grid half no-break no-gap">
|
<div class="grid half no-break no-gap">
|
||||||
<div class="tri-layout-mobile-tab px-m py-s" tri-layout-mobile-tab="info">
|
<button type="button"
|
||||||
|
refs="tri-layout@tab"
|
||||||
|
data-tab="info"
|
||||||
|
aria-label="{{ trans('common.tab_info_label') }}"
|
||||||
|
class="tri-layout-mobile-tab px-m py-m text-primary">
|
||||||
{{ trans('common.tab_info') }}
|
{{ trans('common.tab_info') }}
|
||||||
</div>
|
</button>
|
||||||
<div class="tri-layout-mobile-tab px-m py-s active" tri-layout-mobile-tab="content">
|
<button type="button"
|
||||||
|
refs="tri-layout@tab"
|
||||||
|
data-tab="content"
|
||||||
|
aria-label="{{ trans('common.tab_content_label') }}"
|
||||||
|
aria-selected="true"
|
||||||
|
class="tri-layout-mobile-tab px-m py-m text-primary active">
|
||||||
{{ trans('common.tab_content') }}
|
{{ trans('common.tab_content') }}
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tri-layout-container" tri-layout @yield('container-attrs') >
|
<div refs="tri-layout@container" class="tri-layout-container" @yield('container-attrs') >
|
||||||
|
|
||||||
<div class="tri-layout-left print-hidden pt-m" id="sidebar">
|
<div class="tri-layout-left print-hidden pt-m" id="sidebar">
|
||||||
<aside class="tri-layout-left-contents">
|
<aside class="tri-layout-left-contents">
|
||||||
|
|
Loading…
Reference in New Issue