import { useCallback, useRef, useState } from 'react'; import { useAppTranslation } from '@features/localization/renderer'; import { ChevronDown, ChevronUp } from 'lucide-react'; const DEFAULT_COLLAPSED_HEIGHT = 200; // px interface ExpandableContentProps { /** Content to render inside the expandable container. */ children: React.ReactNode; /** Maximum height (px) before truncation kicks in. Default: 200. */ collapsedHeight?: number; /** Extra className applied to the outermost wrapper. */ className?: string; /** Called when the user clicks "Show more" to expand the content. */ onExpand?: () => void; } /** * Generic expand/collapse wrapper with: * - Collapsed: content clipped at `collapsedHeight`, mask-image fade, "Show more" button * - Expanded: full content, sticky "Show less" button at viewport bottom * * Uses CSS `mask-image` for the fade so it works on any background color * (zebra stripes, card backgrounds, etc.) without needing to know the bg color. */ export const ExpandableContent = ({ children, collapsedHeight = DEFAULT_COLLAPSED_HEIGHT, className, onExpand, }: ExpandableContentProps): React.JSX.Element => { const { t } = useAppTranslation('common'); const anchorRef = useRef(null); const [expanded, setExpanded] = useState(false); const [needsTruncation, setNeedsTruncation] = useState(false); // Measure content height via callback ref — re-runs when children change const measureRef = useCallback( (node: HTMLDivElement | null) => { if (node) { requestAnimationFrame(() => { setNeedsTruncation(node.scrollHeight > collapsedHeight); }); } }, // Re-measure when children identity changes (content prop in callers) // eslint-disable-next-line react-hooks/exhaustive-deps -- children identity triggers re-measure [children, collapsedHeight] ); const handleCollapse = useCallback(() => { setExpanded(false); anchorRef.current?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); }, []); return (
{children}
{/* Show more */} {!expanded && needsTruncation ? (
) : null} {/* Sticky Show less */} {expanded && needsTruncation ? (
) : null}
); };