3

I have successfully implemented the show more/show less. My problem is that I want to achieve it based on the number of lines or height of the screen. I don't want it to be based on number of characters since it would look bad on certain screens. like kinda cut on larger screen while too long on small screens.

Pls check my codesandbox here CLICK HERE

  <DescriptionText>
    {isShowMore ? text.slice(0, 300) : text}
  </DescriptionText>
  {text && text.length > 300 && (
    <ShowMoreText onClick={toggleReadMore}>
      {isShowMore ? "Show more..." : "Show less"}
    </ShowMoreText>
  )}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
Joseph
  • 7,042
  • 23
  • 83
  • 181

2 Answers2

4

Since you are using styled-components I've found a great utility package for it: polished.js

You would be looking for the ellipsis utility.

ellipsis

CSS to represent truncated text with an ellipsis. You can optionally pass a max-width and number of lines before truncating.

ellipsis(width: (string? | number?)?, lines: number): Styles

Example: Display up to 3 lines when show more, otherwise full text.

import { ellipsis } from 'polished';

...

const DescriptionText = styled.div`
  font-size: 14px;
  margin-top: 20px;
  margin-bottom: 20px;
  ${({ showMore }) => showMore && ellipsis(undefined, 3)}
`;

...

const Description = () => {
  const [isShowMore, setIsShowMore] = useState(true);
  const toggleReadMore = () => setIsShowMore(show => !show);

  return (
    <MainContainer>
      <TitleText>Description</TitleText>
      <DescriptionText showMore={isShowMore}>{text}</DescriptionText>
      <ShowMoreText onClick={toggleReadMore}>
        {isShowMore ? "Show more..." : "Show less"}
      </ShowMoreText>
    </MainContainer>
  );
};

enter image description here enter image description here

Edit show-more-based-on-height-in-react

You seem to have mentioned in another answer that you don't want to add any new dependencies, so here is the CSS that is applied via the ellipsis utility. Though, I'd still recommend adding polished to your project if you can as it has many useful styling utilities I've found fairly invaluable.

import styled, { css } from "styled-components";

const DescriptionText = styled.div`
  font-size: 14px;
  margin-top: 20px;
  margin-bottom: 20px;
  ${({ showMore }) =>
    showMore &&
    css`
      display: -webkit-box;
      max-width: 100%;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: normal;
      word-wrap: normal;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 3;
    `}
`;

To handle different screen sizes/responsiveness you can use media queries and specify a different number of lines you want to initially display.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Hi @Drew Reese. If there are few texts, how could you hide the `Show more...`? There will be no sense to show `Show more` if there is very little texts. Thank you. – Joseph Aug 08 '21 at 12:50
  • @Joseph I found a some SO posts regarding testing the rendered content width to compute if an overflow is occurring: https://stackoverflow.com/questions/64689074/how-to-check-if-text-is-truncated-by-css-using-javascript, https://stackoverflow.com/questions/7738117/html-text-overflow-ellipsis-detection. The jquery answer seemed promising but it usually not a good idea to mix jquery with React, but I think in this case it might be ok since jquery isn't being used to update anything React is rendering. (*I tried converting the logic over to React, got close*). – Drew Reese Aug 09 '21 at 07:37
  • @Joseph I'll tinker around a bit more when I have time. – Drew Reese Aug 09 '21 at 07:38
-1

How about window.innerHeight < minHeight? This means you need to define minHeight

const text = `Lorem ipsum .....`; //
let _shouldTruncate = null;
const minHeight = 750;
const Description = () => {
  const descTextEl = useRef(null);
  const [isShowMore, setIsShowMore] = useState(true);
  const toggleReadMore = () => {
    setIsShowMore(!isShowMore);
  };
  const [txtVal, setTxtVal] = useState(""); //save value to state
  //updates `txtVal` on change of `isShowMore`
  useEffect(() => {
    if (_shouldTruncate === null) {
      _shouldTruncate = window.innerHeight < minHeight;
    }
    setTxtVal(_shouldTruncate && isShowMore ? text.slice(0, 300) : text);
  }, [txtVal, isShowMore]);

  return (
    <MainContainer>
      <TitleText>Description</TitleText>
      <DescriptionText ref={descTextEl}>{txtVal}</DescriptionText>
      {_shouldTruncate && (
        <ShowMoreText onClick={toggleReadMore}>
          {isShowMore ? "Show more..." : "Show less"}
        </ShowMoreText>
      )}
    </MainContainer>
  );
};

UPDATE:

Since you wanted to get the number of lines, you might need to calculate it using this logic from this answer:

function countLines() {
   var el = document.getElementById('content');
   var divHeight = el.offsetHeight
   var lineHeight = parseInt(el.style.lineHeight);
   //or use computed lineHeight: 
   //var lineHeight = document.defaultView.getComputedStyle(el, null).getPropertyValue("lineHeight");
   var lines = divHeight / lineHeight;
   alert("Lines: " + lines);
}

Update:

You need to import ReactDom to calculate the style.

Code Sandbox using window.innerHeight

Code Sandbox based on number of lines

xGeo
  • 2,149
  • 2
  • 18
  • 39
  • This is not what I wanted. I want to revise the function that it shouldnt count the number of characters, I want it to be based on the number of lines. It might be several lines on mobile devices BUT only few lines on desktop/laptop, thats why. – Joseph Jul 20 '21 at 16:12
  • the logic is there. save the text in the state and modify it according to your rules. use `useEffect` so you can get the reference to the `HTMLDivElement`. – xGeo Jul 22 '21 at 06:17
  • @Joseph, posted updated codesandbox. Took some time. Lucky I currently don't have anything to do. ;) – xGeo Jul 22 '21 at 08:36
  • You probably might need to add a listener on window's `resize` but you take the wheel man. – xGeo Jul 22 '21 at 08:38