From b32b1c7bff336c3079b101b11d063ff81b325916 Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Fri, 18 Oct 2024 11:44:18 -0400 Subject: [PATCH] refactor: refine multiselect API --- .changeset/popular-guests-carry.md | 5 ++ .changeset/stupid-planets-hide.md | 5 ++ apps/renterd-e2e/src/specs/keys.spec.ts | 4 +- .../Keys/KeysBatchMenu/KeysBatchDelete.tsx | 16 ++--- .../components/Keys/KeysBatchMenu/index.tsx | 12 +--- .../components/Keys/KeysStatsMenu/index.tsx | 18 +++++ apps/renterd/components/Keys/index.tsx | 2 + apps/renterd/contexts/keys/columns.tsx | 21 ++---- apps/renterd/contexts/keys/index.tsx | 44 ++++++------ apps/renterd/contexts/keys/types.ts | 7 +- .../src/components/Table/TableRow.tsx | 13 +++- .../src/components/Table/index.tsx | 4 +- libs/design-system/src/core/Tooltip.tsx | 4 +- .../src/multi/MultiSelectionMenu.tsx | 43 ++++++------ .../src/multi/useMultiSelect.tsx | 68 ++++++++++++++++--- 15 files changed, 166 insertions(+), 100 deletions(-) create mode 100644 .changeset/popular-guests-carry.md create mode 100644 .changeset/stupid-planets-hide.md create mode 100644 apps/renterd/components/Keys/KeysStatsMenu/index.tsx diff --git a/.changeset/popular-guests-carry.md b/.changeset/popular-guests-carry.md new file mode 100644 index 000000000..52065f7ae --- /dev/null +++ b/.changeset/popular-guests-carry.md @@ -0,0 +1,5 @@ +--- +'@siafoundation/design-system': minor +--- + +Table row data now supports an isSelected prop. diff --git a/.changeset/stupid-planets-hide.md b/.changeset/stupid-planets-hide.md new file mode 100644 index 000000000..7d8b1b2d2 --- /dev/null +++ b/.changeset/stupid-planets-hide.md @@ -0,0 +1,5 @@ +--- +'renterd': minor +--- + +The keys table now has pagination controls. diff --git a/apps/renterd-e2e/src/specs/keys.spec.ts b/apps/renterd-e2e/src/specs/keys.spec.ts index bee5151e7..d937d13a8 100644 --- a/apps/renterd-e2e/src/specs/keys.spec.ts +++ b/apps/renterd-e2e/src/specs/keys.spec.ts @@ -39,8 +39,8 @@ test('batch delete multiple keys', async ({ page }) => { const rowIdx3 = getKeyRowByIndex(page, 3) // Select all 4 keys. - await rowIdx0.getByLabel('select key').click() - await rowIdx3.getByLabel('select key').click({ modifiers: ['Shift'] }) + await rowIdx0.click() + await rowIdx3.click({ modifiers: ['Shift'] }) // Delete all 4 keys. const menu = page.getByLabel('key multiselect menu') diff --git a/apps/renterd/components/Keys/KeysBatchMenu/KeysBatchDelete.tsx b/apps/renterd/components/Keys/KeysBatchMenu/KeysBatchDelete.tsx index 6d0433cec..1fd8c5bb9 100644 --- a/apps/renterd/components/Keys/KeysBatchMenu/KeysBatchDelete.tsx +++ b/apps/renterd/components/Keys/KeysBatchMenu/KeysBatchDelete.tsx @@ -15,15 +15,11 @@ import { useDialog } from '../../../contexts/dialog' import { useKeys } from '../../../contexts/keys' export function KeysBatchDelete() { - const { selectionMap, deselect } = useKeys() + const { multiSelect } = useKeys() - const ids = useMemo( - () => Object.entries(selectionMap).map(([_, item]) => item.id), - [selectionMap] - ) const keys = useMemo( - () => Object.entries(selectionMap).map(([_, item]) => item.key), - [selectionMap] + () => Object.entries(multiSelect.selectionMap).map(([_, item]) => item.key), + [multiSelect.selectionMap] ) const { openConfirmDialog } = useDialog() const settingsS3 = useSettingsS3() @@ -43,13 +39,13 @@ export function KeysBatchDelete() { }, }, }) - deselect(ids) + multiSelect.deselectAll() if (response.error) { triggerErrorToast({ title: 'Error deleting keys', body: response.error }) } else { triggerSuccessToast({ title: `Keys deleted` }) } - }, [settingsS3.data, settingsS3Update, deselect, keys, ids]) + }, [settingsS3.data, settingsS3Update, multiSelect, keys]) return ( diff --git a/libs/design-system/src/multi/useMultiSelect.tsx b/libs/design-system/src/multi/useMultiSelect.tsx index 54ba4a182..e2f361ab3 100644 --- a/libs/design-system/src/multi/useMultiSelect.tsx +++ b/libs/design-system/src/multi/useMultiSelect.tsx @@ -2,6 +2,12 @@ import { MouseEvent, useCallback, useMemo, useState } from 'react' +export type MultiSelectItem = { id: string } + +export type MultiSelect = ReturnType< + typeof useMultiSelect +> + export function useMultiSelect(dataset?: Item[]) { const [selectionMap, setSelectionMap] = useState>({}) const [, setLastSelectedItem] = useState<{ @@ -10,7 +16,7 @@ export function useMultiSelect(dataset?: Item[]) { }>() const onSelect = useCallback( - (id: string, e: MouseEvent) => { + (id: string, e: MouseEvent) => { if (!dataset) { return } @@ -61,6 +67,31 @@ export function useMultiSelect(dataset?: Item[]) { return getIsPageAllSelected({ dataset, selectionMap }) }, [dataset, selectionMap]) + const selectedIds = useMemo( + () => + Object.entries(selectionMap) + .filter(([_, item]) => !!item) + .map(([id]) => id), + [selectionMap] + ) + + const someSelectedItemsOutsideCurrentPage = useMemo(() => { + if (!dataset) { + if (selectedIds.length === 0) { + return false + } + return true + } + return selectedIds.some((id) => !dataset.some((datum) => datum.id === id)) + }, [dataset, selectedIds]) + + const someSelectedOnCurrentPage = useMemo(() => { + if (!dataset) { + return false + } + return dataset.some((datum) => selectionMap[datum.id]) + }, [dataset, selectionMap]) + const onSelectPage = useCallback(() => { if (!dataset) { return @@ -112,15 +143,32 @@ export function useMultiSelect(dataset?: Item[]) { [selectionMap] ) - return { - onSelect, - onSelectPage, - selectionMap, - isPageAllSelected, - selectionCount, - deselect, - deselectAll, - } + return useMemo( + () => ({ + onSelect, + onSelectPage, + selectionMap, + selectedIds, + isPageAllSelected, + selectionCount, + someSelectedItemsOutsideCurrentPage, + someSelectedOnCurrentPage, + deselect, + deselectAll, + }), + [ + onSelect, + onSelectPage, + selectionMap, + selectedIds, + isPageAllSelected, + selectionCount, + someSelectedItemsOutsideCurrentPage, + someSelectedOnCurrentPage, + deselect, + deselectAll, + ] + ) } function getIsPageAllSelected({