import { cloneElement, ComponentProps, Fragment, ReactElement, useCallback, useEffect, useRef, useState, } from 'react'; import Box from '../Box'; import ColorSchemeProvider, { useColorScheme } from '../contexts/ColorSchemeProvider'; import { useDefaultLabelContext } from '../contexts/DefaultLabelProvider'; import DesignTokensProvider from '../contexts/DesignTokensProvider'; import Icon, { IconColor } from '../Icon'; import IconCompact from '../IconCompact'; import Link from '../Link'; import Mask from '../Mask'; import Spinner from '../Spinner'; import Text from '../Text'; import useExperimentalTheme from '../utils/useExperimentalTheme'; const SIZE_THUMBNAIL = 32; const SIZE_ICON = 24; const SIZE_ICON_VR = 20; export function Message({ text, textElement, helperLink, textColor, type, }: { text: string | null | undefined | ReactElement; textElement: string | null | undefined | ReactElement; textColor: ComponentProps['color']; helperLink?: { text: string; accessibilityLabel: string; href: string; onClick?: (arg1: { event: React.MouseEvent | React.KeyboardEvent; dangerouslyDisableOnNavigation: () => void; }) => void; }; type?: 'default' | 'success' | 'error' | 'progress'; }) { const theme = useExperimentalTheme(); const isError = type === 'error'; const textRef = useRef(null); const [ellipsisActive, setEllipsisActive] = useState(false); // There’s two attributes for HTML elements which we can use to check if the text is truncated, offsetHeight and scrollHeight. scrollHeight is the total scrollable content height, and offsetHeight is the visible height on the screen. For an overflow view, the scrollHeight is larger than offsetHeight. We can deduce that if the scrollHeight is larger than the offsetHeight, then the element is truncated. const isEllipsisActive = (element: HTMLDivElement) => element.offsetHeight < element.scrollHeight || element.offsetWidth < element.scrollWidth; const checkEllipsisActive = useCallback(() => { if (textRef.current && !ellipsisActive && isEllipsisActive(textRef?.current)) { setEllipsisActive(true); } else if (textRef.current && ellipsisActive && !isEllipsisActive(textRef?.current)) { setEllipsisActive(false); } }, [ellipsisActive]); useEffect(() => { checkEllipsisActive(); if (typeof window !== 'undefined') window.addEventListener('resize', checkEllipsisActive); return () => { if (typeof window !== 'undefined') window?.removeEventListener('resize', checkEllipsisActive); }; }, [checkEllipsisActive]); const isTruncated = !textElement && text && ellipsisActive && !theme.MAIN; const isTruncatedWithHelperLink = isTruncated && helperLink && !theme.MAIN; return ( {textElement ?? null} {!textElement && text ? ( {text} {helperLink ? ( {' '} {helperLink.text} ) : null} ) : null} {/* Should the helkper link */} {isTruncatedWithHelperLink ? ( {helperLink?.text} ) : null} ); } export function ImageThumbnail({ thumbnail }: { thumbnail: ReactElement }) { const theme = useExperimentalTheme(); return ( {thumbnail} ); } export function IconThumbnail({ thumbnail, overrideColor, }: { thumbnail: ReactElement; overrideColor?: IconColor; }) { const theme = useExperimentalTheme(); return ( {cloneElement(thumbnail, { size: theme.MAIN ? 32 : SIZE_ICON, color: overrideColor ?? thumbnail.props.color, })} ); } export function AvatarThumbnail({ thumbnail }: { thumbnail: ReactElement }) { return {cloneElement(thumbnail, { size: 'sm' })}; } export function TypeThumbnail({ type }: { type: 'default' | 'success' | 'error' | 'progress' }) { const { colorSchemeName } = useColorScheme(); const { accessibilityIconSuccessLabel, accessibilityIconErrorLabel, accessibilityProcessingLabel, } = useDefaultLabelContext('Toast'); const theme = useExperimentalTheme(); const errorIcon = theme.MAIN ? ( ) : ( ); const successIcon = theme.MAIN ? ( ) : ( ); return ( {type === 'error' ? errorIcon : null} {type === 'success' ? ( {successIcon} ) : null} {type === 'progress' ? ( ) : null} ); }