Skip to content

Instantly share code, notes, and snippets.

@horlarme
Created August 14, 2024 14:09
Show Gist options
  • Save horlarme/0a48984fa39e15b33beffddb8cfd2b88 to your computer and use it in GitHub Desktop.
Save horlarme/0a48984fa39e15b33beffddb8cfd2b88 to your computer and use it in GitHub Desktop.
<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 }}&nbsp;
</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