"use client" import useMergedRef from "@react-hook/merged-ref" import React, { ElementRef, forwardRef, useCallback, useLayoutEffect, useRef, useState, } from "react" import { useCallbackRef, useIsOverflowed } from "../../hooks" import { LineClampVariantProps, lineClampVariants, } from "../../styles/utils/lineclamp" import { classNames } from "../../utils/classNames" import { Flex, FlexProps } from "../Flex" import { MARKDOWN_SELECTOR } from "../Markdown/constants" import { Text } from "../Text" import { UnstyledButton } from "../UnstyledButton" type BaseTextOverflowProps = { children: React.ReactNode className?: string open?: boolean onOpenChange?: (open: boolean) => unknown inlineSeeMore?: boolean } & LineClampVariantProps & FlexProps type ControlledTextOverflowProps = { open: boolean } & BaseTextOverflowProps type UncontrolledTextOverflowProps = { open?: never } & BaseTextOverflowProps export type TextOverflowProps = | ControlledTextOverflowProps | UncontrolledTextOverflowProps const isControlledTextOverflow = ( props: TextOverflowProps, ): props is ControlledTextOverflowProps => { return "open" in props } /** * Renders a See more/See less button when the text overflows. */ export const TextOverflow = (props: TextOverflowProps) => { if (isControlledTextOverflow(props)) { return } return } const UncontrolledTextOverflowProps = ({ onOpenChange, ...rest }: UncontrolledTextOverflowProps) => { const [open, setOpen] = useState(false) const handleOnOpenChange = useCallback(() => { setOpen(!open) onOpenChange?.(!open) }, [onOpenChange, open]) return ( ) } const ControlledTextOverflow = ({ children, className, open, onOpenChange, lines = 1, inlineSeeMore, ...rest }: ControlledTextOverflowProps) => { const [ref, setRef] = useCallbackRef() const isOverflowed = useIsOverflowed(ref) const showSeeMore = isOverflowed || open return ( {children} {showSeeMore && ( onOpenChange?.(!open)} > {open ? "See less" : "See more"} )} ) } const OverflowContainer = forwardRef< ElementRef<"div">, { children: React.ReactNode; open?: boolean } & LineClampVariantProps >(function OverflowContainer({ children, lines, open }, ref) { const containerRef = useRef(null) const mergedRef = useMergedRef(ref, containerRef) const [width, setWidth] = useState(undefined) return ( <>
{children}
{children} ) }) const MeasuringContainer = ({ enabled, children, lines, onMeasure, }: { enabled: boolean children: React.ReactNode lines?: number onMeasure: (width: number | undefined) => void }) => { const [containerRef, setContainerRef] = useCallbackRef() // Calculate width based on markdown content useLayoutEffect(() => { if (!enabled || !containerRef.current) { return } const markdownContent = containerRef.current.querySelector(MARKDOWN_SELECTOR) if (!markdownContent) { onMeasure(undefined) return } const childrenLines = Array.from(markdownContent.children).slice(0, lines) if (!childrenLines.length) { onMeasure(undefined) return } const TRUNCATE_BUFFER = 12 const measuredWidth = Math.max( ...childrenLines.map(el => el.getBoundingClientRect().width || 0), ) + TRUNCATE_BUFFER onMeasure(measuredWidth) }, [enabled, lines, onMeasure, containerRef]) return (
{children}
) }