Feature - add keyboard navigation support

This commit is contained in:
Sam McElligott 2022-01-03 12:30:41 +00:00
parent 8e7b2d38ea
commit f3ea83ccb4
12 changed files with 123 additions and 34 deletions

View File

@ -60,6 +60,15 @@
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 {
color: var(--text);
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 (
<div className="movieRow" onClick={() => props.onClick && props.onClick()}>
<div className="movieRow" tabIndex={0} onKeyPress={handleKeyPress} onClick={() => props.onClick && props.onClick()}>
{ props.source === "lookmovie" && (
<div className="subtitleIcon">

View File

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

View File

@ -7,10 +7,15 @@ import { PercentageOverlay } from './PercentageOverlay';
// choices: { label: string, value: string }[]
// selected: string
export function NumberSelector({ setType, choices, selected }) {
const handleKeyPress = choice => event => {
if (event.code === 'Space' || event.code === 'Enter'){
setType(choice);
}
}
return (
<div className="numberSelector">
{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)}>
{v.label}
<PercentageOverlay percentage={v.percentage} />

View File

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

View File

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

View File

@ -29,6 +29,15 @@
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 {
transition: transform 100ms ease-in-out;
transform: translateY(.1rem);
@ -39,3 +48,5 @@
transform: translateY(.1rem) translateX(-.5rem);
}

View File

@ -14,16 +14,24 @@ export function Title(props) {
const accentLink = props.accentLink || "";
const accent = props.accent || "";
function handleAccentClick(){
if (accentLink.length > 0) {
history.push(`/${streamData.type}`);
resetStreamData();
}
}
function handleKeyPress(event){
if (event.code === 'Enter' || event.code === 'Space'){
handleAccentClick();
}
}
return (
<div>
{accent.length > 0 ? (
<p onClick={() => {
if (accentLink.length > 0) {
history.push(`/${streamData.type}`);
resetStreamData();
}
}} className={`title-accent ${accentLink.length > 0 ? 'title-accent-link' : ''}`}>
<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}
</p>
) : null}

View File

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

View File

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

View File

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

View File

@ -207,6 +207,12 @@ export function SearchView() {
return <Redirect to="/movie" />
}
const handleKeyPress = page => event => {
if (event.code === 'Enter' || event.code === 'Space'){
setPage(page);
}
}
return (
<div className="cardView">
<Helmet>
@ -215,9 +221,9 @@ export function SearchView() {
{/* 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 ?
<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>