Created
August 14, 2024 14:09
-
-
Save horlarme/0a48984fa39e15b33beffddb8cfd2b88 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script setup lang="ts"> | |
import {computed, nextTick, onBeforeUnmount, onMounted, ref} from "vue"; | |
import RecipientsBadge from "@/components/RecipientsBadge.vue"; | |
interface Props { | |
recipients: Array<string> | |
} | |
const props = defineProps<Props>() | |
let resizeHandlerTimerId: number | undefined; | |
const recipientsRef = ref<Array<HTMLSpanElement>>([]) | |
const badgeContainerRef = ref<HTMLSpanElement | null>(null) | |
const hiddenCount = ref(0) | |
const commaAppendedRecipients = computed(() => props.recipients.map((a, i, { length }) => i !== length ? a.concat(',') : a)) | |
const commaSeparatedRecipients = computed(() => commaAppendedRecipients.value.join(' ')) | |
function isOverflowingParent(element: HTMLElement) { | |
const boundingBox = element.getBoundingClientRect(); | |
const parentBox = element.parentElement!.getBoundingClientRect() | |
return boundingBox.right > parentBox.right | |
} | |
// One other solution might be to make the handler a recursive function that checks the last element's visibility | |
function handler() { | |
// Clear timeout | |
if (resizeHandlerTimerId) { | |
clearTimeout(resizeHandlerTimerId) | |
} | |
// Reset Count | |
hiddenCount.value = 0; | |
// Using setTimeout as our debounce-r | |
resizeHandlerTimerId = setTimeout(() => { | |
if (recipientsRef.value) { | |
// Identify and hide recipient | |
recipientsRef.value.forEach((el) => { | |
el.classList.remove('hidden') | |
// Checking which of the email address is not shown completely | |
if (isOverflowingParent(el)) { | |
el.classList.add('hidden') | |
hiddenCount.value = hiddenCount.value + 1 | |
} | |
}) | |
} | |
nextTick(() => { | |
// Determine if badge is in viewport | |
if (badgeContainerRef.value && isOverflowingParent(badgeContainerRef.value)) { | |
// If the badge isn't fully shown, we will hide the last recipient and increase the count too | |
const firstHiddenReceiptIndex = recipientsRef.value.findIndex(el => el.classList.contains('hidden')) | |
recipientsRef.value.at(firstHiddenReceiptIndex - 1)?.classList.add('hidden') | |
hiddenCount.value = hiddenCount.value + 1 | |
} | |
}) | |
}, 150) | |
} | |
function showTooltip(event: MouseEvent) { | |
const badgeElement = event.target as HTMLElement | |
const badgeBox = badgeElement.getBoundingClientRect() | |
const element = badgeElement.querySelector('.tooltip') as HTMLSpanElement | undefined | |
if (element) { | |
element.style.setProperty('display', 'flex') | |
element.style.setProperty('top', badgeBox.top + 8 + 'px') | |
element.style.setProperty('left', badgeBox.left + 8 + 'px') | |
} | |
} | |
function hideTooltip(event: Event) { | |
const element = event.target as HTMLElement | |
const tooltipElement = element.querySelector('.tooltip') as HTMLSpanElement | undefined; | |
if (tooltipElement) { | |
tooltipElement.style.setProperty('display', 'none') | |
} | |
} | |
onMounted(() => { | |
handler() | |
addEventListener('resize', handler) | |
}) | |
onBeforeUnmount(() => { | |
removeEventListener('resize', handler) | |
}) | |
</script> | |
<template> | |
<span class="recipients"> | |
<span | |
class="recipient" | |
ref="recipientsRef" | |
v-for="recipient in commaAppendedRecipients" | |
:key="recipient" | |
> | |
{{ recipient }} | |
</span> | |
<template v-if="hiddenCount > 0"> | |
<span class="hidden-indicator">...</span> | |
<span | |
ref="badgeContainerRef" | |
class="badge-container" | |
@mouseenter="showTooltip" | |
@mouseleave="hideTooltip" | |
> | |
<RecipientsBadge :num-truncated="hiddenCount"/> | |
<span class="tooltip"> | |
{{ commaSeparatedRecipients }} | |
</span> | |
</span> | |
</template> | |
</span> | |
</template> | |
<style scoped> | |
.recipients { | |
display: flex; | |
align-items: center; | |
} | |
/* Hiding elements from view but available to screen readers */ | |
.hidden { | |
position: absolute; | |
width: 1px; | |
height: 1px; | |
padding: 0; | |
margin: -1px; | |
overflow: hidden; | |
clip: rect(0, 0, 0, 0); | |
white-space: nowrap; | |
border-width: 0; | |
} | |
.hidden-indicator { | |
flex-grow: 1; | |
} | |
.tooltip { | |
padding: 8px 16px; | |
background: var(--color-primary); | |
color: #f0f0f0; | |
border-radius: 24px; | |
display: none; | |
align-items: center; | |
position: fixed; | |
top: 0; | |
left: 0; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment