All files / src/components/Textfield TruncatedTextArea.tsx

0% Statements 0/28
0% Branches 0/16
0% Functions 0/7
0% Lines 0/25

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92                                                                                                                                                                                       
import React, { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react';
 
import { Link, Typography, useTheme } from '@mui/material';
 
import { TruncateConfig } from './Textfield';
import './styles.scss';
 
interface TruncatedTextAreaProps {
  truncateConfig: TruncateConfig;
  children: ReactNode;
}
 
const TruncatedTextArea = ({ truncateConfig, children }: TruncatedTextAreaProps) => {
  const {
    maxHeight,
    showLessText,
    showMoreText,
    showTextStyle,
    alignment = 'left',
    collapseOnBlur = false,
  } = truncateConfig;
 
  const theme = useTheme();
  const [showAll, setShowAll] = useState(false);
  const [needsTruncating, setNeedsTruncating] = useState(false);
  const contentRef = useRef<HTMLDivElement>(null);
 
  useEffect(() => {
    const node = contentRef.current;
    if (!node) {
      return;
    }
 
    const measure = () => {
      // offsetHeight is an untransformed layout value, so it is in the same units as maxHeight
      const truncating = node.offsetHeight > maxHeight;
      setNeedsTruncating(truncating);
      if (!truncating) {
        // Otherwise content that grows long again later would start out expanded
        setShowAll(false);
      }
    };
    measure();
 
    const observer = new ResizeObserver(measure);
    observer.observe(node);
 
    return () => observer.disconnect();
  }, [maxHeight]);
 
  const toggleShowAll = () => setShowAll((prev) => !prev);
 
  const containerStyle: CSSProperties = {
    margin: 0,
    padding: 0,
    overflow: 'hidden',
    width: '100%',
  };
 
  if (needsTruncating && !showAll) {
    containerStyle.maxHeight = `${maxHeight}px`;
  }
 
  return (
    <>
      <div style={containerStyle}>
        {/* Measured separately from the container above, which clips to maxHeight when truncating */}
        <div ref={contentRef} style={{ display: 'flow-root' }}>
          {children}
        </div>
      </div>
 
      {needsTruncating && (
        <div style={{ width: '100%', textAlign: alignment }}>
          <Link
            component='button'
            onClick={toggleShowAll}
            onBlur={collapseOnBlur ? () => setShowAll(false) : undefined}
            sx={{ color: theme.palette.TwClrTxtBrand, textDecorationColor: `${theme.palette.TwClrTxtBrand}80` }}
          >
            <Typography sx={{ ...showTextStyle, marginTop: '-3px' }}>
              {showAll ? showLessText : showMoreText}
            </Typography>
          </Link>
        </div>
      )}
    </>
  );
};
 
export default TruncatedTextArea;