Skip to content

Instantly share code, notes, and snippets.

@tristanwagner
Created May 21, 2024 19:31
Show Gist options
  • Save tristanwagner/b7b3e3d433f8ffd1d6352208c9f047bb to your computer and use it in GitHub Desktop.
Save tristanwagner/b7b3e3d433f8ffd1d6352208c9f047bb to your computer and use it in GitHub Desktop.
import {ComponentPropsWithoutRef, useEffect, useRef, useState} from "react"
import styled from "styled-components";
import RecipientsBadge from "./RecipientsBadge";
const RecipientsTooltip = styled.span`
position: fixed;
top: 8px;
right: 8px;
display: none;
align-items: center;
padding: 8px 16px;
border-radius: 24px;
background-color: #666;
color: #f0f0f0;
`;
export type RecipientDisplayProps = {
recipients: string[]
} & Omit<ComponentPropsWithoutRef<'span'>, 'style'>;
function RecipientsDisplay({recipients, ...rest}: RecipientDisplayProps) {
const [overflowed, setOverflowed] = useState<boolean>(false);
const [displayedRecipients, setDisplayedRecipients] = useState<string[]>(recipients);
const [hiddenRecipients, setHiddenRecipients] = useState<string[]>([]);
const displayTextRef = useRef<HTMLSpanElement | null>(null);
const tooltipRef = useRef<HTMLSpanElement | null>(null);
// check if current content of the container overflows the parent
const isOverflowed = (container: HTMLSpanElement | null) => {
const parent: HTMLTableCellElement | null | undefined = container?.closest("td");
const parentCs = getComputedStyle(parent as Element);
const parentWidth = parseFloat(parentCs.width) - (parseFloat(parentCs.paddingRight) + parseFloat(parentCs.paddingLeft));
if (container && parentWidth) {
return container.offsetWidth > parentWidth;
}
return false;
};
// Handle check for text to fit or not and update accordingly
useEffect(() => {
setOverflowed(isOverflowed(displayTextRef.current));
if (overflowed && displayedRecipients.length > 1) {
setHiddenRecipients([displayedRecipients.slice(-1)[0], ...hiddenRecipients]);
setDisplayedRecipients(displayedRecipients.slice(0, -1));
}
if (overflowed && displayedRecipients.length == 1 && displayTextRef?.current) {
displayTextRef.current.style.overflow = "hidden";
displayTextRef.current.style.textOverflow = "ellipsis";
}
}, [overflowed, displayedRecipients]);
// on mount handle window resize by adding an event listener
useEffect(() => {
const handleResize = () => {
setOverflowed(isOverflowed(displayTextRef.current));
}
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return (
<span {...rest}>
<span ref={displayTextRef}>
{`${displayedRecipients.join(", ")}${hiddenRecipients.length ? ", ..." : ""}`}
</span>
{hiddenRecipients.length ?
<RecipientsBadge
onMouseEnter={() => {
if (tooltipRef?.current) tooltipRef.current.style.display = "flex";
}}
onMouseLeave={() => {
if (tooltipRef?.current) tooltipRef.current.style.display = "none";
}}
numTruncated={hiddenRecipients.length}/>
: null
}
<RecipientsTooltip ref={tooltipRef}>{recipients.join(", ")}</RecipientsTooltip>
</span>
)
}
export default styled(RecipientsDisplay)`
display: flex;
justify-content: space-between;
align-items: center;
`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment