index.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import type { FC } from 'react'
  2. import React from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import { RiArrowLeftLine, RiArrowRightLine } from '@remixicon/react'
  5. import { useDebounceFn } from 'ahooks'
  6. import { Pagination } from './pagination'
  7. import Button from '@/app/components/base/button'
  8. import Input from '@/app/components/base/input'
  9. import cn from '@/utils/classnames'
  10. type Props = {
  11. className?: string
  12. current: number
  13. onChange: (cur: number) => void
  14. total: number
  15. limit?: number
  16. onLimitChange?: (limit: number) => void
  17. }
  18. const CustomizedPagination: FC<Props> = ({
  19. className,
  20. current,
  21. onChange,
  22. total,
  23. limit = 10,
  24. onLimitChange,
  25. }) => {
  26. const { t } = useTranslation()
  27. const totalPages = Math.ceil(total / limit)
  28. const inputRef = React.useRef<HTMLDivElement>(null)
  29. const [showInput, setShowInput] = React.useState(false)
  30. const [inputValue, setInputValue] = React.useState<string | number>(current + 1)
  31. const [showPerPageTip, setShowPerPageTip] = React.useState(false)
  32. const { run: handlePaging } = useDebounceFn((value: string) => {
  33. if (parseInt(value) > totalPages) {
  34. setInputValue(totalPages)
  35. onChange(totalPages - 1)
  36. setShowInput(false)
  37. return
  38. }
  39. if (parseInt(value) < 1) {
  40. setInputValue(1)
  41. onChange(0)
  42. setShowInput(false)
  43. return
  44. }
  45. onChange(parseInt(value) - 1)
  46. setInputValue(parseInt(value))
  47. setShowInput(false)
  48. }, { wait: 500 })
  49. const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  50. const value = e.target.value
  51. if (!value)
  52. return setInputValue('')
  53. if (isNaN(parseInt(value)))
  54. return setInputValue('')
  55. setInputValue(parseInt(value))
  56. handlePaging(value)
  57. }
  58. return (
  59. <Pagination
  60. className={cn('flex items-center w-full px-6 py-3 select-none', className)}
  61. currentPage={current}
  62. edgePageCount={2}
  63. middlePagesSiblingCount={1}
  64. setCurrentPage={onChange}
  65. totalPages={totalPages}
  66. truncableClassName='flex items-center justify-center w-8 px-1 py-2 system-sm-medium text-text-tertiary'
  67. truncableText='...'
  68. >
  69. <div className='flex items-center gap-0.5 p-0.5 rounded-[10px] bg-background-section-burn'>
  70. <Pagination.PrevButton
  71. as={<div></div>}
  72. disabled={current === 0}
  73. >
  74. <Button
  75. variant='secondary'
  76. className='w-7 h-7 px-1.5'
  77. disabled={current === 0}
  78. >
  79. <RiArrowLeftLine className='h-4 w-4' />
  80. </Button>
  81. </Pagination.PrevButton>
  82. {!showInput && (
  83. <div
  84. ref={inputRef}
  85. className='flex items-center gap-0.5 px-2 py-1.5 rounded-lg hover:bg-state-base-hover-alt hover:cursor-text'
  86. onClick={() => setShowInput(true)}
  87. >
  88. <div className='system-xs-medium text-text-secondary'>{current + 1}</div>
  89. <div className='system-xs-medium text-text-quaternary'>/</div>
  90. <div className='system-xs-medium text-text-secondary'>{totalPages}</div>
  91. </div>
  92. )}
  93. {showInput && (
  94. <Input
  95. styleCss={{
  96. height: '28px',
  97. width: `${inputRef.current?.clientWidth}px`,
  98. }}
  99. placeholder=''
  100. autoFocus
  101. value={inputValue}
  102. onChange={handleInputChange}
  103. onBlur={() => setShowInput(false)}
  104. />
  105. )}
  106. <Pagination.NextButton
  107. as={<div></div>}
  108. disabled={current === totalPages - 1}
  109. >
  110. <Button
  111. variant='secondary'
  112. className='w-7 h-7 px-1.5'
  113. disabled={current === totalPages - 1}
  114. >
  115. <RiArrowRightLine className='h-4 w-4' />
  116. </Button>
  117. </Pagination.NextButton>
  118. </div>
  119. <div className={cn('grow flex items-center justify-center gap-1 list-none')}>
  120. <Pagination.PageButton
  121. className='flex items-center justify-center min-w-8 px-1 py-2 rounded-lg system-sm-medium cursor-pointer hover:bg-components-button-ghost-bg-hover'
  122. activeClassName='bg-components-button-tertiary-bg text-components-button-tertiary-text hover:bg-components-button-ghost-bg-hover'
  123. inactiveClassName='text-text-tertiary'
  124. />
  125. </div>
  126. {onLimitChange && (
  127. <div className='shrink-0 flex items-center gap-2'>
  128. <div className='shrink-0 w-[51px] text-end text-text-tertiary system-2xs-regular-uppercase'>{showPerPageTip ? t('common.pagination.perPage') : ''}</div>
  129. <div
  130. className='flex items-center gap-[1px] p-0.5 rounded-[10px] bg-components-segmented-control-bg-normal'
  131. onMouseEnter={() => setShowPerPageTip(true)}
  132. onMouseLeave={() => setShowPerPageTip(false)}
  133. >
  134. <div
  135. className={cn(
  136. 'px-2.5 py-1.5 rounded-lg border-[0.5px] border-transparent system-sm-medium text-text-tertiary cursor-pointer hover:bg-state-base-hover hover:text-text-secondary',
  137. limit === 10 && 'shadow-xs border-components-segmented-control-item-active-border bg-components-segmented-control-item-active-bg text-text-secondary hover:bg-components-segmented-control-item-active-bg',
  138. )}
  139. onClick={() => onLimitChange?.(10)}
  140. >10</div>
  141. <div
  142. className={cn(
  143. 'px-2.5 py-1.5 rounded-lg border-[0.5px] border-transparent system-sm-medium text-text-tertiary cursor-pointer hover:bg-state-base-hover hover:text-text-secondary',
  144. limit === 25 && 'shadow-xs border-components-segmented-control-item-active-border bg-components-segmented-control-item-active-bg text-text-secondary hover:bg-components-segmented-control-item-active-bg',
  145. )}
  146. onClick={() => onLimitChange?.(25)}
  147. >25</div>
  148. <div
  149. className={cn(
  150. 'px-2.5 py-1.5 rounded-lg border-[0.5px] border-transparent system-sm-medium text-text-tertiary cursor-pointer hover:bg-state-base-hover hover:text-text-secondary',
  151. limit === 50 && 'shadow-xs border-components-segmented-control-item-active-border bg-components-segmented-control-item-active-bg text-text-secondary hover:bg-components-segmented-control-item-active-bg',
  152. )}
  153. onClick={() => onLimitChange?.(50)}
  154. >50</div>
  155. </div>
  156. </div>
  157. )}
  158. </Pagination>
  159. )
  160. }
  161. export default CustomizedPagination