Skip to content

Instantly share code, notes, and snippets.

@yasirismail009
Created October 24, 2024 13:21
Show Gist options
  • Save yasirismail009/1e2b082a56e3ee7ad12f1bc1128aadad to your computer and use it in GitHub Desktop.
Save yasirismail009/1e2b082a56e3ee7ad12f1bc1128aadad to your computer and use it in GitHub Desktop.
<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>
<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>
<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