useKeyboardNavigation.tsx 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import {useCallback, useEffect, useState} from 'react';
  2. export function useRovingTabIndex(items: any[]) {
  3. const [tabIndex, setTabIndex] = useState<number | null>(null);
  4. const onKeyDown = useCallback(
  5. (evt: React.KeyboardEvent) => {
  6. if (items.length === 0) {
  7. return;
  8. }
  9. if (evt.key === 'Escape') {
  10. setTabIndex(null);
  11. }
  12. if (evt.key === 'ArrowDown' || evt.key === 'Tab') {
  13. evt.preventDefault();
  14. if (tabIndex === items.length - 1 || tabIndex === null) {
  15. setTabIndex(0);
  16. } else {
  17. setTabIndex((tabIndex ?? 0) + 1);
  18. }
  19. }
  20. if (evt.key === 'ArrowUp' || (evt.key === 'Tab' && evt.shiftKey)) {
  21. evt.preventDefault();
  22. if (tabIndex === 0 || tabIndex === null) {
  23. setTabIndex(items.length - 1);
  24. } else {
  25. setTabIndex((tabIndex ?? 0) - 1);
  26. }
  27. }
  28. },
  29. [tabIndex, items]
  30. );
  31. return {
  32. tabIndex,
  33. setTabIndex,
  34. onKeyDown,
  35. };
  36. }
  37. export function useKeyboardNavigation() {
  38. const [menuRef, setMenuRef] = useState<HTMLDivElement | null>(null);
  39. const items: {id: number; node: HTMLElement | null}[] = [];
  40. const {tabIndex, setTabIndex, onKeyDown} = useRovingTabIndex(items);
  41. useEffect(() => {
  42. if (menuRef) {
  43. if (tabIndex === null) {
  44. menuRef.focus();
  45. }
  46. }
  47. }, [menuRef, tabIndex]);
  48. function getMenuProps() {
  49. return {
  50. tabIndex: -1,
  51. ref: setMenuRef,
  52. onKeyDown,
  53. };
  54. }
  55. function getItemProps() {
  56. const idx = items.length;
  57. items.push({id: idx, node: null});
  58. return {
  59. tabIndex: tabIndex === idx ? 0 : -1,
  60. ref: (node: HTMLElement | null) => {
  61. if (items[idx]) {
  62. if (tabIndex === idx) {
  63. node?.focus();
  64. }
  65. items[idx].node = node;
  66. }
  67. },
  68. onMouseEnter: () => {
  69. setTabIndex(idx);
  70. },
  71. onKeyDown,
  72. };
  73. }
  74. return {
  75. menuRef,
  76. getItemProps,
  77. getMenuProps,
  78. tabIndex,
  79. setTabIndex,
  80. };
  81. }