Handle selection within generic CardList

VueJS generics are kinda insane. And awesome.
This commit is contained in:
Eli Ribble 2026-05-18 15:40:02 +00:00
parent 80b06c2a52
commit da229592f5
No known key found for this signature in database
2 changed files with 77 additions and 6 deletions

View file

@ -29,7 +29,12 @@
</style> </style>
<template> <template>
<ListCard :items="items" :loading="loading"> <ListCard
:items="items"
:loading="loading"
selection-mode="single"
@selection-change="handleSelectionChange"
>
<!-- Filters Slot --> <!-- Filters Slot -->
<template #filters="{ applyFilter, clearFilters, activeFilters }"> <template #filters="{ applyFilter, clearFilters, activeFilters }">
<div class="user-filters"> <div class="user-filters">
@ -50,11 +55,11 @@
</template> </template>
<!-- Item Slot --> <!-- Item Slot -->
<template #item="{ item }"> <template #item="{ item, isSelected, toggleSelection }">
<ListCardContact <ListCardContact
:contact="item" :contact="item"
@click="handleContactClick(item)" @click="toggleSelection"
:isSelected="false" :is-selected="isSelected"
/> />
</template> </template>
</ListCard> </ListCard>
@ -96,6 +101,10 @@ const handleClearFilters = (clearFilters: () => void) => {
const handleContactClick = (contact: any) => { const handleContactClick = (contact: any) => {
console.log("Clicked contact:", contact); console.log("Clicked contact:", contact);
}; };
const handleSelectionChange = (selectedContacts: Contact[]) => {
console.log("Selected contacts:", selectedContacts);
// Do something with the selected contacts
};
const items = computed((): Contact[] => { const items = computed((): Contact[] => {
if (props.contacts) { if (props.contacts) {

View file

@ -34,7 +34,13 @@
</button> </button>
</div> </div>
<div v-else v-for="item in filteredItems" :key="item.id"> <div v-else v-for="item in filteredItems" :key="item.id">
<slot name="item" :item="item" :index="item.id"></slot> <slot
name="item"
:item="item"
:index="item.id"
:is-selected="isSelected(item)"
:toggle-selection="() => toggleSelection(item)"
></slot>
</div> </div>
</div> </div>
</div> </div>
@ -47,11 +53,13 @@ interface Props {
items: T[]; items: T[];
loading?: boolean; loading?: boolean;
itemKey?: keyof T | ((item: T) => string | number); itemKey?: keyof T | ((item: T) => string | number);
selectionMode?: "single" | "multiple" | "none";
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
loading: false, loading: false,
itemKey: "id", itemKey: "id",
selectionMode: "none",
}); });
defineSlots<{ defineSlots<{
@ -60,12 +68,22 @@ defineSlots<{
clearFilters: () => void; clearFilters: () => void;
activeFilters: number; activeFilters: number;
}): any; }): any;
item(props: { item: T; index: number }): any; item(props: {
item: T;
index: string | number;
isSelected: boolean;
toggleSelection: () => void;
}): any;
footer(props: { total: number }): any; footer(props: { total: number }): any;
}>(); }>();
// State // State
const emit = defineEmits<{
"update:selectedItems": [items: T[]];
"selection-change": [items: T[]];
}>();
const filterFunctions = ref<Array<(item: T) => boolean>>([]); const filterFunctions = ref<Array<(item: T) => boolean>>([]);
const selectedItemKeys = ref<Set<string | number>>(new Set());
// Computed // Computed
const filteredItems = computed(() => { const filteredItems = computed(() => {
@ -78,6 +96,11 @@ const filteredItems = computed(() => {
); );
}); });
const activeFilters = computed(() => filterFunctions.value.length); const activeFilters = computed(() => filterFunctions.value.length);
const selectedItems = computed(() => {
return props.items.filter((item) =>
selectedItemKeys.value.has(getItemKey(item)),
);
});
// Methods // Methods
const applyFilter = (filterFn: (item: T) => boolean) => { const applyFilter = (filterFn: (item: T) => boolean) => {
@ -95,4 +118,43 @@ const getItemKey = (item: T): string | number => {
} }
return item[props.itemKey] as string | number; return item[props.itemKey] as string | number;
}; };
const isSelected = (item: T): boolean => {
return selectedItemKeys.value.has(getItemKey(item));
};
const toggleSelection = (item: T) => {
if (props.selectionMode === "none") return;
const key = getItemKey(item);
if (props.selectionMode === "single") {
if (selectedItemKeys.value.has(key)) {
selectedItemKeys.value.clear();
} else {
selectedItemKeys.value.clear();
selectedItemKeys.value.add(key);
}
} else {
if (selectedItemKeys.value.has(key)) {
selectedItemKeys.value.delete(key);
} else {
selectedItemKeys.value.add(key);
}
}
emit("update:selectedItems", selectedItems.value);
emit("selection-change", selectedItems.value);
};
const clearSelection = () => {
selectedItemKeys.value.clear();
emit("update:selectedItems", []);
emit("selection-change", []);
};
defineExpose({
clearSelection,
selectedItems,
});
</script> </script>