寻找一种方法让material-ui的工具提示在表格单元格中展开文本仅如果文本被省略号截断(溢出)。
目前在我的表格中有一个像这样的单元格:
<TableCell className={classes.descriptionCell}>{row.description}</TableCell>
我的descriptionCell样式是这样的:
descriptionCell: {
whiteSpace: 'nowrap',
maxWidth: '200px',
overflow: 'hidden',
textOverflow: 'ellipsis'
}
这使得文本在该表中的行为方式符合我希望的方式,但我希望能够悬停并在工具提示中查看其余部分,最好是 Material-UI 的内置工具提示组件。
我知道这里有一个包 https://www.npmjs.com/package/react-ellipsis-with-tooltip 应该可以做到这一点,但它使用引导工具提示,而不是材料 UI。
离开@benjamin.keen 的回答。这是一个独立的功能组件,它只是他的答案的扩展,使用钩子来执行比较功能。
import React, { useRef, useEffect, useState } from 'react';
import Tooltip from '@material-ui/core/Tooltip';
const OverflowTip = props => {
// Create Ref
const textElementRef = useRef();
const compareSize = () => {
const compare =
textElementRef.current.scrollWidth > textElementRef.current.clientWidth;
console.log('compare: ', compare);
setHover(compare);
};
// compare once and add resize listener on "componentDidMount"
useEffect(() => {
compareSize();
window.addEventListener('resize', compareSize);
}, []);
// remove resize listener again on "componentWillUnmount"
useEffect(() => () => {
window.removeEventListener('resize', compareSize);
}, []);
// Define state and function to update the value
const [hoverStatus, setHover] = useState(false);
return (
<Tooltip
title={props.value}
interactive
disableHoverListener={!hoverStatus}
style={{fontSize: '2em'}}
>
<div
ref={textElementRef}
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{props.someLongText}
</div>
</Tooltip>
);
};
export default OverflowTip;
基于benjamin.keen的回答,这是他的代码的功能版本:
import React, { useRef, useState, useEffect } from 'react';
import Tooltip from '@material-ui/core/Tooltip';
const OverflowTip = ({ children }) => {
const [isOverflowed, setIsOverflow] = useState(false);
const textElementRef = useRef();
useEffect(() => {
setIsOverflow(textElementRef.current.scrollWidth > textElementRef.current.clientWidth);
}, []);
return (
<Tooltip title={children} disableHoverListener={!isOverflowed}>
<div
ref={textElementRef}
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{children}
</div>
</Tooltip>
);
};
我今天遇到了同样的问题,@vijay-menon 的回答非常有帮助。这是一个用于同一件事的简单独立组件:
import React, { Component } from 'react';
import Tooltip from '@material-ui/core/Tooltip';
class OverflowTip extends Component {
constructor(props) {
super(props);
this.state = {
overflowed: false
};
this.textElement = React.createRef();
}
componentDidMount () {
this.setState({
isOverflowed: this.textElement.current.scrollWidth > this.textElement.current.clientWidth
});
}
render () {
const { isOverflowed } = this.state;
return (
<Tooltip
title={this.props.children}
disableHoverListener={!isOverflowed}>
<div
ref={this.textElement}
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}>
{this.props.children}
</div>
</Tooltip>
);
}
}
使用示例:
<OverflowTip>
some long text here that may get truncated based on space
</OverflowTip>
一个麻烦的是,如果页面中元素的空间动态变化(例如页面调整大小或动态 DOM 更改),它不会确认新空间并重新计算它是否溢出。
Tippy 等其他工具提示库有一个在“尝试”打开工具提示时触发的方法。这是进行溢出检查的完美位置,因为无论文本元素的 DOM 宽度是否发生变化,它都始终有效。不幸的是,使用 Material UI 提供的 API 来做到这一点比较麻烦。
我在这里使用ref来获取TableCell DOM节点,然后比较scrollWidth和clientWidth来确定是否必须显示Tooltip。(这是基于答案
here) 我已将“rowref”(具有引用的属性)和“open”(禁用/启用工具提示)添加为行的新属性。我不知道您的数据来自哪里,但我假设您可以将这些属性添加到行中。
还有一点需要注意,我只是设置“disableHoverListener”属性来禁用 tooltip 。还有其他道具 - "disableFocusListener" 和 "disableTouchListener" ,如果你想使用它们。更多信息
这里
import React, { useRef, useEffect, useState } from 'react';
import Tooltip from '@material-ui/core/Tooltip';
interface Props {
tooltip: string;
text: string;
}
const OverflowTooltip = (props: Props) => {
const textElementRef = useRef<HTMLInputElement | null>(null);
const compareSize = () => {
const compare =
textElementRef.current.scrollWidth > textElementRef.current.clientWidth;
setHover(compare);
};
useEffect(() => {
compareSize();
window.addEventListener('resize', compareSize);
}, []);
useEffect(() => () => {
window.removeEventListener('resize', compareSize);
}, []);
const [hoverStatus, setHover] = useState(false);
return (
<Tooltip
title={props.tooltip}
interactive
disableHoverListener={!hoverStatus}
>
<div
ref={textElementRef}
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{props.text}
</div>
</Tooltip>
);
};
export default OverflowTooltip;
我们这样使用它:
<OverflowTooltip
tooltip={'tooltip message here'}
text={'very long text here'}
/>
import { Tooltip, Typography, TypographyProps } from "@mui/material";
import { FC, ReactChild, useEffect, useRef, useState } from "react";
export interface OverflowTypograpyProps extends TypographyProps {
children: ReactChild;
}
export const OverflowTypograpy: FC<OverflowTypograpyProps> = ({
children,
...props
}) => {
const ref = useRef<HTMLSpanElement>(null);
const [tooltipEnabled, setTooltipEnabled] = useState(false);
useEffect(() => {
const compareSize = () => {
if (ref.current) {
const compare = ref.current.scrollWidth > ref.current.clientWidth;
setTooltipEnabled(compare);
}
};
compareSize();
window.addEventListener("resize", compareSize);
return () => window.removeEventListener("resize", compareSize);
}, []);
return (
<Tooltip title={children} disableHoverListener={!tooltipEnabled}>
<Typography
ref={ref}
noWrap
overflow="hidden"
textOverflow="ellipsis"
{...props}
>
{children}
</Typography>
</Tooltip>
);
};
onMouseEnter
和
onMouseLeave
import React, { useState, MouseEvent } from "react";
import Tooltip, { TooltipProps } from "@mui/material/Tooltip";
export const OverflowTooltip = ({ children, ...props }: TooltipProps) => {
const [tooltipEnabled, setTooltipEnabled] = useState(false);
const handleShouldShow = ({ currentTarget }: MouseEvent<Element>) => {
if (currentTarget.scrollWidth > currentTarget.clientWidth) {
setTooltipEnabled(true);
}
};
const hideTooltip = () => setTooltipEnabled(false);
return (
<Tooltip
onMouseEnter={handleShouldShow}
onMouseLeave={hideTooltip}
disableHoverListener={!tooltipEnabled}
{...props}
>
<div
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{children}
</div>
{children}
</Tooltip>
);
};
scrollWidth
和
clientWidth
返回四舍五入的整数值,当它们之间的差异很小时,我们将得到相等的值,并且工具提示将不起作用。问题是省略号也被算作 clientWidth
,所以当我们只有一个或多个字符溢出时,我们会看到省略号,但 scrollWidth
和 clientWidth
是相等的。
以下是帮助我以分数精度确定 scrollWidth
和 clientWidth
并解决此问题的解决方案:import React, { useRef, useState, useEffect } from 'react';
import { Tooltip } from '@material-ui/core';
const OverflowTooltip = ({ children }) => {
const textElementRef = useRef();
const checkOverflow = () => {
// Using getBoundingClientRect, instead of scrollWidth and clientWidth, to get width with fractional accuracy
const clientWidth = textElementRef.current.getBoundingClientRect().width
textElementRef.current.style.overflow = 'visible';
const contentWidth = textElementRef.current.getBoundingClientRect().width
textElementRef.current.style.overflow = 'hidden';
setIsOverflow(contentWidth > clientWidth);
}
useEffect(() => {
checkOverflow();
window.addEventListener('resize', checkOverflow)
return () => {
window.removeEventListener('resize', checkOverflow)
}
}, []);
const [isOverflowed, setIsOverflow] = useState(false);
return (
<Tooltip title={children} disableHoverListener={!isOverflowed}>
<span ref={textElementRef}
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{children}
</span>
</Tooltip>
);
};
export default OverflowTooltip
您也可以在此处检查实现
https://codesandbox.io/s/control-tooltip-for-text-ellipsis-mkj1vp
(请调整输出窗口大小以查看省略号效果)
import { useState, MouseEvent } from "react";
import { Tooltip, Typography } from "@mui/material";
const LONGLABEL = "abcdefgh ijklmn opqrst uvwzyz";
export const OverflowTooltipExample = () => {
const [tooltipEnabled, setTooltipEnabled] = useState(false);
const handleShouldShow = ({ currentTarget }: MouseEvent<Element>) => {
if (currentTarget.scrollWidth > currentTarget.clientWidth) {
setTooltipEnabled(true);
}
};
return (
<Tooltip
title={LONGLABEL}
open={tooltipEnabled}
onClose={() => setTooltipEnabled(false)}
>
<Typography onMouseEnter={handleShouldShow} noWrap>
{LONGLABEL}
</Typography>
</Tooltip>
);
};
需要
useEffect()
,因为
ref.current
最初为 null,但是当组件安装时它会被设置,您可以基于它抓取 html 元素。interface MyInterface {
content: Content;
}
export const MyComponent: React.FC<MyInterface> = ({ content }) => {
const ref = useRef(null);
const [showTooltip, setShowTooltip] = useState(false);
useEffect(() => {
if (!ref.current) return;
const div = ref.current as HTMLDivElement;
const isOverflow = div.offsetWidth < div.scrollWidth;
setShowTooltip(isOverflow);
}, []);
const renderContent = () => (
<div ref={ref}>
content
</div>
);
return (
<>
{ref.current && showTooltip ? (
<Tooltip title={content.value}>
{renderContent()}
</Tooltip>
) : (
renderContent()
)}
</>
);
};
import React, { useRef, useState, useEffect } from 'react'
import {
Tooltip,
styled
} from '@mui/material'
const StyledDiv = styled('div')({
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
})
const OverflowTooltip = ({ children }) => {
const textElementRef = useRef()
const checkOverflow = () => {
setIsOverflow(textElementRef.current.scrollWidth > textElementRef.current.clientWidth)
}
useEffect(() => {
checkOverflow()
window.addEventListener('resize', checkOverflow)
return () => {
window.removeEventListener('resize', checkOverflow)
}
}, [])
const [isOverflowed, setIsOverflow] = useState(false)
return (
<StyledDiv ref={textElementRef} >
<Tooltip title={children} disableHoverListener={!isOverflowed}>
{children}
</Tooltip>
</StyledDiv>
)
}
export default OverflowTooltip