简体   繁体   English

React 显示 Material-UI 工具提示仅适用于带有省略号的文本

[英]React show Material-UI Tooltip only for text that has ellipsis

Looking for a way to have material-ui's tooltip expand the text in a table cell ONLY if the text is cut off with an ellipsis (overflowing).寻找一种方法让 material-ui 的工具提示在文本被省略号(溢出)截断时才展开表格单元格中的文本。

Currently in my table I have a cell like this:目前在我的表中,我有一个这样的单元格:

<TableCell className={classes.descriptionCell}>{row.description}</TableCell>

and my styling for descriptionCell is like this:我对 descriptionCell 的样式是这样的:

    descriptionCell: {
        whiteSpace: 'nowrap',
        maxWidth: '200px',
        overflow: 'hidden',
        textOverflow: 'ellipsis'
    }

This makes the text behave the way I would like it to in this table, but I want to be able to hover and view the rest of it in a tooltip, preferably Material-UI's built in tooltip component.这使得文本按照我希望在该表中的方式运行,但我希望能够 hover 并在工具提示中查看它的 rest,最好是 Material-UI 的内置工具提示组件。

I know there is a package that exists here https://www.npmjs.com/package/react-ellipsis-with-tooltip which should do this, BUT it uses bootstrap tooltip, not material UI.我知道这里有一个 package https://www.npmjs.com/package/react-ellipsis-with-tooltip应该这样做,但它使用引导工具提示,而不是材料 UI。

To go off of @benjamin.keen answer.离开@benjamin.keen 的回答。 Here is a standalone functional component which is just an extension of his answer using hooks to perform the comparison functions.这是一个独立的功能组件,它只是他使用钩子执行比较功能的答案的扩展。

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;

Please find the codesandbox below - https://codesandbox.io/s/material-demo-p2omr请在下面找到代码和框 - https://codesandbox.io/s/material-demo-p2omr

I am using ref here to get the TableCell DOM Node and then comparing the scrollWidth and clientWidth to determine if Tooltip has to be displayed.(This is based on answer here )我在这里使用 ref 来获取 TableCell DOM 节点,然后比较 scrollWidth 和 clientWidth 以确定是否必须显示 Tooltip。(这是基于此处的答案)

I have added "rowref" (property that has the ref) and "open" (disable/enable tooltip) as new properties to the rows.我已将“rowref”(具有 ref 的属性)和“open”(禁用/启用工具提示)添加为行的新属性。 I don't know where your data is coming from, but I am assuming you can add these properties to the row.我不知道您的数据来自哪里,但我假设您可以将这些属性添加到行中。

One more thing to note, I am only setting "disableHoverListener" prop to disable tooltip .还有一件事要注意,我只是设置“disableHoverListener”道具来禁用 tooltip 。 There are other props - "disableFocusListener" & "disableTouchListener" , If you want to use those.还有其他道具 - "disableFocusListener" 和 "disableTouchListener" ,如果你想使用它们。 More info here更多信息在这里

Hope this works out for you.希望这对你有用。 Let me know if you have any doubts in the code.如果您对代码有任何疑问,请告诉我。

I ran into this same problem today and @vijay-menon's answer was very helpful.我今天遇到了同样的问题,@vijay-menon 的回答非常有帮助。 Here's a simple standalone component for the same thing:这是一个简单的独立组件,用于同一件事:

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

Example usage:用法示例:

<OverflowTip>
      some long text here that may get truncated based on space
</OverflowTip>

The one nuisance is that if the space for the element dynamically changes in the page (eg page resize or dynamic DOM change) it won't acknowledge the new space and recompute whether it's overflowed.一个麻烦是如果元素的空间在页面中动态变化(例如页面调整大小或动态 DOM 变化),它不会确认新空间并重新计算它是否溢出。

Other tooltip libraries like Tippy have a method that's fired when trying to open the tooltip.其他工具提示库(如 Tippy)有一个方法,当尝试打开工具提示时会触发该方法。 That's a perfect place to do the overflow check because it'll always work, regardless if the DOM width had changed for the text element.这是进行溢出检查的理想场所,因为它始终有效,无论文本元素的 DOM 宽度是否已更改。 Unfortunately it's fussier to do that with the API provided by Material UI.不幸的是,使用 Material UI 提供的 API 来做到这一点比较麻烦。

based on benjamin.keen answer, this is the functional version of his code:基于 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>
  );
};

I don't think you need to get into any side effect hooks.我不认为你需要进入任何副作用钩子。 The top post suggests putting an event listener on the window which fires on every mouse move event.最上面的帖子建议在 window 上放置一个事件监听器,它会在每次鼠标移动事件时触发。 We can just define some callbacks and pass them to onMouseEnter and onMouseLeave我们可以只定义一些回调并将它们传递给onMouseEnteronMouseLeave

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

based on @Dheeraj answer - this is the very close to his component but in type script version and more makes sense props names:基于@Dheeraj 的回答——这与他的组件非常接近,但在类型脚本版本中,更有意义的道具名称:

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;

and we use it like this:我们像这样使用它:

<OverflowTooltip 
    tooltip={'tooltip message here'}
    text={'very long text here'}
/>

If someone needs a TypScript version:如果有人需要 TypScript 版本:

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

The method of defining whether the text is overflowed has a flaw in the accepted answer.定义文本是否溢出的方法在接受的答案中存在缺陷。 Since scrollWidth<\/code> and clientWidth<\/code> return rounded integer values, when the difference between them is small, then we will get equal values and tooltip won't work.由于scrollWidth<\/code>和clientWidth<\/code>返回四舍五入的整数值,当它们之间的差异很小时,我们将得到相等的值,并且 tooltip 将不起作用。 The problem is that ellipsis is also counted as clientWidth<\/code> , so when we have an overflow of just one or tho characters, we will see ellipsis, but scrollWidth<\/code> and clientWidth<\/code> would be equal.问题是省略号也算作clientWidth<\/code> ,所以当我们只有一个或 tho 个字符溢出时,我们会看到省略号,但scrollWidth<\/code>和clientWidth<\/code>会相等。 Below is the solution which worked for me to determine scrollWidth<\/code> and clientWidth<\/code> with fractional accuracy and fixed this issue:以下是对我scrollWidth<\/code>的解决方案,用于确定具有分数精度的scrollWidth<\/code>和clientWidth<\/code>并解决了此问题:

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

If you want to show the tooltip only if the content overflows this will work.如果您只想在内容溢出时显示工具提示,这将起作用。

The useEffect()<\/code> is needed because the ref.current<\/code> is null initially, but when the component mounts it gets set and you can grab the html element based on it.需要useEffect()<\/code>是因为ref.current<\/code>最初为 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()
        )}
    </>
);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM