Skip to content

Instantly share code, notes, and snippets.

@danielcardeenas
Last active August 25, 2024 20:23
Show Gist options
  • Save danielcardeenas/499324ca693d278f069b19a17c446d74 to your computer and use it in GitHub Desktop.
Save danielcardeenas/499324ca693d278f069b19a17c446d74 to your computer and use it in GitHub Desktop.
Hennge Challange - Email Audit (React)
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
import RecipientsBadge from './RecipientsBadge'
import { styled } from 'styled-components'
const GAP_PX = 40
const TooltipComponent = styled.div`
position: fixed;
top: 8px;
right: 8px;
padding: 8px 16px;
background-color: #666;
color: #f0f0f0;
border-radius: 24px;
display: flex;
align-items: center;
`
const Container = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`
const RecipientsList = styled.span`
overflow: hidden;
text-overflow: ellipsis;
`
interface RecipientDisplayProps {
recipients: string[]
}
export default function RecipientsDisplay({
recipients,
}: RecipientDisplayProps) {
// Parent
const parentRef = useRef<HTMLDivElement>(null)
const [parentWidth, setParentWidth] = useState(0)
// Container
const ref = useRef<HTMLDivElement>(null)
const widthsCache = useRef<number[]>([])
// Recipients
const [visibleRecipients, setVisibleRecipients] =
useState<string[]>(recipients)
// Tooltip
const [isTooltipVisible, setIsTooltipVisible] = useState(false)
useEffect(() => {
if (!ref) {
return
}
const resizeObserver = new ResizeObserver(event => {
// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentBoxSize
const width = event[0].contentBoxSize[0].inlineSize
setParentWidth(width)
})
resizeObserver.observe(parentRef.current as Element)
}, [ref])
useLayoutEffect(() => {
const { current } = ref
if (current && parentWidth > 0) {
if (widthsCache.current.length === 0) {
// Calculate the width of each recipient one time
const widths: number[] = []
current.querySelectorAll(`span`).forEach(span => {
const spanWidth = span.getBoundingClientRect().width
widths.push(spanWidth)
})
widthsCache.current = widths
}
// Find the index of the last visible recipient
let totalWidth = 0
let lastVisibleIndex = 0
for (let i = 0; i < widthsCache.current.length; i++) {
totalWidth += widthsCache.current[i]
if (totalWidth > parentWidth - GAP_PX) {
break
}
lastVisibleIndex = i
}
// Update the visible recipients
setVisibleRecipients(recipients.slice(0, lastVisibleIndex + 1))
}
}, [ref, parentWidth])
return (
<Container ref={parentRef}>
<RecipientsList ref={ref}>
{visibleRecipients.map((recipient, index) => (
<span key={`key-${recipient}-${index}`}>
{recipient}
{index < visibleRecipients.length - 1 ? ', ' : ''}
</span>
))}
{visibleRecipients.length < recipients.length ? ', ...' : ''}
</RecipientsList>
{visibleRecipients.length < recipients.length ? (
<RecipientsBadge
onMouseEnter={() => setIsTooltipVisible(true)}
onMouseLeave={() => setIsTooltipVisible(false)}
numTruncated={recipients.length - visibleRecipients.length}
/>
) : null}
{isTooltipVisible && (
<TooltipComponent>{recipients.join(', ')}</TooltipComponent>
)}
</Container>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment