Created
October 24, 2024 13:21
-
-
Save yasirismail009/1e2b082a56e3ee7ad12f1bc1128aadad 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 } from 'vue' | |
import { groupBy } from 'lodash-es' | |
import RecipientsDisplay from '@/components/RecipientsDisplay.vue' | |
import DateDisplay from '@/components/DateDisplay.vue' | |
import TimeDisplay from '@/components/TimeDisplay.vue' | |
import type { Email } from '@/types/Email' | |
const props = defineProps<{ | |
emails: Email[] | |
}>() | |
// Convert into an array of arrays based on date sent | |
const emailGroupsByDate = computed(() => { | |
const emailsByDate = groupBy<Email>(props.emails, ({ datetime }) => | |
new Date(datetime).toLocaleDateString() | |
) | |
return Object.values(emailsByDate) | |
}) | |
</script> | |
<template> | |
<table cellspacing="0"> | |
<thead> | |
<tr> | |
<th>Sender</th> | |
<th>Recipients</th> | |
<th>Subject</th> | |
<th class="align-right">Date</th> | |
<th class="align-right">Time</th> | |
</tr> | |
</thead> | |
<tbody v-for="emailGroup in emailGroupsByDate" :key="emailGroup[0].datetime"> | |
<tr | |
v-for="{ id, from, to: recipients, subject, datetime } in emailGroup" | |
:key="id" | |
> | |
<td>{{ from }}</td> | |
<td :data-testid="id" style="overflow: visible;"> | |
<RecipientsDisplay :recipients="recipients" /> | |
</td> | |
<td>{{ subject }}</td> | |
<td class="align-right"> | |
<DateDisplay :datetime="datetime" /> | |
</td> | |
<td class="align-right"> | |
<TimeDisplay :datetime="datetime" /> | |
</td> | |
</tr> | |
</tbody> | |
</table> | |
</template> | |
<style scoped> | |
:root { | |
--border-style: solid 1px #777; | |
} | |
table { | |
table-layout: fixed; | |
border: var(--border-style); | |
width: 100%; | |
text-align: left; | |
} | |
th, | |
td { | |
border: var(--border-style); | |
padding: 5px 10px; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
height: 34px; | |
box-sizing: border-box; | |
} | |
th:nth-child(1) { | |
width: 20%; | |
} | |
th:nth-child(2) { | |
width: 30%; | |
} | |
th:nth-child(3) { | |
width: 50%; | |
} | |
th:nth-child(4) { | |
width: 90px; | |
} | |
th:nth-child(5) { | |
width: 70px; | |
} | |
tbody:nth-child(even) { | |
background-color: #ddd; | |
} | |
.align-right { | |
text-align: right; | |
} | |
</style> |
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"> | |
const props = defineProps<{ numTruncated: number }>(); | |
</script> | |
<template> | |
<span class="badge" data-testid="badge">+{{ props.numTruncated }}</span> | |
</template> | |
<style scoped> | |
.badge { | |
padding: 2px 5px; | |
border-radius: 3px; | |
background-color: var(--color-primary); | |
color: #f0f0f0; | |
cursor: pointer; | |
} | |
</style> | |
<script lang="ts"> | |
// Add this part to ensure it's exported as default | |
export default { | |
name: 'RecipientsBadge' | |
}; | |
</script> |
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
<template> | |
<div ref="containerRef" class="recipients-container"> | |
<!-- Display visible recipients --> | |
<span class="recipients-list">{{ visibleRecipients.join(', ') }}</span> | |
<!-- Show ellipsis and badge if some recipients are hidden --> | |
<span v-if="hiddenCount > 0"> | |
, ... | |
<StyledRecipientsBadge | |
:num-truncated="hiddenCount" | |
@mouseenter="isTooltipVisible = true" | |
@mouseleave="isTooltipVisible = false" | |
/> | |
</span> | |
<!-- Tooltip for displaying all recipients --> | |
<Tooltip v-if="isTooltipVisible" class="tooltip show"> | |
<TooltipTitle class="tooltip-title">Recipients:</TooltipTitle> | |
<TooltipList class="tooltip-list"> | |
<TooltipItem v-for="(recipient, index) in recipients" :key="index" class="tooltip-item"> | |
{{ recipient }} | |
</TooltipItem> | |
</TooltipList> | |
</Tooltip> | |
</div> | |
</template> | |
<script lang="ts"> | |
import { defineComponent, ref, onMounted, onBeforeUnmount } from 'vue'; | |
import StyledRecipientsBadge from './RecipientsBadge.vue'; // Use the correct name here | |
interface RecipientsDisplayProps { | |
recipients: string[]; | |
} | |
export default defineComponent({ | |
name: 'RecipientsDisplay', | |
components: { | |
StyledRecipientsBadge, // Register the StyledRecipientsBadge component | |
}, | |
props: { | |
recipients: { | |
type: Array as () => string[], | |
required: true, | |
}, | |
}, | |
setup(props: RecipientsDisplayProps) { | |
const isTooltipVisible = ref<boolean>(false); | |
const visibleRecipients = ref<string[]>([]); | |
const hiddenCount = ref<number>(0); | |
const containerRef = ref<HTMLDivElement | null>(null); | |
const adjustRecipientsDisplay = () => { | |
if (!containerRef.value) return; | |
const containerWidth = containerRef.value.offsetWidth; | |
let currentWidth = 0; | |
let visible: string[] = []; | |
let hidden = 0; | |
props.recipients.forEach((recipient, index) => { | |
const width = measureTextWidth(recipient); | |
if (currentWidth + width + measureTextWidth(", ...") < containerWidth || index === 0) { | |
currentWidth += width + measureTextWidth(', '); | |
visible.push(recipient); | |
} else { | |
hidden += 1; | |
} | |
}); | |
visibleRecipients.value = visible; | |
hiddenCount.value = hidden; | |
}; | |
const measureTextWidth = (text: string): number => { | |
const canvas = document.createElement('canvas'); | |
const context = canvas.getContext('2d'); | |
if (context) { | |
context.font = getComputedStyle(document.body).font; | |
return context.measureText(text).width; | |
} | |
return 0; | |
}; | |
onMounted(() => { | |
adjustRecipientsDisplay(); | |
window.addEventListener('resize', adjustRecipientsDisplay); | |
}); | |
onBeforeUnmount(() => { | |
window.removeEventListener('resize', adjustRecipientsDisplay); | |
}); | |
return { | |
containerRef, | |
isTooltipVisible, | |
visibleRecipients, | |
hiddenCount, | |
}; | |
}, | |
}); | |
</script> | |
<style scoped> | |
.recipients-container { | |
display: flex; | |
align-items: center; | |
white-space: nowrap; | |
width: 100%; | |
position: relative; /* Ensure this is set for absolute positioning of children */ | |
} | |
.recipients-list { | |
overflow: hidden; | |
white-space: nowrap; | |
text-overflow: ellipsis; | |
max-width: 100%; | |
display: inline-block; | |
} | |
.tooltip { | |
position: absolute; | |
bottom: 100%; /* Position above the badge */ | |
left: 70%; /* Center horizontally */ | |
transform: translateX(-50%); /* Adjust to center */ | |
padding: 8px 12px; | |
background-color: #666; | |
color: #f0f0f0; | |
border-radius: 8px; | |
white-space: nowrap; | |
z-index: 9999; /* Adjusted for safety */ | |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |
margin-bottom: 8px; /* Space between badge and tooltip */ | |
display: none; /* Hide initially */ | |
} | |
.tooltip.show { | |
display: block; /* Show when needed */ | |
} | |
.tooltip-title { | |
font-weight: bold; | |
margin-bottom: 4px; | |
} | |
.tooltip-list { | |
display: flex; /* Change this to flex to stack items */ | |
flex-direction: column; /* Stack items in a column */ | |
list-style: none; | |
padding: 0; | |
margin: 0; | |
} | |
.tooltip-item { | |
margin: 4px 0; | |
} | |
.styled-recipients-badge { | |
flex-shrink: 0; | |
padding: 2px 5px; | |
border-radius: 3px; | |
background-color: var(--color-primary); | |
color: #f0f0f0; | |
position: relative; | |
cursor: pointer; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment