React 仅针对有省略号的文本显示 Material-UI 工具提示

问题描述 投票:0回答:11

寻找一种方法让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。

reactjs react-redux tooltip material-ui ellipsis
11个回答
22
投票

离开@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;

15
投票

基于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>
  );
};

7
投票

我今天遇到了同样的问题,@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 来做到这一点比较麻烦。


5
投票
https://codesandbox.io/s/material-demo-p2omr

我在这里使用ref来获取TableCell DOM节点,然后比较scrollWidth和clientWidth来确定是否必须显示Tooltip。(这是基于答案

here

我已将“rowref”(具有引用的属性)和“open”(禁用/启用工具提示)添加为行的新属性。我不知道您的数据来自哪里,但我假设您可以将这些属性添加到行中。

还有一点需要注意,我只是设置“disableHoverListener”属性来禁用 tooltip 。还有其他道具 - "disableFocusListener" 和 "disableTouchListener" ,如果你想使用它们。更多信息

这里

希望这对您有用。如果您对代码有任何疑问,请告诉我。


4
投票

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'} />



4
投票

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> ); };



3
投票
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>
  );
};



2
投票
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



2
投票

您也可以在此处检查实现

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> ); };



0
投票

需要

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()
        )}
    </>
);

};


0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.