1- import { FC , ReactNode , useCallback , useEffect , useRef , useState } from 'react'
2- import Box from '@mui/material/Box'
3- import InfoIcon from '@mui/icons-material/Info'
4- import { trimLongString } from '../../utils/trimLongString'
5- import { MaybeWithTooltip } from './MaybeWithTooltip'
1+ import { FC , ReactNode } from 'react'
2+ import { AdaptiveDynamicTrimmer } from './AdaptiveDynamicTrimmer'
3+ import { trimLongString } from 'app/utils/trimLongString'
64
75type AdaptiveTrimmerProps = {
86 text : string | undefined
@@ -15,126 +13,18 @@ type AdaptiveTrimmerProps = {
1513 *
1614 * This component will do automatic detection of available space,
1715 * and determine the best way to display content accordingly.
16+ *
17+ * The implementation is based on AdaptiveDynamicTrimmer,
18+ * supplying it with a generator function which simply trims the given text to the wanted length.
1819 */
19- export const AdaptiveTrimmer : FC < AdaptiveTrimmerProps > = ( { text = '' , strategy = 'end' , extraTooltip } ) => {
20- // Initial setup
21- const fullLength = text . length
22- const textRef = useRef < HTMLDivElement | null > ( null )
23-
24- // Data about the currently rendered version
25- const [ currentContent , setCurrentContent ] = useState ( '' )
26- const [ currentLength , setCurrentLength ] = useState ( 0 )
27-
28- // Known good - this fits
29- const [ largestKnownGood , setLargestKnownGood ] = useState ( 0 )
30-
31- // Known bad - this doesn't fit
32- const [ smallestKnownBad , setSmallestKnownBad ] = useState ( fullLength + 1 )
33-
34- // Are we exploring our possibilities now?
35- const [ inDiscovery , setInDiscovery ] = useState ( false )
36-
37- const attemptContent = useCallback ( ( content : string , length : number ) => {
38- setCurrentContent ( content )
39- setCurrentLength ( length )
40- } , [ ] )
41-
42- const attemptShortenedContent = useCallback (
43- ( length : number ) => {
44- const content =
45- strategy === 'middle'
46- ? trimLongString ( text , Math . floor ( length / 2 ) - 1 , Math . floor ( length / 2 ) - 1 ) !
47- : trimLongString ( text , length , 0 ) !
48-
49- attemptContent ( content , length )
50- } ,
51- [ strategy , text , attemptContent ] ,
52- )
53-
54- const initDiscovery = useCallback ( ( ) => {
55- setLargestKnownGood ( 0 )
56- setSmallestKnownBad ( fullLength + 1 )
57- attemptContent ( text , fullLength )
58- setInDiscovery ( true )
59- } , [ text , fullLength , attemptContent ] )
60-
61- useEffect ( ( ) => {
62- initDiscovery ( )
63- const handleResize = ( ) => {
64- initDiscovery ( )
65- }
66-
67- window . addEventListener ( 'resize' , handleResize )
68- return ( ) => window . removeEventListener ( 'resize' , handleResize )
69- } , [ initDiscovery ] )
70-
71- useEffect ( ( ) => {
72- if ( inDiscovery ) {
73- if ( ! textRef . current ) {
74- return
75- }
76- const isOverflow = textRef . current . scrollWidth > textRef . current . clientWidth
77-
78- if ( isOverflow ) {
79- // This is too much
80-
81- // Update known bad length
82- const newSmallestKnownBad = Math . min ( currentLength , smallestKnownBad )
83- setSmallestKnownBad ( newSmallestKnownBad )
84-
85- // We should try something smaller
86- attemptShortenedContent ( Math . floor ( ( largestKnownGood + newSmallestKnownBad ) / 2 ) )
87- } else {
88- // This is OK
89-
90- // Update known good length
91- const newLargestKnownGood = Math . max ( currentLength , largestKnownGood )
92- setLargestKnownGood ( currentLength )
93-
94- if ( currentLength === fullLength ) {
95- // The whole thing fits, so we are good.
96- setInDiscovery ( false )
97- } else {
98- if ( currentLength + 1 === smallestKnownBad ) {
99- // This the best we can do, for now
100- setInDiscovery ( false )
101- } else {
102- // So far, so good, but we should try something longer
103- attemptShortenedContent ( Math . floor ( ( newLargestKnownGood + smallestKnownBad ) / 2 ) )
104- }
105- }
106- }
20+ export const AdaptiveTrimmer : FC < AdaptiveTrimmerProps > = ( { text = '' , strategy = 'end' , extraTooltip } ) => (
21+ < AdaptiveDynamicTrimmer
22+ getFullContent = { ( ) => ( { content : text , length : text . length } ) }
23+ getShortenedContent = { length =>
24+ strategy === 'middle'
25+ ? trimLongString ( text , Math . floor ( length / 2 ) - 1 , Math . floor ( length / 2 ) - 1 ) !
26+ : trimLongString ( text , length , 0 ) !
10727 }
108- } , [ inDiscovery , currentLength , largestKnownGood , smallestKnownBad , attemptShortenedContent , fullLength ] )
109-
110- if ( ! text ) return null
111-
112- const title =
113- currentLength !== fullLength ? (
114- < Box >
115- < Box > { text } </ Box >
116- { extraTooltip && (
117- < Box sx = { { display : 'inline-flex' , alignItems : 'center' , gap : 2 } } >
118- < InfoIcon />
119- { extraTooltip }
120- </ Box >
121- ) }
122- </ Box >
123- ) : (
124- extraTooltip
125- )
126-
127- return (
128- < Box
129- ref = { textRef }
130- sx = { {
131- overflow : 'hidden' ,
132- maxWidth : '100%' ,
133- } }
134- >
135- < MaybeWithTooltip title = { title } spanSx = { { whiteSpace : 'nowrap' } } >
136- { currentContent }
137- </ MaybeWithTooltip >
138- </ Box >
139- )
140- }
28+ extraTooltip = { extraTooltip }
29+ />
30+ )
0 commit comments