0

I have a card on my page, which has a title that is the name of a user. The names are sometimes longer than the card, the overflow previously was ellipsis, and a tooltip appeared on hover to show the full name. Now it should be changed in a way, that if it would overflow, then the texts letter-spacing attribute would decrease all the way down to -2, and after that will it start to truly overflow with an ellipsis. How to calculate the values from the available width of the enclosing div? Or how else should it be impemented.

Current state:

enter image description here

For names that can fit with letter-spacing : -2

enter image description here

For long names :

enter image description here

For short names:

enter image description here

Note, container size is the same across all cards!

Szilard Ban
  • 181
  • 8

1 Answers1

0

Let's assume your card component is like this

function Card({ name }) {
  return (
    <div className="card">
      <div className="card__pic"></div>
      <h3 className="card__name" ref={nameRef}>
        {name}
      </h3>
    </div>
  );
}

All we need is the element containing the name i.e "card__name".

  1. Access the 'name' element.

To access this name element, we have to use useRef hook. Here's how you would use it.

function Card({ name }) {
  const nameRef = useRef(null);

  useEffect(() => {
    // nameRef.current is accessible here
  }, []);

  return (
    <div className="card">
      <div className="card__pic"></div>
      <h3 className="card__name" ref={nameRef}>
        {name}
      </h3>
    </div>
  );
}

We have to pass ref={refVariable} to the element we'd like to access. The refVariable will be initialized with the element node after mount i.e we can use it inside the useEffect hook.

  1. Determine if the name is overflowing.

Now since we have the element, we can use it to check if the text inside is overflowing. I've created a function isTextOverflowingCard to which we pass the reference to the element we get after mount. It will return true if it text is overflowing else false.

function isTextOverflowingCard(textElement) {
  if (textElement.scrollWidth > textElement.clientWidth) {
    return true;
  }
  return false;
}

Now inside Card we can use this function and based on its return value, add a class conditionally which will have the tight letter-spacing value.

function Card({ name }) {
  const nameRef = useRef(null);
  const [textOverflow, setTextOverflow] = useState(false);

  useEffect(() => {
    // nameRef.current is accessible here
    setTextOverflow(isTextOverflowingCard(nameRef.current));
  }, []);

  return (
    <div className="card">
      <div className="card__pic"></div>
      <h3 className={`card__name ${textOverflow ? 'card__name--tight-kerning' : ''}`} ref={nameRef}>
        {name}
      </h3>
    </div>
  );
}

In CSS,

.card__name--tight-kerning {
  letter-spacing: -2px;
}

You can change useEffect to useLayoutEffect, which is better suited for such dom related calculations and applying to ui.

References:

  1. Detect if text has overflown
  2. useEffect vs useLayoutEffect
  3. https://reactjs.org/docs/hooks-reference.html#useref
Badal Saibo
  • 2,499
  • 11
  • 23