115 lines
2.7 KiB
TypeScript
115 lines
2.7 KiB
TypeScript
/**
|
|
* @ai-summary Hook for managing bulk selection state across paginated data
|
|
* @ai-context Supports individual toggle, select all, and reset operations
|
|
*/
|
|
|
|
import { useState, useCallback, useMemo } from 'react';
|
|
|
|
/**
|
|
* Options for bulk selection hook
|
|
*/
|
|
interface UseBulkSelectionOptions<T> {
|
|
items: T[];
|
|
keyExtractor?: (item: T) => string;
|
|
}
|
|
|
|
/**
|
|
* Return type for bulk selection hook
|
|
*/
|
|
interface UseBulkSelectionReturn<T> {
|
|
selected: Set<string>;
|
|
toggleItem: (id: string) => void;
|
|
toggleAll: (items: T[]) => void;
|
|
isSelected: (id: string) => boolean;
|
|
reset: () => void;
|
|
count: number;
|
|
selectedItems: T[];
|
|
}
|
|
|
|
/**
|
|
* Custom hook for managing bulk selection state
|
|
* Supports selection across pagination boundaries
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const { selected, toggleItem, toggleAll, reset, count } = useBulkSelection({ items: data });
|
|
* ```
|
|
*/
|
|
export function useBulkSelection<T extends { id: string }>(
|
|
options: UseBulkSelectionOptions<T>
|
|
): UseBulkSelectionReturn<T> {
|
|
const { items, keyExtractor = (item: T) => item.id } = options;
|
|
|
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
|
|
|
/**
|
|
* Toggle individual item selection
|
|
*/
|
|
const toggleItem = useCallback((id: string) => {
|
|
setSelected((prev) => {
|
|
const next = new Set(prev);
|
|
if (next.has(id)) {
|
|
next.delete(id);
|
|
} else {
|
|
next.add(id);
|
|
}
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
/**
|
|
* Toggle all items - if all are selected, deselect all; otherwise select all
|
|
* This supports "Select All" across pagination
|
|
*/
|
|
const toggleAll = useCallback((itemsToToggle: T[]) => {
|
|
setSelected((prev) => {
|
|
const itemIds = itemsToToggle.map(keyExtractor);
|
|
const allSelected = itemIds.every((id) => prev.has(id));
|
|
|
|
if (allSelected) {
|
|
// Deselect all items
|
|
const next = new Set(prev);
|
|
itemIds.forEach((id) => next.delete(id));
|
|
return next;
|
|
} else {
|
|
// Select all items
|
|
const next = new Set(prev);
|
|
itemIds.forEach((id) => next.add(id));
|
|
return next;
|
|
}
|
|
});
|
|
}, [keyExtractor]);
|
|
|
|
/**
|
|
* Check if item is selected
|
|
*/
|
|
const isSelected = useCallback(
|
|
(id: string) => selected.has(id),
|
|
[selected]
|
|
);
|
|
|
|
/**
|
|
* Clear all selections
|
|
*/
|
|
const reset = useCallback(() => {
|
|
setSelected(new Set());
|
|
}, []);
|
|
|
|
/**
|
|
* Get array of selected items from current items list
|
|
*/
|
|
const selectedItems = useMemo(() => {
|
|
return items.filter((item) => selected.has(keyExtractor(item)));
|
|
}, [items, selected, keyExtractor]);
|
|
|
|
return {
|
|
selected,
|
|
toggleItem,
|
|
toggleAll,
|
|
isSelected,
|
|
reset,
|
|
count: selected.size,
|
|
selectedItems,
|
|
};
|
|
}
|