index.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import React from 'react';
  2. import { DBRecipe } from 'types/recipes';
  3. import { Input2 as Input} from 'components/Input';
  4. import List from 'components/List';
  5. import { getRecipes } from 'containers/Recipes/services';
  6. import { throttle } from 'throttle-debounce';
  7. import './styles.scss';
  8. enum Key {
  9. Down = 40,
  10. Up = 38,
  11. Enter = 13,
  12. Esc = 27
  13. }
  14. type Props = {
  15. onSelect: (selected: DBRecipe) => void
  16. }
  17. type State = {
  18. results: DBRecipe[],
  19. search: string,
  20. cursor: number,
  21. selected?: DBRecipe,
  22. openResult: boolean,
  23. }
  24. class RecipeSearch extends React.Component<Props, State> {
  25. constructor(props: Props) {
  26. super(props);
  27. this.state = {
  28. results: [],
  29. search: '',
  30. cursor: 0,
  31. openResult: false
  32. }
  33. }
  34. autocompleteSearch = throttle(500, (query: string) => {
  35. getRecipes(query).then(results => this.setState({results}))
  36. })
  37. incrementCursor = () => this.state.results.length && this.state.cursor < (this.state.results.length - 1) ? this.state.cursor + 1 : 0;
  38. decrementCursor = () => this.state.results.length && this.state.cursor > 0 ? this.state.cursor - 1 : (this.state.results.length - 1);
  39. onKeyDown = (event: React.KeyboardEvent) => {
  40. if (event.keyCode === Key.Down) {
  41. const cursor = this.incrementCursor();
  42. this.setState({
  43. selected: this.state.results[cursor],
  44. cursor: cursor,
  45. });
  46. }
  47. if (event.keyCode === Key.Up) {
  48. const cursor = this.decrementCursor();
  49. this.setState({
  50. selected: this.state.results[cursor],
  51. cursor: cursor,
  52. });
  53. }
  54. if (event.keyCode === Key.Enter) {
  55. this.selectRecipe(this.state.results[this.state.cursor])
  56. }
  57. if (event.keyCode === Key.Esc) {
  58. this.setState({
  59. openResult: false
  60. })
  61. }
  62. }
  63. selectRecipe = (recipe: DBRecipe) => {
  64. this.props.onSelect(recipe);
  65. this.setState({
  66. openResult: false,
  67. search: ''
  68. })
  69. }
  70. changeQuery = (event: React.ChangeEvent<HTMLInputElement>) => {
  71. this.setState({
  72. search: event.target.value,
  73. openResult: true,
  74. }, () => {
  75. this.autocompleteSearch(this.state.search)
  76. })
  77. }
  78. closeResults = (event: any) => {
  79. this.setState({
  80. openResult: false
  81. })
  82. }
  83. render() {
  84. const { results, search, cursor, openResult } = this.state;
  85. return (
  86. <div className='cbk-recipe-search'>
  87. <Input
  88. className='cbk-recipe-search__input'
  89. value={search}
  90. onChange={this.changeQuery}
  91. onKeyDown={this.onKeyDown}
  92. onBlur={this.closeResults}
  93. />
  94. <div className='cbk-recipe-search__results'>
  95. {
  96. results.length && openResult ? (
  97. <List
  98. dense
  99. items={results}
  100. focus={cursor}
  101. render={recipe => (
  102. <div
  103. className='cbk-recipe-search__results__item'
  104. onClick={() => this.selectRecipe(recipe)}
  105. >
  106. {recipe.name}
  107. </div>
  108. )}
  109. />
  110. ) : null
  111. }
  112. </div>
  113. </div>)
  114. }
  115. }
  116. export default RecipeSearch