Merge pull request #51 from sammce/accessibility

Feature - add keyboard navigation support
This commit is contained in:
James Hawkins 2022-01-08 20:13:10 +00:00 committed by GitHub
commit 9cf4e77194
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 123 additions and 34 deletions

View File

@ -60,6 +60,15 @@
transform: translateX(.3rem) translateY(.1rem); transform: translateX(.3rem) translateY(.1rem);
} }
.movieRow:focus-visible {
border: 1px solid #fff;
background-color: var(--content-hover);
}
.movieRow:focus-visible .watch .arrow {
transform: translateX(.3rem) translateY(.1rem);
}
.attribute { .attribute {
color: var(--text); color: var(--text);
background-color: var(--theme-color); background-color: var(--theme-color);

View File

@ -19,8 +19,14 @@ export function MovieRow(props) {
} }
} }
function handleKeyPress(event){
if ((event.code === 'Enter' || event.code === 'Space') && props.onClick){
props.onClick();
}
}
return ( return (
<div className="movieRow" onClick={() => props.onClick && props.onClick()}> <div className="movieRow" tabIndex={0} onKeyPress={handleKeyPress} onClick={() => props.onClick && props.onClick()}>
{ props.source === "lookmovie" && ( { props.source === "lookmovie" && (
<div className="subtitleIcon"> <div className="subtitleIcon">

View File

@ -39,10 +39,15 @@
box-sizing: border-box; box-sizing: border-box;
} }
.numberSelector .choice:hover { .numberSelector .choice:hover,
.numberSelector .choiceWrapper:focus-visible .choice {
background-color: var(--choice-hover); background-color: var(--choice-hover);
} }
.numberSelector .choiceWrapper:focus-visible {
border: 1px solid #fff;
}
.numberSelector .choice.selected { .numberSelector .choice.selected {
color: var(--choice-active-text, var(--text)); color: var(--choice-active-text, var(--text));
background-color: var(--choice-active); background-color: var(--choice-active);

View File

@ -7,10 +7,15 @@ import { PercentageOverlay } from './PercentageOverlay';
// choices: { label: string, value: string }[] // choices: { label: string, value: string }[]
// selected: string // selected: string
export function NumberSelector({ setType, choices, selected }) { export function NumberSelector({ setType, choices, selected }) {
const handleKeyPress = choice => event => {
if (event.code === 'Space' || event.code === 'Enter'){
setType(choice);
}
}
return ( return (
<div className="numberSelector"> <div className="numberSelector">
{choices.map(v=>( {choices.map(v=>(
<div key={v.value} className="choiceWrapper"> <div key={v.value} className="choiceWrapper" tabIndex={0} onKeyPress={handleKeyPress(v.value)}>
<div className={`choice ${selected&&selected===v.value?'selected':''}`} onClick={() => setType(v.value)}> <div className={`choice ${selected&&selected===v.value?'selected':''}`} onClick={() => setType(v.value)}>
{v.label} {v.label}
<PercentageOverlay percentage={v.percentage} /> <PercentageOverlay percentage={v.percentage} />

View File

@ -8,6 +8,10 @@
position: relative; position: relative;
} }
.select-box:focus-visible .selected {
border: 1px solid #fff;
}
.select-box > * { .select-box > * {
box-sizing: border-box; box-sizing: border-box;
} }

View File

@ -1,9 +1,9 @@
import { useRef, useState, useEffect } from "react" import { useRef, useState, useEffect } from "react"
import "./SelectBox.css" import "./SelectBox.css"
function Option({ option, onClick }) { function Option({ option, ...props }) {
return ( return (
<div className="option" onClick={onClick}> <div className="option" {...props}>
<input <input
type="radio" type="radio"
className="radio" className="radio"
@ -53,18 +53,30 @@ export function SelectBox({ options, selectedItem, setSelectedItem }) {
closeDropdown() closeDropdown()
} }
const handleSelectedKeyPress = event => {
if (event.code === 'Enter' || event.code === 'Space'){
setActive(a => !a);
}
}
const handleOptionKeyPress = (option, i) => event => {
if (event.code === 'Enter' || event.code === 'Space'){
onOptionClick(event, option, i);
}
}
return ( return (
<div className="select-box" ref={containerRef} onClick={() => setActive(a => !a)} > <div className="select-box" ref={containerRef} onClick={() => setActive(a => !a)} >
<div className={"options-container" + (active ? " active" : "")}> <div className="selected" tabIndex={0} onKeyPress={handleSelectedKeyPress}>
{options.map((opt, i) => (
<Option option={opt} key={i} onClick={(e) => onOptionClick(e, opt, i)} />
))}
</div>
<div className="selected">
{options ? ( {options ? (
<Option option={options[selectedItem]} /> <Option option={options[selectedItem]} />
) : null} ) : null}
</div> </div>
<div className={"options-container" + (active ? " active" : "")}>
{options.map((opt, i) => (
<Option option={opt} key={i} onClick={(e) => onOptionClick(e, opt, i)} tabIndex={active ? 0 : undefined} onKeyPress={active ? handleOptionKeyPress(opt, i) : undefined} />
))}
</div>
</div> </div>
) )
} }

View File

@ -29,6 +29,15 @@
cursor: pointer; cursor: pointer;
} }
.title.accent.title-accent-link:focus-visible {
border: 1px solid #ffffff;
}
.title.accent.title-accent-link:focus-visible .arrow {
transform: translateY(.1rem) translateX(-.5rem);
}
.title-accent.title-accent-link .arrow { .title-accent.title-accent-link .arrow {
transition: transform 100ms ease-in-out; transition: transform 100ms ease-in-out;
transform: translateY(.1rem); transform: translateY(.1rem);
@ -39,3 +48,5 @@
transform: translateY(.1rem) translateX(-.5rem); transform: translateY(.1rem) translateX(-.5rem);
} }

View File

@ -15,15 +15,23 @@ export function Title(props) {
const accentLink = props.accentLink || ""; const accentLink = props.accentLink || "";
const accent = props.accent || ""; const accent = props.accent || "";
return ( function handleAccentClick(){
<div>
{accent.length > 0 ? (
<p onClick={() => {
if (accentLink.length > 0) { if (accentLink.length > 0) {
history.push(`/${streamData.type}`); history.push(`/${streamData.type}`);
resetStreamData(); resetStreamData();
} }
}} className={`title-accent ${accentLink.length > 0 ? 'title-accent-link' : ''}`}> }
function handleKeyPress(event){
if (event.code === 'Enter' || event.code === 'Space'){
handleAccentClick();
}
}
return (
<div>
{accent.length > 0 ? (
<p onClick={handleAccentClick} className={`title-accent ${accentLink.length > 0 ? 'title-accent-link' : ''}`} tabIndex={accentLink.length > 0 ? 0 : undefined} onKeyPress={handleKeyPress}>
{accentLink.length > 0 ? (<Arrow left/>) : null}{accent} {accentLink.length > 0 ? (<Arrow left/>) : null}{accent}
</p> </p>
) : null} ) : null}

View File

@ -39,6 +39,11 @@
color: var(--text-secondary); color: var(--text-secondary);
} }
.typeSelector .choice:focus-visible {
border: 1px solid #fff;
color: var(--text-secondary);
}
.typeSelector .choice.selected { .typeSelector .choice.selected {
color: var(--text); color: var(--text);
} }

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import './TypeSelector.css' import './TypeSelector.css';
// setType: (txt: string) => void // setType: (txt: string) => void
// choices: { label: string, value: string }[] // choices: { label: string, value: string }[]
@ -8,16 +8,29 @@ export function TypeSelector({ setType, choices, selected, noWrap = false }) {
const selectedIndex = choices.findIndex(v => v.value === selected); const selectedIndex = choices.findIndex(v => v.value === selected);
const transformStyles = { const transformStyles = {
opacity: selectedIndex !== -1 ? 1 : 0, opacity: selectedIndex !== -1 ? 1 : 0,
transform: `translateX(${selectedIndex!==-1?selectedIndex*7:0}rem)` transform: `translateX(${selectedIndex !== -1 ? selectedIndex * 7 : 0}rem)`,
};
const handleKeyPress = choice => event => {
if (event.code === 'Enter' || event.code === 'Space') {
setType(choice);
} }
};
return ( return (
<div className={`typeSelector ${noWrap ? 'nowrap' : ''}`}> <div className={`typeSelector ${noWrap ? 'nowrap' : ''}`}>
{choices.map(v => ( {choices.map(v => (
<div key={v.value} className={`choice ${selected===v.value?'selected':''}`} onClick={() => setType(v.value)}> <div
key={v.value}
className={`choice ${selected === v.value ? 'selected' : ''}`}
onClick={() => setType(v.value)}
onKeyPress={handleKeyPress(v.value)}
tabIndex={0}
>
{v.label} {v.label}
</div> </div>
))} ))}
<div className="selectedBar" style={transformStyles} /> <div className="selectedBar" style={transformStyles} />
</div> </div>
) );
} }

View File

@ -18,6 +18,11 @@
border-radius: 4px; border-radius: 4px;
color: var(--text); color: var(--text);
} }
.cardView nav span:focus-visible {
border: 1px solid #fff;
}
.cardView nav span:not(.selected-link) { .cardView nav span:not(.selected-link) {
cursor: pointer; cursor: pointer;
} }

View File

@ -207,6 +207,12 @@ export function SearchView() {
return <Redirect to="/movie" /> return <Redirect to="/movie" />
} }
const handleKeyPress = page => event => {
if (event.code === 'Enter' || event.code === 'Space'){
setPage(page);
}
}
return ( return (
<div className="cardView"> <div className="cardView">
<Helmet> <Helmet>
@ -215,9 +221,9 @@ export function SearchView() {
{/* Nav */} {/* Nav */}
<nav> <nav>
<span className={page === 'search' ? 'selected-link' : ''} onClick={() => setPage('search')}>Search</span> <span className={page === 'search' ? 'selected-link' : ''} onClick={() => setPage('search')} onKeyPress={handleKeyPress('search')} tabIndex={0}>Search</span>
{continueWatching.length > 0 ? {continueWatching.length > 0 ?
<span className={page === 'watching' ? 'selected-link' : ''} onClick={() => setPage('watching')}>Continue watching</span> <span className={page === 'watching' ? 'selected-link' : ''} onClick={() => setPage('watching')} onKeyPress={handleKeyPress('watching')} tabIndex={0}>Continue watching</span>
: ''} : ''}
</nav> </nav>