Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@fullcalendar/vue3": "6.1.21",
"@google/model-viewer": "4.3.1",
"@sentry/vue": "10.63.0",
"@tanstack/vue-virtual": "^3.13.31",
"@unhead/vue": "3.1.7",
"@vuepic/vue-datepicker": "14.0.0",
"bowser": "2.14.1",
Expand Down
52 changes: 51 additions & 1 deletion src/components/cells/ValidationCell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<div class="filler" v-if="contactSheet"></div>
<div
class="wrapper status-wrapper"
:class="{ 'single-line': maxAssignees > 0 }"
:style="statusWrapperStyle"
v-if="!minimized"
>
Expand Down Expand Up @@ -64,7 +65,7 @@
'font-weight': isDarkTheme ? 'bold' : 'normal'
}"
:key="`avatar-${person.id}`"
v-for="person in assignees"
v-for="person in visibleAssignees"
>
<img
loading="lazy"
Expand All @@ -74,6 +75,13 @@
/>
<template v-else>{{ person.initials }}</template>
</span>
<span
class="avatar has-text-centered more-assignees"
:title="extraAssigneesTitle"
v-if="extraAssigneesCount > 0"
>
+{{ extraAssigneesCount }}
</span>
</template>
<span class="subscribed" v-if="task?.is_subscribed">
<eye-icon :size="12" />
Expand Down Expand Up @@ -111,6 +119,12 @@ const props = defineProps({
isBorder: { type: Boolean, default: true },
isCastingReady: { type: Boolean, default: false },
left: { type: String, default: '0px' },
// Opt-in (0 = no cap, keeps the historical wrapping behavior): show at
// most this many assignee avatars and collapse the rest into a "+N"
// count on a single non-wrapping line. Virtualized lists (EditList) use
// it so a heavily-assigned task can never wrap the avatar stack and
// change the row height.
maxAssignees: { type: Number, default: 0 },
minimized: { type: Boolean, default: false },
rowX: { type: Number, default: 0 },
selectable: { type: Boolean, default: true },
Expand Down Expand Up @@ -151,6 +165,23 @@ const assignees = computed(() =>
)
)

const visibleAssignees = computed(() =>
props.maxAssignees > 0
? assignees.value.slice(0, props.maxAssignees)
: assignees.value
)

const extraAssigneesCount = computed(
() => assignees.value.length - visibleAssignees.value.length
)

const extraAssigneesTitle = computed(() =>
assignees.value
.slice(props.maxAssignees)
.map(person => person.full_name)
.join('\n')
)

const priority = computed(() => formatPrioritySymbol(task.value.priority))

const cellStyle = computed(() => {
Expand Down Expand Up @@ -276,6 +307,25 @@ defineExpose({
width: 100%;
}

// Opt-in via the maxAssignees prop: the cell content stays on one line and
// clips instead of wrapping, so the cell height is constant whatever the
// status name length or assignee count.
.status-wrapper.single-line {
flex-wrap: nowrap;
overflow: hidden;

> * {
flex-shrink: 0;
}
}

.avatar.more-assignees {
background: var(--background-alt);
color: var(--text);
font-size: 9px;
font-weight: bold;
}

.full-wrapper {
flex: 1;
}
Expand Down
Loading