highlightFuseMatches.tsx 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import * as React from 'react';
  2. type Match = {
  3. indices: [number, number][];
  4. value: string;
  5. };
  6. type HighlightResult = {
  7. highlight: boolean;
  8. text: string;
  9. };
  10. type MatchResult = HighlightResult[];
  11. /**
  12. * Parses matches from fuse.js library
  13. *
  14. * Example `match` would be
  15. *
  16. * {
  17. * value: 'Authentication tokens allow you to perform actions',
  18. * indices: [[4, 6], [12, 13], [15, 16]],
  19. * }
  20. *
  21. * So:
  22. *
  23. * 00-03 -> not highlighted,
  24. * 04-06 -> highlighted,
  25. * 07-11 -> not highlighted,
  26. * 12-13 -> highlighted,
  27. * ...etc
  28. *
  29. * @param match The match object from fuse
  30. * @param match.value The entire string that has matches
  31. * @param match.indices Array of indices that represent matches
  32. */
  33. const getFuseMatches = ({value, indices}: Match): MatchResult => {
  34. if (indices.length === 0) {
  35. return [{highlight: false, text: value}];
  36. }
  37. const strLength = value.length;
  38. const result: MatchResult = [];
  39. let prev = [0, -1];
  40. indices.forEach(([start, end]) => {
  41. // Unhighlighted string before the match
  42. const stringBeforeMatch = value.substring(prev[1] + 1, start);
  43. // Only add to result if non-empty string
  44. if (!!stringBeforeMatch) {
  45. result.push({
  46. highlight: false,
  47. text: stringBeforeMatch,
  48. });
  49. }
  50. // This is the matched string, which should be highlighted
  51. const matchedString = value.substring(start, end + 1);
  52. result.push({
  53. highlight: true,
  54. text: matchedString,
  55. });
  56. prev = [start, end];
  57. });
  58. // The rest of the string starting from the last match index
  59. const restOfString = value.substring(prev[1] + 1, strLength);
  60. // Only add to result if non-empty string
  61. if (!!restOfString) {
  62. result.push({highlight: false, text: restOfString});
  63. }
  64. return result;
  65. };
  66. /**
  67. * Given a match object from fuse.js, returns an array of components with
  68. * "highlighted" (bold) substrings.
  69. */
  70. const highlightFuseMatches = (matchObj: Match, Marker: React.ElementType = 'mark') =>
  71. getFuseMatches(matchObj).map(({highlight, text}, index) => {
  72. if (!text) {
  73. return null;
  74. }
  75. if (highlight) {
  76. return <Marker key={index}>{text}</Marker>;
  77. }
  78. return <span key={index}>{text}</span>;
  79. });
  80. export {getFuseMatches};
  81. export default highlightFuseMatches;