- Updated the ElipsisText component to conditionally wrap truncated text in a Tooltip, enhancing user experience by providing additional context on hover. - Simplified the rendering logic by consolidating the content structure, improving code readability and maintainability.
113 lines
3.1 KiB
JavaScript
113 lines
3.1 KiB
JavaScript
import { useLayoutEffect, useRef, useState } from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import { Typography } from 'antd'
|
|
import Tooltip from './Tooltip'
|
|
|
|
const { Text } = Typography
|
|
|
|
const childrenToString = (children) => {
|
|
if (children == null || typeof children === 'boolean') return ''
|
|
if (typeof children === 'string' || typeof children === 'number') {
|
|
return String(children)
|
|
}
|
|
if (Array.isArray(children)) {
|
|
return children.map(childrenToString).join('')
|
|
}
|
|
if (typeof children === 'object' && children.props) {
|
|
return childrenToString(children.props.children)
|
|
}
|
|
return ''
|
|
}
|
|
|
|
const splitMiddle = (text) => {
|
|
const mid = Math.ceil(text.length / 2)
|
|
return {
|
|
start: text.slice(0, mid),
|
|
end: text.slice(mid)
|
|
}
|
|
}
|
|
|
|
const ElipsisText = ({ children, style, className, title, code, ...rest }) => {
|
|
const text = childrenToString(children)
|
|
const { start, end } = splitMiddle(text)
|
|
|
|
const containerRef = useRef(null)
|
|
const measureRef = useRef(null)
|
|
const truncatedRef = useRef(false)
|
|
const [truncated, setTruncated] = useState(false)
|
|
|
|
useLayoutEffect(() => {
|
|
const container = containerRef.current
|
|
const measure = measureRef.current
|
|
if (!container || !measure) return
|
|
|
|
const update = () => {
|
|
const available = container.getBoundingClientRect().width
|
|
if (available < 1) return
|
|
|
|
const textWidth = measure.getBoundingClientRect().width
|
|
const next = Boolean(end) && textWidth > available
|
|
if (truncatedRef.current === next) return
|
|
truncatedRef.current = next
|
|
setTruncated(next)
|
|
}
|
|
|
|
update()
|
|
const frame = requestAnimationFrame(update)
|
|
|
|
const resizeObserver = new ResizeObserver(update)
|
|
resizeObserver.observe(container)
|
|
|
|
const intersectionObserver = new IntersectionObserver(update)
|
|
intersectionObserver.observe(container)
|
|
|
|
return () => {
|
|
cancelAnimationFrame(frame)
|
|
resizeObserver.disconnect()
|
|
intersectionObserver.disconnect()
|
|
}
|
|
}, [text, end])
|
|
|
|
const showTruncated = truncated && Boolean(end)
|
|
const RootTag = code ? 'code' : 'span'
|
|
|
|
const content = (
|
|
<span
|
|
className={showTruncated ? 'elipsis-text is-truncated' : 'elipsis-text'}
|
|
ref={containerRef}
|
|
>
|
|
<RootTag className='elipsis-text-measure' ref={measureRef}>
|
|
{text}
|
|
</RootTag>
|
|
<RootTag className='elipsis-text-full'>{text}</RootTag>
|
|
<RootTag className='elipsis-text-truncated'>
|
|
<span className='elipsis-text-start'>{start}</span>
|
|
<span className='elipsis-text-dots'>...</span>
|
|
<span className='elipsis-text-end'>
|
|
<span className='elipsis-text-end-inner'>{end}</span>
|
|
</span>
|
|
</RootTag>
|
|
</span>
|
|
)
|
|
|
|
return (
|
|
<Text
|
|
{...rest}
|
|
className={['elipsis-text-wrapper', className].filter(Boolean).join(' ')}
|
|
style={style}
|
|
>
|
|
{truncated ? <Tooltip title={title ?? text}>{content}</Tooltip> : content}
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
ElipsisText.propTypes = {
|
|
children: PropTypes.node,
|
|
style: PropTypes.object,
|
|
className: PropTypes.string,
|
|
title: PropTypes.string,
|
|
code: PropTypes.bool
|
|
}
|
|
|
|
export default ElipsisText
|