review-panel-expandable-content.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import { memo, useCallback, useRef, useState } from 'react'
  2. import OLButton from '@/shared/components/ol/ol-button'
  3. import { useTranslation } from 'react-i18next'
  4. import classNames from 'classnames'
  5. import { PreventSelectingEntry } from './review-panel-prevent-selecting'
  6. export const ExpandableContent = memo<{
  7. className?: string
  8. content: string
  9. contentLimit?: number
  10. newLineCharsLimit?: number
  11. checkNewLines?: boolean
  12. inline?: boolean
  13. translate?: 'yes' | 'no'
  14. }>(function ExpandableContent({
  15. content,
  16. className,
  17. contentLimit = 50,
  18. newLineCharsLimit = 3,
  19. checkNewLines = true,
  20. inline = false,
  21. translate,
  22. }) {
  23. const { t } = useTranslation()
  24. const contentRef = useRef<HTMLDivElement>(null)
  25. const [isExpanded, setIsExpanded] = useState(false)
  26. const limit = checkNewLines
  27. ? Math.min(
  28. contentLimit,
  29. indexOfNthLine(content, newLineCharsLimit) ?? Infinity
  30. )
  31. : contentLimit
  32. const isOverflowing = content.length > limit
  33. const handleShowMore = useCallback(() => {
  34. setIsExpanded(true)
  35. contentRef.current?.dispatchEvent(
  36. new CustomEvent('review-panel:position', { bubbles: true })
  37. )
  38. }, [])
  39. const handleShowLess = useCallback(() => {
  40. setIsExpanded(false)
  41. contentRef.current?.dispatchEvent(
  42. new CustomEvent('review-panel:position', { bubbles: true })
  43. )
  44. }, [])
  45. return (
  46. <>
  47. <div
  48. ref={contentRef}
  49. className={classNames('review-panel-expandable-content', className)}
  50. translate={translate}
  51. >
  52. {isExpanded ? content : content.slice(0, limit)}
  53. {isOverflowing && !isExpanded && '...'}
  54. </div>
  55. <div
  56. className={classNames('review-panel-expandable-links', {
  57. 'review-panel-expandable-inline': inline,
  58. })}
  59. >
  60. <PreventSelectingEntry>
  61. {isExpanded ? (
  62. <OLButton
  63. variant="link"
  64. className="btn-inline-link"
  65. onClick={handleShowLess}
  66. >
  67. {t('show_less')}
  68. </OLButton>
  69. ) : (
  70. isOverflowing && (
  71. <OLButton
  72. variant="link"
  73. className="btn-inline-link"
  74. onClick={handleShowMore}
  75. >
  76. {t('show_more')}
  77. </OLButton>
  78. )
  79. )}
  80. </PreventSelectingEntry>
  81. </div>
  82. </>
  83. )
  84. })
  85. function indexOfNthLine(content: string, n: number) {
  86. if (n < 1) return null
  87. let line = 0
  88. for (let i = 0; i < content.length; i++) {
  89. if (content[i] === '\n') {
  90. line++
  91. if (line === n) {
  92. return i
  93. }
  94. }
  95. }
  96. return null
  97. }