downshift-input.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import { useState, useEffect } from 'react'
  2. import { useCombobox } from 'downshift'
  3. import classnames from 'classnames'
  4. type DownshiftInputProps = {
  5. items: string[]
  6. inputValue: string
  7. label: string
  8. setValue: React.Dispatch<React.SetStateAction<string>>
  9. } & React.InputHTMLAttributes<HTMLInputElement>
  10. const filterItemsByInputValue = (
  11. items: DownshiftInputProps['items'],
  12. inputValue: DownshiftInputProps['inputValue']
  13. ) => items.filter(item => item.toLowerCase().includes(inputValue.toLowerCase()))
  14. function DownshiftInput({
  15. items,
  16. inputValue,
  17. placeholder,
  18. label,
  19. setValue,
  20. disabled,
  21. }: DownshiftInputProps) {
  22. const [inputItems, setInputItems] = useState(items)
  23. useEffect(() => {
  24. setInputItems(items)
  25. }, [items])
  26. const {
  27. isOpen,
  28. getLabelProps,
  29. getMenuProps,
  30. getInputProps,
  31. getComboboxProps,
  32. getItemProps,
  33. openMenu,
  34. selectedItem,
  35. } = useCombobox({
  36. inputValue,
  37. items: inputItems,
  38. initialSelectedItem: inputValue,
  39. onSelectedItemChange: ({ selectedItem }) => {
  40. setValue(selectedItem ?? '')
  41. },
  42. onInputValueChange: ({ inputValue = '' }) => {
  43. setInputItems(filterItemsByInputValue(items, inputValue))
  44. },
  45. onStateChange: ({ type }) => {
  46. if (type === useCombobox.stateChangeTypes.FunctionOpenMenu) {
  47. setInputItems(filterItemsByInputValue(items, inputValue))
  48. }
  49. },
  50. })
  51. return (
  52. <div
  53. className={classnames(
  54. 'ui-select-container ui-select-bootstrap dropdown',
  55. {
  56. open: isOpen && inputItems.length,
  57. }
  58. )}
  59. >
  60. <div {...getComboboxProps()} className="form-group mb-2">
  61. {/* eslint-disable-next-line jsx-a11y/label-has-for */}
  62. <label {...getLabelProps()} className="sr-only">
  63. {label}
  64. </label>
  65. <input
  66. {...getInputProps({
  67. onChange: (event: React.ChangeEvent<HTMLInputElement>) => {
  68. setValue(event.target.value)
  69. },
  70. onFocus: () => {
  71. if (!isOpen) {
  72. openMenu()
  73. }
  74. },
  75. })}
  76. className="form-control"
  77. type="text"
  78. placeholder={placeholder}
  79. disabled={disabled}
  80. />
  81. </div>
  82. <ul
  83. {...getMenuProps()}
  84. className="ui-select-choices ui-select-choices-content ui-select-dropdown dropdown-menu"
  85. >
  86. {inputItems.map((item, index) => (
  87. <li
  88. className="ui-select-choices-group"
  89. key={`${item}${index}`}
  90. {...getItemProps({ item, index })}
  91. >
  92. <div
  93. className={classnames('ui-select-choices-row', {
  94. active: selectedItem === item,
  95. })}
  96. >
  97. <span className="ui-select-choices-row-inner">
  98. <span>{item}</span>
  99. </span>
  100. </div>
  101. </li>
  102. ))}
  103. </ul>
  104. </div>
  105. )
  106. }
  107. export default DownshiftInput