club home redesign; deal multi-select delete; anchored card menus

This commit is contained in:
2026-10-12 17:59:07 +03:30
parent a2b01f787e
commit faac26d07d
6 changed files with 373 additions and 50 deletions
+81 -8
View File
@@ -15,6 +15,8 @@ const props = withDefaults(
accent: PipelineAccent;
dragging?: boolean;
copy?: PipelineUiCopy;
selectMode?: boolean;
selected?: boolean;
}>(),
{ copy: () => DEAL_BOARD_COPY },
);
@@ -24,6 +26,8 @@ const emit = defineEmits<{
dragEnd: [];
open: [dealId: string];
menu: [dealId: string];
toggleSelect: [dealId: string];
bulkDelete: [];
}>();
const showAmount = computed(
@@ -42,30 +46,60 @@ const avatarSrc = computed(() =>
const extraAssignees = computed(() =>
Math.max(0, (props.deal.assigneeNames?.length ?? 0) - 1),
);
const showTrash = computed(() => props.selectMode && props.selected);
</script>
<template>
<article
class="deal-card"
:class="{ dragging, 'no-amount': !showAmount }"
:class="{
dragging,
'no-amount': !showAmount,
'select-mode': selectMode,
selected,
}"
:data-card-id="deal.id"
draggable="true"
:draggable="!selectMode"
role="listitem"
:aria-grabbed="dragging ? 'true' : 'false'"
:style="{
'--badge-bg': colors.soft,
'--badge-fg': colors.text,
}"
@dragstart="emit('dragStart', deal, $event)"
@dragstart="!selectMode && emit('dragStart', deal, $event)"
@dragend="emit('dragEnd')"
@dblclick="emit('open', deal.id)"
@dblclick="!selectMode && emit('open', deal.id)"
@click="selectMode && emit('toggleSelect', deal.id)"
>
<span class="badge" aria-hidden="true">{{ deal.initials }}</span>
<button
v-if="selectMode"
type="button"
class="tick"
:class="{ on: selected }"
:aria-label="selected ? 'لغو انتخاب' : 'انتخاب معامله'"
:aria-pressed="selected"
@click.stop="emit('toggleSelect', deal.id)"
>
<UiIcon v-if="selected" name="check" :size="14" />
</button>
<span v-else class="badge" aria-hidden="true">{{ deal.initials }}</span>
<div class="titles">
<h3 @click.stop="emit('open', deal.id)">{{ deal.title }}</h3>
<h3 @click.stop="selectMode ? emit('toggleSelect', deal.id) : emit('open', deal.id)">
{{ deal.title }}
</h3>
<p v-if="deal.description" class="desc">{{ deal.description }}</p>
</div>
<button
v-if="showTrash"
type="button"
class="menu trash"
aria-label="حذف انتخاب‌شده‌ها"
@click.stop="emit('bulkDelete')"
>
<UiIcon name="trash" :size="16" />
</button>
<button
v-else
type="button"
class="menu"
:aria-label="copy.itemMenuAria"
@@ -116,11 +150,21 @@ const extraAssignees = computed(() =>
box-shadow 0.18s ease,
border-color 0.18s ease,
transform 0.18s ease,
opacity 0.18s ease;
opacity 0.18s ease,
background 0.18s ease;
text-align: right;
direction: rtl;
}
.deal-card.select-mode {
cursor: pointer;
}
.deal-card.selected {
border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
background: color-mix(in srgb, var(--accent) 6%, #fff);
}
.deal-card:hover {
border-color: color-mix(in srgb, var(--accent) 28%, var(--line));
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.07);
@@ -130,6 +174,10 @@ const extraAssignees = computed(() =>
cursor: grabbing;
}
.deal-card.select-mode:active {
cursor: pointer;
}
.deal-card.dragging {
opacity: 0.95;
transform: scale(1.02);
@@ -147,7 +195,8 @@ const extraAssignees = computed(() =>
}
.badge,
.avatar {
.avatar,
.tick {
width: var(--face);
height: var(--face);
max-width: var(--face);
@@ -165,6 +214,21 @@ const extraAssignees = computed(() =>
line-height: 1;
}
.tick {
display: grid;
place-items: center;
border: 2px solid #cbd5e1;
background: #fff;
color: #fff;
padding: 0;
cursor: pointer;
}
.tick.on {
border-color: var(--accent);
background: var(--accent);
}
.avatar {
object-fit: cover;
border: 1px solid var(--line);
@@ -239,6 +303,15 @@ const extraAssignees = computed(() =>
outline: none;
}
.menu.trash {
color: var(--danger);
}
.menu.trash:hover,
.menu.trash:focus-visible {
color: var(--danger);
}
.date {
display: flex;
align-items: center;