1- import { FC , ReactNode , useCallback , useEffect , useRef , useState } from 'react'
1+ import { FC , ReactNode , useMemo } from 'react'
22import Box from '@mui/material/Box'
33import InfoIcon from '@mui/icons-material/Info'
44import { MaybeWithTooltip } from '../Tooltip/MaybeWithTooltip'
5+ import { getAdaptiveId , ShorteningResult , useAdaptiveSizing } from './hooks'
56
67type AdaptiveDynamicTrimmerProps = {
8+ /**
9+ * The ID (prefix) used for debugging
10+ */
11+ idPrefix : string
12+
13+ /**
14+ * A function to return the full content
15+ */
716 getFullContent : ( ) => {
817 content : ReactNode
918 length : number
1019 }
11- getShortenedContent : ( wantedLength : number ) => ReactNode
20+
21+ /**
22+ * A function to return shortened content
23+ */
24+ getShortenedContent : ( wantedLength : number ) => ShorteningResult
25+
26+ /**
27+ * The minimum length we ever want to shorten to.
28+ *
29+ * Default is 2
30+ */
31+ minLength ?: number
1232
1333 /**
1434 * Normally, the tooltip will be the full content. Do you want to add something?
@@ -19,6 +39,11 @@ type AdaptiveDynamicTrimmerProps = {
1939 * Normally, the tooltip will be the full content. Do you want to replace it with something else?
2040 */
2141 tooltipOverride ?: ReactNode
42+
43+ /**
44+ * Do we want to see debug output about the adaptive trimming process?
45+ */
46+ debugMode ?: boolean
2247}
2348
2449/**
@@ -31,132 +56,44 @@ type AdaptiveDynamicTrimmerProps = {
3156 * expects a function to provide a shortened version of the components.
3257 */
3358export const AdaptiveDynamicTrimmer : FC < AdaptiveDynamicTrimmerProps > = ( {
59+ idPrefix,
3460 getFullContent,
3561 getShortenedContent,
3662 extraTooltip,
3763 tooltipOverride,
64+ debugMode = false ,
65+ minLength = 2 ,
3866} ) => {
39- // Initial setup
40- const textRef = useRef < HTMLDivElement | null > ( null )
41- const { content : fullContent , length : fullLength } = getFullContent ( )
42-
43- // Data about the currently rendered version
44- const [ currentContent , setCurrentContent ] = useState < ReactNode > ( )
45- const [ currentLength , setCurrentLength ] = useState ( 0 )
46-
47- // Known good - this fits
48- const [ largestKnownGood , setLargestKnownGood ] = useState ( 0 )
49-
50- // Known bad - this doesn't fit
51- const [ smallestKnownBad , setSmallestKnownBad ] = useState ( fullLength + 1 )
52-
53- // Are we exploring our possibilities now?
54- const [ inDiscovery , setInDiscovery ] = useState ( false )
55-
56- const attemptContent = useCallback ( ( content : ReactNode , length : number ) => {
57- setCurrentContent ( content )
58- setCurrentLength ( length )
59- } , [ ] )
60-
61- const attemptShortenedContent = useCallback (
62- ( length : number ) => {
63- const content = getShortenedContent ( length )
64-
65- attemptContent ( content , length )
66- } ,
67- [ attemptContent , getShortenedContent ] ,
67+ const id = useMemo ( ( ) => getAdaptiveId ( idPrefix ) , [ idPrefix ] )
68+
69+ const { currentContent, fullContent, textRef, isTruncated, isFinal } = useAdaptiveSizing (
70+ id ,
71+ getFullContent ,
72+ getShortenedContent ,
73+ debugMode ,
74+ minLength ,
6875 )
6976
70- const initDiscovery = useCallback ( ( ) => {
71- setLargestKnownGood ( 0 )
72- setSmallestKnownBad ( fullLength + 1 )
73- attemptContent ( fullContent , fullLength )
74- setInDiscovery ( true )
75- } , [ fullContent , fullLength , attemptContent ] )
76-
77- useEffect ( ( ) => {
78- initDiscovery ( )
79- const handleResize = ( ) => {
80- initDiscovery ( )
81- }
82-
83- window . addEventListener ( 'resize' , handleResize )
84- return ( ) => window . removeEventListener ( 'resize' , handleResize )
85- } , [ initDiscovery ] )
86-
87- useEffect ( ( ) => {
88- if ( inDiscovery ) {
89- if ( ! textRef . current ) {
90- return
91- }
92- const isOverflow = textRef . current . scrollWidth > textRef . current . clientWidth
93-
94- if ( isOverflow ) {
95- // This is too much
96-
97- // Update known bad length
98- const newSmallestKnownBad = Math . min ( currentLength , smallestKnownBad )
99- setSmallestKnownBad ( newSmallestKnownBad )
100-
101- // We should try something smaller
102- attemptShortenedContent ( Math . floor ( ( largestKnownGood + newSmallestKnownBad ) / 2 ) )
103- } else {
104- // This is OK
105-
106- // Update known good length
107- const newLargestKnownGood = Math . max ( currentLength , largestKnownGood )
108- setLargestKnownGood ( currentLength )
109-
110- if ( currentLength === fullLength ) {
111- // The whole thing fits, so we are good.
112- setInDiscovery ( false )
113- } else {
114- if ( currentLength + 1 === smallestKnownBad ) {
115- // This the best we can do, for now
116- setInDiscovery ( false )
117- } else {
118- // So far, so good, but we should try something longer
119- attemptShortenedContent ( Math . floor ( ( newLargestKnownGood + smallestKnownBad ) / 2 ) )
120- }
121- }
122- }
123- }
124- } , [
125- attemptShortenedContent ,
126- currentLength ,
127- fullContent ,
128- fullLength ,
129- inDiscovery ,
130- initDiscovery ,
131- largestKnownGood ,
132- smallestKnownBad ,
133- ] )
134-
135- const title =
136- currentLength !== fullLength ? (
137- < Box >
138- < Box > { tooltipOverride ?? fullContent } </ Box >
139- { extraTooltip && (
140- < Box sx = { { display : 'inline-flex' , alignItems : 'center' , gap : 2 } } >
141- < InfoIcon />
142- { extraTooltip }
143- </ Box >
144- ) }
145- </ Box >
146- ) : (
147- extraTooltip
148- )
77+ const title = isTruncated ? (
78+ < Box >
79+ < Box > { tooltipOverride ?? fullContent } </ Box >
80+ { extraTooltip && (
81+ < Box sx = { { display : 'inline-flex' , alignItems : 'center' , gap : 2 } } >
82+ < InfoIcon />
83+ { extraTooltip }
84+ </ Box >
85+ ) }
86+ </ Box >
87+ ) : (
88+ extraTooltip
89+ )
14990
15091 return (
151- < Box
152- ref = { textRef }
153- sx = { {
154- overflow : 'hidden' ,
155- maxWidth : '100%' ,
156- textWrap : 'nowrap' ,
157- } }
158- >
159- < MaybeWithTooltip title = { title } spanSx = { { whiteSpace : 'nowrap' } } >
92+ < Box component = { 'span' } ref = { textRef } sx = { { maxWidth : '100%' , overflowX : 'hidden' } } >
93+ < MaybeWithTooltip
94+ title = { title }
95+ spanSx = { { textWrap : 'nowrap' , whiteSpace : 'nowrap' , opacity : isFinal ? 1 : 0 } }
96+ >
16097 { currentContent }
16198 </ MaybeWithTooltip >
16299 </ Box >
0 commit comments