From 486e3bebdb9a43f73b76b0f6a853a62d0e21046c Mon Sep 17 00:00:00 2001 From: Bernt Christian Egeland Date: Sat, 11 Dec 2021 15:58:56 +0100 Subject: [PATCH] keyboard navigation --- web/src/components/Calender.jsx | 46 +++++++++++++++++++++++++--- web/src/components/RelativeModal.jsx | 2 +- web/src/components/Select.jsx | 8 +++-- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/web/src/components/Calender.jsx b/web/src/components/Calender.jsx index 26e5997be..9ca02a6d4 100644 --- a/web/src/components/Calender.jsx +++ b/web/src/components/Calender.jsx @@ -1,12 +1,14 @@ import { h } from 'preact'; -import { useEffect, useState, useCallback, useMemo } from 'preact/hooks'; +import { useEffect, useState, useCallback, useMemo, useRef } from 'preact/hooks'; import ArrowRight from '../icons/ArrowRight'; import ArrowRightDouble from '../icons/ArrowRightDouble'; const oneDay = 60 * 60 * 24 * 1000; const todayTimestamp = Date.now() - (Date.now() % oneDay) + new Date().getTimezoneOffset() * 1000 * 60; -const Calender = ({ onChange, calenderRef }) => { +const Calender = ({ onChange, calenderRef, close }) => { + const keyRef = useRef([]); + const date = new Date(); const year = date.getFullYear(); const month = date.getMonth(); @@ -38,6 +40,7 @@ const Calender = ({ onChange, calenderRef }) => { timeRange: { before: null, after: null }, monthDetails: null, }); + const getNumberOfDays = useCallback((year, month) => { return 40 - new Date(year, month, 40).getDate(); }, []); @@ -90,6 +93,7 @@ const Calender = ({ onChange, calenderRef }) => { index++; } } + // setState((prev) => ({ ...prev, selectedDay: todayTimestamp, monthDetails: monthArray })); return monthArray; }, [getNumberOfDays, getDayDetails] @@ -99,6 +103,13 @@ const Calender = ({ onChange, calenderRef }) => { setState((prev) => ({ ...prev, selectedDay: todayTimestamp, monthDetails: getMonthDetails(year, month) })); }, [year, month, getMonthDetails]); + useEffect(() => { + // add refs for keyboard navigation + if (state.monthDetails) { + keyRef.current = keyRef.current.slice(0, state.monthDetails.length); + } + }, [state.monthDetails]); + const isCurrentDay = (day) => day.timestamp === todayTimestamp; const isSelectedRange = useCallback( @@ -203,13 +214,40 @@ const Calender = ({ onChange, calenderRef }) => { }); }; + const handleKeydown = (e, day, index) => { + if ((keyRef.current && e.key === 'Enter') || e.keyCode === 32) { + onDateClick(day); + } + if (e.key === 'ArrowLeft') { + index > 0 && keyRef.current[index - 1].focus(); + } + if (e.key === 'ArrowRight') { + index < 41 && keyRef.current[index + 1].focus(); + } + if (e.key === 'ArrowUp') { + e.preventDefault(); + index > 6 && keyRef.current[index - 7].focus(); + } + if (e.key === 'ArrowDown') { + e.preventDefault(); + index < 36 && keyRef.current[index + 7].focus(); + } + if (e.key === 'Escape') { + close(); + } + }; + const renderCalendar = () => { const days = state.monthDetails && - state.monthDetails.map((day, index) => { + state.monthDetails.map((day, idx) => { return (
onDateClick(day)} + onkeydown={(e) => handleKeydown(e, day, idx)} + ref={(ref) => (keyRef.current[idx] = ref)} + autoFocus={isCurrentDay(day)} + tabIndex={idx} className={`h-12 w-12 float-left flex flex-shrink justify-center items-center cursor-pointer ${ day.month !== 0 ? ' opacity-50 bg-gray-700 dark:bg-gray-700 pointer-events-none' : '' } @@ -217,7 +255,7 @@ const Calender = ({ onChange, calenderRef }) => { ${isSelectedRange(day) ? ' bg-blue-600 dark:hover:bg-blue-600' : ''} ${isLastDayInRange(day) ? ' rounded-r-xl ' : ''} ${isCurrentDay(day) && !isLastDayInRange(day) ? 'rounded-full bg-gray-100 dark:hover:bg-gray-100 ' : ''}`} - key={index} + key={idx} >
{day.date} diff --git a/web/src/components/RelativeModal.jsx b/web/src/components/RelativeModal.jsx index 7dd1bf76e..f566b5417 100644 --- a/web/src/components/RelativeModal.jsx +++ b/web/src/components/RelativeModal.jsx @@ -27,7 +27,7 @@ export default function RelativeModal({ const handleKeydown = useCallback( (event) => { - const focusable = ref.current.querySelectorAll('[tabindex]'); + const focusable = ref.current && ref.current.querySelectorAll('[tabindex]'); if (event.key === 'Tab' && focusable.length) { if (event.shiftKey && document.activeElement === focusable[0]) { focusable[focusable.length - 1].focus(); diff --git a/web/src/components/Select.jsx b/web/src/components/Select.jsx index 22bb46957..24f03ca7d 100644 --- a/web/src/components/Select.jsx +++ b/web/src/components/Select.jsx @@ -120,12 +120,14 @@ export default function Select({ } case 'ArrowDown': { + event.preventDefault(); const newIndex = focused + 1; newIndex < options.length && setFocused(newIndex); break; } case 'ArrowUp': { + event.preventDefault(); const newIndex = focused - 1; newIndex > -1 && setFocused(newIndex); break; @@ -153,12 +155,14 @@ export default function Select({ } case 'ArrowDown': { + event.preventDefault(); const newIndex = focused + 1; newIndex < options.length && setFocused(newIndex); break; } case 'ArrowUp': { + event.preventDefault(); const newIndex = focused - 1; newIndex > -1 && setFocused(newIndex); break; @@ -167,7 +171,7 @@ export default function Select({ // no default } }, - [onChange, options, showMenu, setShowMenu, setFocused, focused, selected] + [onChange, options, showMenu, setShowMenu, setFocused, focused, selected, paramName] ); const handleDismiss = useCallback(() => { @@ -206,7 +210,7 @@ export default function Select({ /> {showDatePicker && ( - + setShowDatePicker(false)} /> )} {showMenu ? (