Search code examples
javascriptreactjsmaterial-uislider

How to get tooltip on the hover of the label in material ui slider


I am trying to create a slider quiz and what i want is on the hover of the label on the slider the tooltip appears

Right now when i hover on the thumb and its on the location of my mouse i can see the tooltip Here is the image

The mouse is denoted my red M enter image description here

I want to show the tooltip when the mouse is on other value enter image description here

And when the thumb and mouse is on the same point the toolpoint is visible enter image description here

Here is the code on how I am using this

<PrettoSlider1     
      ValueLabelComponent={ValueLabelComponent2}
/>
 const ValueLabelComponent2 = (props) => {
    const { children, value } = props;
    if (value === 0) {
      return (
        <Tooltip placement="top" title={"Strongly disagree"}>
          {children}
        </Tooltip>
      );
    } else if (value <= 1) {
      return (
        <Tooltip placement="top" title={"Disagree"}>
          {children}
        </Tooltip>
      );
    } else if (value <= 2) {
      return (
        <Tooltip placement="top" title={"Neutral"}>
          {children}
        </Tooltip>
      );
    } else if (value <= 3) {
      return (
        <Tooltip placement="top" title={"Agree"}>
          {children}
        </Tooltip>
      );
    } else if (value <= 4) {
      return (
        <Tooltip placement="top" title={"Strongly agree"}>
          {children}
        </Tooltip>
      );
    } else {
      return (
        <Tooltip placement="top" title={""}>
          {children}
        </Tooltip>
      );
    }
  };

Is it possible that if i hover like in 2nd image I can get the tooltip without the presence of thumb


Solution

  • I had to implement a similar use case and came up with the following solution. So in case you stumble over this question, you might find it useful.

    In my solution I track the mouse cursor X coordinate inside the slider and calculate the closest mark index by using the distance to the mouse cursor. When the cursor hovers over the slider, the tooltip for the closest mark/step is displayed and follows the cursor. It looks like this:

    Example

    As a result, the user sees the tooltip for the mark that will be selected when clicking at the current position (it seems that the Slider component uses a similar mechanism to determine which discrete step/mark to select).

    It might not be the most elegant or performant solution, but it serves my use case very well.

    The code is the following:

    import * as React from "react";
    import Box from "@mui/material/Box";
    import Slider from "@mui/material/Slider";
    import { Tooltip } from "@mui/material";
    
    export default function SliderWithTooltips() {
      const [markTooltip, setMarkTooltip] = React.useState("");
    
      const handleMouseMove = React.useCallback(
        (event: React.MouseEvent<HTMLDivElement>) => {
          // Determines the closest mark index to the mouse cursor by calculating
          // the distance of the relative X coordinate (relative to the slider element) of
          // the mouse cursor and the mark dom element.
          const mouseRelativeX =
            event.clientX - event.currentTarget.getBoundingClientRect().left;
          const sliderMarks = extractMarkElementRelativeXValues(
            event.currentTarget
          );
          let closestMarkIndex = 0;
          let closestDistance = Number.MAX_VALUE;
          let lastDistance = closestDistance;
    
          for (const mark of sliderMarks) {
            const distance = Math.abs(mouseRelativeX - mark.relativeX);
    
            // If the distance is increasing again, we have passed the closest mark and can abort the loop.
            if (distance > lastDistance) {
              break;
            }
    
            if (distance < closestDistance) {
              closestDistance = distance;
              closestMarkIndex = mark.index;
            }
    
            lastDistance = distance;
          }
    
          setMarkTooltip(markTooltips[closestMarkIndex]);
        },
        [setMarkTooltip]
      );
    
      return (
        <Box sx={{ width: 600, p: 4 }}>
          <Tooltip title={markTooltip} followCursor>
            <Slider
              valueLabelDisplay="auto"
              step={null}
              marks={marks}
              min={0}
              max={9}
              onMouseMove={handleMouseMove}
            />
          </Tooltip>
        </Box>
      );
    }
    
    function extractMarkElementRelativeXValues(sliderElement: HTMLSpanElement) {
      const sliderLeftX = sliderElement.getBoundingClientRect().left;
    
      return Array.from(sliderElement.getElementsByClassName("MuiSlider-mark")).map(
        (element) => ({
          index: Number(element.getAttribute("data-index")),
          relativeX: element.getBoundingClientRect().left - sliderLeftX,
        })
      );
    }
    
    const marks = [
      { value: 0 },
      { value: 1, label: "1" },
      { value: 2, label: "2" },
      { value: 3, label: "3" },
      { value: 4, label: "4" },
      { value: 5, label: "5" },
      { value: 6, label: "6" },
      { value: 7, label: "7" },
      { value: 8, label: "8" },
      { value: 9, label: "9" },
    ];
    
    const markTooltips = [
      "No value",
      "Some tooltip 1",
      "Some tooltip 2",
      "Some tooltip 3",
      "Some tooltip 4",
      "Some tooltip 5",
      "Some tooltip 6",
      "Some tooltip 7",
      "Some tooltip 8",
      "Some tooltip 9",
    ];
    

    I also created a CodeSandbox example here: https://codesandbox.io/p/sandbox/goofy-lehmann-5ljcdn?file=%2Fsrc%2FDemo.tsx

    HTH, Ben