mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-05 16:51:01 +00:00
feat(useVerticalSpatialNav): added hook for enabling spatial nav on vertical navigation
This commit is contained in:
parent
41865276d5
commit
476f2f8551
3 changed files with 68 additions and 4 deletions
|
|
@ -1,9 +1,10 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import React, { memo, useEffect } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { VerticalNavBar, HorizontalNavBar } from 'stremio/components/NavBar';
|
||||
import styles from './MainNavBars.less';
|
||||
import { useGamepad, useVerticalSpatialNavigation } from 'stremio/services';
|
||||
|
||||
const TABS = [
|
||||
{ id: 'board', label: 'Board', icon: 'home', href: '#/' },
|
||||
|
|
@ -21,7 +22,13 @@ type Props = {
|
|||
children?: React.ReactNode,
|
||||
};
|
||||
|
||||
const GAMEPAD_HANDLER_ID = 'vertical-nav';
|
||||
|
||||
const MainNavBars = memo(({ className, route, query, children }: Props) => {
|
||||
const navRef = React.useRef(null);
|
||||
|
||||
useVerticalSpatialNavigation(navRef, GAMEPAD_HANDLER_ID);
|
||||
|
||||
return (
|
||||
<div className={classnames(className, styles['main-nav-bars-container'])}>
|
||||
<HorizontalNavBar
|
||||
|
|
@ -34,6 +41,7 @@ const MainNavBars = memo(({ className, route, query, children }: Props) => {
|
|||
navMenu={true}
|
||||
/>
|
||||
<VerticalNavBar
|
||||
ref={navRef}
|
||||
className={styles['vertical-nav-bar']}
|
||||
selected={route}
|
||||
tabs={TABS}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ const { useTranslation } = require('react-i18next');
|
|||
const NavTabButton = require('./NavTabButton');
|
||||
const styles = require('./styles');
|
||||
|
||||
const VerticalNavBar = React.memo(({ className, selected, tabs }) => {
|
||||
const VerticalNavBar = React.memo(React.forwardRef(({ className, selected, tabs }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<nav className={classnames(className, styles['vertical-nav-bar-container'])}>
|
||||
<nav ref={ref} className={classnames(className, styles['vertical-nav-bar-container'])}>
|
||||
{
|
||||
Array.isArray(tabs) ?
|
||||
tabs.map((tab, index) => (
|
||||
|
|
@ -30,7 +30,7 @@ const VerticalNavBar = React.memo(({ className, selected, tabs }) => {
|
|||
}
|
||||
</nav>
|
||||
);
|
||||
});
|
||||
}));
|
||||
|
||||
VerticalNavBar.displayName = 'VerticalNavBar';
|
||||
|
||||
|
|
|
|||
56
src/services/SpatialNavigation/useSpatialNavigation.tsx
Normal file
56
src/services/SpatialNavigation/useSpatialNavigation.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useGamepad } from '../GamepadContext';
|
||||
|
||||
const useVerticalSpatialNavigation = (sectionRef: React.RefObject<HTMLDivElement>, gamepadHandlerId: string) => {
|
||||
const gamepad = useGamepad();
|
||||
|
||||
useEffect(() => {
|
||||
const focusableSelector = 'a';
|
||||
const focusableElements = () =>
|
||||
Array.from(sectionRef.current?.querySelectorAll(focusableSelector) || []);
|
||||
|
||||
const moveFocus = (direction: 'prev' | 'next') => {
|
||||
const elements = focusableElements();
|
||||
if (!elements.length) return;
|
||||
|
||||
const currentIndex = elements.findIndex((item) => item.classList.contains('selected'));
|
||||
|
||||
let nextIndex = currentIndex;
|
||||
|
||||
if (direction === 'next')
|
||||
nextIndex = (elements.length + currentIndex + 1) % elements.length;
|
||||
if (direction === 'prev')
|
||||
nextIndex = (elements.length + currentIndex - 1) % elements.length;
|
||||
|
||||
elements[nextIndex]?.click();
|
||||
};
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if (!event.nativeEvent?.spatialNavigationPrevented) {
|
||||
switch (event.key) {
|
||||
case 'Tab':
|
||||
moveFocus('next');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
gamepad?.on('buttonLT', gamepadHandlerId, () => moveFocus('prev'));
|
||||
gamepad?.on('buttonRT', gamepadHandlerId, () => moveFocus('next'));
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
gamepad?.off('buttonLT', gamepadHandlerId);
|
||||
gamepad?.off('buttonRT', gamepadHandlerId);
|
||||
};
|
||||
}, [gamepad, sectionRef]);
|
||||
|
||||
return sectionRef;
|
||||
};
|
||||
|
||||
export {
|
||||
useVerticalSpatialNavigation,
|
||||
};
|
||||
Loading…
Reference in a new issue