symbol-palette-content.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import { Tabs } from '@reach/tabs'
  2. import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import PropTypes from 'prop-types'
  5. import { matchSorter } from 'match-sorter'
  6. import symbols from '../data/symbols.json'
  7. import { buildCategorisedSymbols, createCategories } from '../utils/categories'
  8. import SymbolPaletteSearch from './symbol-palette-search'
  9. import SymbolPaletteBody from './symbol-palette-body'
  10. import SymbolPaletteTabs from './symbol-palette-tabs'
  11. // import SymbolPaletteInfoLink from './symbol-palette-info-link'
  12. import BetaBadge from '../../../shared/components/beta-badge'
  13. import '@reach/tabs/styles.css'
  14. export default function SymbolPaletteContent({ handleSelect }) {
  15. const [input, setInput] = useState('')
  16. const { t } = useTranslation()
  17. // build the list of categories with translated labels
  18. const categories = useMemo(() => createCategories(t), [t])
  19. // group the symbols by category
  20. const categorisedSymbols = useMemo(
  21. () => buildCategorisedSymbols(categories),
  22. [categories]
  23. )
  24. // select symbols which match the input
  25. const filteredSymbols = useMemo(() => {
  26. if (input === '') {
  27. return null
  28. }
  29. const words = input.trim().split(/\s+/)
  30. return words.reduceRight(
  31. (symbols, word) =>
  32. matchSorter(symbols, word, {
  33. keys: ['command', 'description', 'character', 'aliases'],
  34. threshold: matchSorter.rankings.CONTAINS,
  35. }),
  36. symbols
  37. )
  38. }, [input])
  39. const inputRef = useRef(null)
  40. // allow the input to be focused
  41. const focusInput = useCallback(() => {
  42. if (inputRef.current) {
  43. inputRef.current.focus()
  44. }
  45. }, [])
  46. // focus the input when the symbol palette is opened
  47. useEffect(() => {
  48. if (inputRef.current) {
  49. inputRef.current.focus()
  50. }
  51. }, [])
  52. return (
  53. <Tabs className="symbol-palette-container">
  54. <div className="symbol-palette">
  55. <div className="symbol-palette-header">
  56. {input.length <= 0 ? (
  57. <SymbolPaletteTabs categories={categories} />
  58. ) : (
  59. <div className="symbol-palette-search-hint">
  60. {t('showing_symbol_search_results', { search: input })}
  61. </div>
  62. )}
  63. <div className="symbol-palette-header-group">
  64. <BetaBadge
  65. tooltip={{
  66. id: 'tooltip-symbol-palette-beta',
  67. text:
  68. 'The Symbol Palette is a beta feature. Click here to give feedback.',
  69. placement: 'top',
  70. }}
  71. url="https://forms.gle/BybHV5svGE8rJ6Ki9"
  72. />
  73. {/* NOTE: replace the beta badge with this info link when rolling out to all users */}
  74. {/* <SymbolPaletteInfoLink /> */}
  75. <SymbolPaletteSearch setInput={setInput} inputRef={inputRef} />
  76. </div>
  77. </div>
  78. <div className="symbol-palette-body">
  79. <SymbolPaletteBody
  80. categories={categories}
  81. categorisedSymbols={categorisedSymbols}
  82. filteredSymbols={filteredSymbols}
  83. handleSelect={handleSelect}
  84. focusInput={focusInput}
  85. />
  86. </div>
  87. </div>
  88. </Tabs>
  89. )
  90. }
  91. SymbolPaletteContent.propTypes = {
  92. handleSelect: PropTypes.func.isRequired,
  93. }