Make explorer tab-navigable

* use focus-trap-react
* switch clickable span to button
* let focus trap handle outside click
This commit is contained in:
Harris L 2017-02-11 06:58:32 +00:00 committed by Thibaud Colas
parent 81c6f3e3b1
commit 3f85c5fed5
6 changed files with 94 additions and 93 deletions

View file

@ -6,6 +6,7 @@ const PageCount = ({ id, count, title }) => (
<a
href={`${ADMIN_URLS.PAGES}${id}/`}
className="c-explorer__see-more"
tabIndex={0}
>
{STRINGS.EXPLORE_ALL_IN}{' '}
<span className="c-explorer__see-more__title">{title}</span>{' '}

View file

@ -16,10 +16,14 @@ const ExplorerHeader = ({ page, depth, onPop, transName }) => {
className: 'c-explorer__rel',
};
// TODO Do not use a span for a clickable element.
return (
<div className="c-explorer__header">
<span className={`c-explorer__trigger${depth > 1 ? ' c-explorer__trigger--enabled' : ''}`} onClick={onPop}>
<button
role="button"
className={`c-explorer__trigger${depth > 1 ? ' c-explorer__trigger--enabled' : ''}`}
onClick={onPop}
tabIndex={depth === 1 ? -1 : 0}
>
<span className="u-overflow c-explorer__overflow">
<CSSTransitionGroup {...transitionProps}>
<span className="c-explorer__parent-name" key={depth}>
@ -32,7 +36,7 @@ const ExplorerHeader = ({ page, depth, onPop, transName }) => {
</span>
</CSSTransitionGroup>
</span>
</span>
</button>
</div>
);
};

View file

@ -21,23 +21,23 @@ const ExplorerItem = ({ title, typeName, data, onItemClick }) => {
const hasChildren = count > 0;
return (
<Button href={`${ADMIN_URLS.PAGES}${id}`} className="c-explorer__item">
<div className="c-explorer__item">
{hasChildren ? (
<span
role="button"
className="c-explorer__children"
<button
type="button"
className="c-explorer__item__children"
onClick={onItemClick.bind(null, id)}
>
<Icon name="arrow-right" title={STRINGS.SEE_CHILDREN} />
</span>
</button>
) : null}
<h3 className="c-explorer__title">{title}</h3>
<p className="c-explorer__meta">
<span className="c-explorer__meta__type">{typeName}</span> | <AbsoluteDate time={time} /> | <PublicationStatus status={status} />
</p>
</Button>
<Button href={`${ADMIN_URLS.PAGES}${id}`} className="c-explorer__item__link">
<h3 className="c-explorer__title">{title}</h3>
<p className="c-explorer__meta">
<span className="c-explorer__meta__type">{typeName}</span> | <AbsoluteDate time={time} /> | <PublicationStatus status={status} />
</p>
</Button>
</div>
);
};

View file

@ -1,5 +1,6 @@
import React from 'react';
import CSSTransitionGroup from 'react-addons-css-transition-group';
import FocusTrap from 'focus-trap-react'
import { EXPLORER_ANIM_DURATION } from '../../config/config';
import { STRINGS } from '../../config/wagtail';
@ -13,7 +14,6 @@ import PageCount from './PageCount';
export default class ExplorerPanel extends React.Component {
constructor(props) {
super(props);
this.clickOutside = this.clickOutside.bind(this);
this.onItemClick = this.onItemClick.bind(this);
this.state = {
@ -51,34 +51,11 @@ export default class ExplorerPanel extends React.Component {
componentDidMount() {
this.props.init();
document.body.classList.add('explorer-open');
document.addEventListener('click', this.clickOutside);
var Anchors = document.getElementsByTagName("a");
for (var i = 0; i < Anchors.length ; i++) {
Anchors[i].addEventListener("click", this.clickOutside)
}
}
componentWillUnmount() {
document.body.classList.remove('explorer-open');
document.removeEventListener('click', this.clickOutside);
var Anchors = document.getElementsByTagName("a");
for (var i = 0; i < Anchors.length ; i++) {
Anchors[i].removeEventListener("click", this.clickOutside)
}
}
clickOutside(e) {
const { explorer } = this.refs;
if (!explorer) {
return;
}
if (!explorer.contains(e.target)) {
this.props.onClose();
}
}
getClass() {
@ -189,26 +166,36 @@ export default class ExplorerPanel extends React.Component {
};
return (
<div className={this.getClass()} ref="explorer">
<ExplorerHeader {...headerProps} transName={this.state.animation} />
<div className="c-explorer__drawer">
<CSSTransitionGroup {...transitionProps}>
<div {...transitionTargetProps}>
<CSSTransitionGroup {...innerTransitionProps}>
{page.isFetching ? <LoadingSpinner key={1} /> : (
<div key={0}>
{this.getContents()}
{(page.children.count > page.children.items.length) && (
<PageCount id={page.id} count={page.meta.children.count} title={page.title} />
)}
</div>
)}
</CSSTransitionGroup>
<FocusTrap
paused={page.isFetching}
focusTrapOptions={{
onDeactivate: this.props.onClose,
clickOutsideDeactivates: true,
}}
>
{/* FocusTrap gets antsy while the page is loading, so we give it something to focus on. */}
{page.isFetching && <div tabIndex={0} />}
<div className={this.getClass()} tabIndex={-1}>
<ExplorerHeader {...headerProps} transName={this.state.animation} />
<div className="c-explorer__drawer">
<CSSTransitionGroup {...transitionProps}>
<div {...transitionTargetProps}>
<CSSTransitionGroup {...innerTransitionProps}>
{page.isFetching ? <LoadingSpinner key={1} /> : (
<div key={0}>
{this.getContents()}
{(page.children.count > page.children.items.length) && (
<PageCount id={page.id} count={page.meta.children.count} title={page.title} />
)}
</div>
)}
</CSSTransitionGroup>
</div>
</CSSTransitionGroup>
</div>
</CSSTransitionGroup>
</div>
</div>
</div>
</FocusTrap>
);
}
}

View file

@ -32,15 +32,21 @@ $c-explorer-easing: cubic-bezier(0.075, 0.820, 0.165, 1.000);
white-space: nowrap;
overflow: hidden;
width: 100%;
float: left;
background: none;
border: none;
text-align: left;
color: $c-explorer-secondary;
line-height: inherit;
font: inherit;
cursor: default;
}
.c-explorer__trigger--enabled {
cursor: pointer;
&:hover {
&:hover, &:focus {
color: $color-white;
background: rgba(0,0,0,0.2);
outline: none;
}
}
@ -70,16 +76,6 @@ $c-explorer-easing: cubic-bezier(0.075, 0.820, 0.165, 1.000);
padding: 1rem;
}
.c-explorer__item {
padding: 1rem;
cursor: pointer;
border-bottom: solid 1px #676767;
&:last-child {
border-bottom: 0;
}
}
.c-explorer__placeholder {
padding: 1rem;
color: $color-white;
@ -99,10 +95,42 @@ $c-explorer-easing: cubic-bezier(0.075, 0.820, 0.165, 1.000);
.c-explorer__item {
display: block;
position: relative;
border-bottom: solid 1px #676767;
&:last-child {
border-bottom: 0;
}
}
&:hover {
.c-explorer__item__link {
display: block;
padding: 1rem;
cursor: pointer;
&:hover, &:focus {
background: rgba(0, 0, 0, 0.25);
color: $color-white;
outline: none;
}
}
.c-explorer__item__children {
display: inline-block;
color: $color-white;
line-height: 1;
padding: .7em .3em .7em .7em;
cursor: pointer;
display: inline-block;
position: absolute;
right: 0;
top: 0;
bottom: 0;
font-size: 2em;
background: transparent;
border: 0;
&:hover, &:focus {
background: rgba(0,0,0,0.5);
color: $color-white;
outline: none;
}
}
@ -112,9 +140,10 @@ $c-explorer-easing: cubic-bezier(0.075, 0.820, 0.165, 1.000);
color: $c-explorer-secondary;
display: block;
&:hover {
&:hover, &:focus {
color: $c-explorer-secondary;
background: rgba(0,0,0,0.4);
outline: none;
}
}
@ -122,27 +151,6 @@ $c-explorer-easing: cubic-bezier(0.075, 0.820, 0.165, 1.000);
color: $color-white;
}
.c-explorer__children {
display: inline-block;
color: $color-white;
line-height: 1;
padding: .7em .3em .7em .7em;
float: right;
cursor: pointer;
display: inline-block;
position: absolute;
right: 0;
top: 0;
bottom: 0;
font-size: 2em;
&:hover {
background: rgba(0,0,0,0.5);
color: $color-white;
}
}
.c-status {
background: $color-grey-1;
color: $c-explorer-secondary;

View file

@ -54,6 +54,7 @@
"webpack": "^1.12.14"
},
"dependencies": {
"focus-trap-react": "^3.0.2",
"lodash": "^4.17.4",
"moment": "^2.17.1",
"react": "^15.4.2",