country-input.tsx 3.2 KB

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