symbol-palette-items.js 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import { useCallback, useEffect, useState } from 'react'
  2. import PropTypes from 'prop-types'
  3. import SymbolPaletteItem from './symbol-palette-item'
  4. export default function SymbolPaletteItems({
  5. items,
  6. handleSelect,
  7. focusInput,
  8. }) {
  9. const [focusedIndex, setFocusedIndex] = useState(0)
  10. // reset the focused item when the list of items changes
  11. useEffect(() => {
  12. setFocusedIndex(0)
  13. }, [items])
  14. // navigate through items with left and right arrows
  15. const handleKeyDown = useCallback(
  16. event => {
  17. if (event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) {
  18. return
  19. }
  20. switch (event.key) {
  21. // focus previous item
  22. case 'ArrowLeft':
  23. case 'ArrowUp':
  24. setFocusedIndex(index => (index > 0 ? index - 1 : items.length - 1))
  25. break
  26. // focus next item
  27. case 'ArrowRight':
  28. case 'ArrowDown':
  29. setFocusedIndex(index => (index < items.length - 1 ? index + 1 : 0))
  30. break
  31. // focus first item
  32. case 'Home':
  33. setFocusedIndex(0)
  34. break
  35. // focus last item
  36. case 'End':
  37. setFocusedIndex(items.length - 1)
  38. break
  39. // allow the default action
  40. case 'Enter':
  41. case ' ':
  42. break
  43. // any other key returns focus to the input
  44. default:
  45. focusInput()
  46. break
  47. }
  48. },
  49. [focusInput, items.length]
  50. )
  51. return (
  52. <div className="symbol-palette-items" role="listbox" aria-label="Symbols">
  53. {items.map((symbol, index) => (
  54. <SymbolPaletteItem
  55. key={symbol.codepoint}
  56. symbol={symbol}
  57. handleSelect={symbol => {
  58. handleSelect(symbol)
  59. setFocusedIndex(index)
  60. }}
  61. handleKeyDown={handleKeyDown}
  62. focused={index === focusedIndex}
  63. />
  64. ))}
  65. </div>
  66. )
  67. }
  68. SymbolPaletteItems.propTypes = {
  69. items: PropTypes.arrayOf(
  70. PropTypes.shape({
  71. codepoint: PropTypes.string.isRequired,
  72. })
  73. ).isRequired,
  74. handleSelect: PropTypes.func.isRequired,
  75. focusInput: PropTypes.func.isRequired,
  76. }