Repository: twentyhq/twenty
File: packages/twenty-ui/src/display/tooltip/OverflowingTextWithTooltip.tsx (TypeScript)

Code before:
  text-overflow: ellipsis;
  overflow: hidden;
  height: ${({ size }) => (size === 'large' ? spacing4 : 'auto')};

  white-space: nowrap;
`;

const StyledPre = styled.pre`
  font-family: inherit;
  white-space: pre-wrap;
`;

type OverflowingTextWithTooltipProps = {
  size?: 'large' | 'small';
  isTooltipMultiline?: boolean;
  displayedMaxRows?: number;
  hideTooltip?: boolean;
} & (
  | {
      text: string | null | undefined;
      tooltipContent?: never;
    }
  | {
      text: Exclude<ReactNode, string | null | undefined>;
      tooltipContent: string;
    }
);

export const OverflowingTextWithTooltip = ({
  size = 'small',
  text,
  isTooltipMultiline,
  displayedMaxRows,
  hideTooltip,
  tooltipContent,
}: OverflowingTextWithTooltipProps) => {
  const textElementId = `title-id-${+new Date()}`;

  const textRef = useRef<HTMLDivElement>(null);

  const [isTitleOverflowing, setIsTitleOverflowing] = useState(false);
  const [shouldRenderTooltip, setShouldRenderTooltip] = useState(false);

  const handleMouseEnter = () => {
    const isOverflowing = textRef.current
      ? textRef.current?.scrollHeight > textRef.current?.clientHeight ||
        textRef.current.scrollWidth > textRef.current.clientWidth
      : false;

    setIsTitleOverflowing(isOverflowing);
    setShouldRenderTooltip(true);

Review comment (bug):
The discriminated union type forces callers to provide `tooltipContent` when passing a ReactNode as `text`. However, when `text` is a ReactNode but `tooltipContent` is undefined or empty string, the logic falls through to checking if `text` is a non-empty string (line 115), which will fail for ReactNode values. This creates a scenario where a ReactNode with an empty `tooltipContent=""` would result in `tooltipText` being `null`, which may not be the intended behavior. Consider validating that `tooltipContent` is non-empty when provided, or adjusting the type to not allow empty strings.

Code after:
  text-overflow: ellipsis;
  overflow: hidden;
  height: ${({ size }) => (size === 'large' ? spacing4 : 'auto')};

  white-space: nowrap;
`;

const StyledPre = styled.pre`
  font-family: inherit;
  white-space: pre-wrap;
`;

type OverflowingTextWithTooltipProps = {
  size?: 'large' | 'small';
  isTooltipMultiline?: boolean;
  displayedMaxRows?: number;
} & (
  | {
      text: string | null | undefined;
      tooltipContent?: never;
    }
  | {
      text: Exclude<ReactNode, string | null | undefined>;
      tooltipContent: string;
    }
);

export const OverflowingTextWithTooltip = ({
  size = 'small',
  text,
  isTooltipMultiline,
  displayedMaxRows,
  tooltipContent,
}: OverflowingTextWithTooltipProps) => {
  const textElementId = `title-id-${+new Date()}`;

  const textRef = useRef<HTMLDivElement>(null);

  const [isTitleOverflowing, setIsTitleOverflowing] = useState(false);
  const [shouldRenderTooltip, setShouldRenderTooltip] = useState(false);

  const handleMouseEnter = () => {
    const isOverflowing = textRef.current
      ? textRef.current?.scrollHeight > textRef.current?.clientHeight ||
        textRef.current.scrollWidth > textRef.current.clientWidth
      : false;

    setIsTitleOverflowing(isOverflowing);
    setShouldRenderTooltip(true);
  };