您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

search-input.tsx 3.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import type { ChangeEventHandler } from 'react'
  2. import {
  3. useCallback,
  4. useRef,
  5. useState,
  6. } from 'react'
  7. import { useTranslation } from 'react-i18next'
  8. import { useEducation } from './hooks'
  9. import Input from '@/app/components/base/input'
  10. import {
  11. PortalToFollowElem,
  12. PortalToFollowElemContent,
  13. PortalToFollowElemTrigger,
  14. } from '@/app/components/base/portal-to-follow-elem'
  15. type SearchInputProps = {
  16. value?: string
  17. onChange: (value: string) => void
  18. }
  19. const SearchInput = ({
  20. value,
  21. onChange,
  22. }: SearchInputProps) => {
  23. const { t } = useTranslation()
  24. const [open, setOpen] = useState(false)
  25. const {
  26. schools,
  27. setSchools,
  28. querySchoolsWithDebounced,
  29. handleUpdateSchools,
  30. hasNext,
  31. } = useEducation()
  32. const pageRef = useRef(0)
  33. const valueRef = useRef(value)
  34. const handleSearch = useCallback((debounced?: boolean) => {
  35. const keywords = valueRef.current
  36. const page = pageRef.current
  37. if (debounced) {
  38. querySchoolsWithDebounced({
  39. keywords,
  40. page,
  41. })
  42. return
  43. }
  44. handleUpdateSchools({
  45. keywords,
  46. page,
  47. })
  48. }, [querySchoolsWithDebounced, handleUpdateSchools])
  49. const handleValueChange: ChangeEventHandler<HTMLInputElement> = useCallback((e) => {
  50. setOpen(true)
  51. setSchools([])
  52. pageRef.current = 0
  53. const inputValue = e.target.value
  54. valueRef.current = inputValue
  55. onChange(inputValue)
  56. handleSearch(true)
  57. }, [onChange, handleSearch, setSchools])
  58. const handleScroll = useCallback((e: Event) => {
  59. const target = e.target as HTMLDivElement
  60. const {
  61. scrollTop,
  62. scrollHeight,
  63. clientHeight,
  64. } = target
  65. if (scrollTop + clientHeight >= scrollHeight - 5 && scrollTop > 0 && hasNext) {
  66. pageRef.current += 1
  67. handleSearch()
  68. }
  69. }, [handleSearch, hasNext])
  70. return (
  71. <PortalToFollowElem
  72. open={open}
  73. onOpenChange={setOpen}
  74. placement='bottom'
  75. offset={4}
  76. triggerPopupSameWidth
  77. >
  78. <PortalToFollowElemTrigger className='block w-full'>
  79. <Input
  80. className='w-full'
  81. placeholder={t('education.form.schoolName.placeholder')}
  82. value={value}
  83. onChange={handleValueChange}
  84. />
  85. </PortalToFollowElemTrigger>
  86. <PortalToFollowElemContent className='z-[32]'>
  87. {
  88. !!schools.length && value && (
  89. <div
  90. className='max-h-[330px] overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1'
  91. onScroll={handleScroll as any}
  92. >
  93. {
  94. schools.map((school, index) => (
  95. <div
  96. key={index}
  97. className='system-md-regular flex h-8 cursor-pointer items-center truncate rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover'
  98. title={school}
  99. onClick={() => {
  100. onChange(school)
  101. setOpen(false)
  102. }}
  103. >
  104. {school}
  105. </div>
  106. ))
  107. }
  108. </div>
  109. )
  110. }
  111. </PortalToFollowElemContent>
  112. </PortalToFollowElem>
  113. )
  114. }
  115. export default SearchInput