From 5e6351e099761ffb8e9ba8c247a3618f3320337c Mon Sep 17 00:00:00 2001 From: brendanlaschke Date: Sat, 5 Aug 2023 01:16:01 +0200 Subject: [PATCH] Drag To Select Table (#1064) * - added drag to select on EntityTable * - moved dragToSelect to own component * - convert DragSelect to a generic component * - fixed unused vars * formatting --------- Co-authored-by: Charles Bochet --- .../ui/table/components/EntityTable.tsx | 21 +++++++- .../ui/table/components/EntityTableBody.tsx | 9 +++- .../ui/table/components/EntityTableRow.tsx | 6 ++- .../ui/table/hooks/useSetRowSelectedState.ts | 9 ++++ front/src/utils/DragSelect.tsx | 51 +++++++++++++++++++ 5 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 front/src/modules/ui/table/hooks/useSetRowSelectedState.ts create mode 100644 front/src/utils/DragSelect.tsx diff --git a/front/src/modules/ui/table/components/EntityTable.tsx b/front/src/modules/ui/table/components/EntityTable.tsx index e7b6da1aa0..1f8eb6627a 100644 --- a/front/src/modules/ui/table/components/EntityTable.tsx +++ b/front/src/modules/ui/table/components/EntityTable.tsx @@ -1,12 +1,16 @@ import { useRef } from 'react'; import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; +import { DragSelect } from '../../../../utils/DragSelect'; import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus'; import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus'; +import { useSetRowSelectedState } from '../hooks/useSetRowSelectedState'; import { EntityUpdateMutationHookContext } from '../states/EntityUpdateMutationHookContext'; +import { tableRowIdsState } from '../states/tableRowIdsState'; import { TableHeader } from '../table-header/components/TableHeader'; import { EntityTableBody } from './EntityTableBody'; @@ -99,6 +103,16 @@ export function EntityTable({ useUpdateEntityMutation, }: OwnProps) { const tableBodyRef = useRef(null); + const entityTableBodyRef = useRef(null); + + const rowIds = useRecoilValue(tableRowIdsState); + const setRowSelectedState = useSetRowSelectedState(); + + function resetSelections() { + for (const rowId of rowIds) { + setRowSelectedState(rowId, false); + } + } useMapKeyboardToSoftFocus(); @@ -124,9 +138,14 @@ export function EntityTable({ - + + diff --git a/front/src/modules/ui/table/components/EntityTableBody.tsx b/front/src/modules/ui/table/components/EntityTableBody.tsx index c6204abde5..1b93382f7f 100644 --- a/front/src/modules/ui/table/components/EntityTableBody.tsx +++ b/front/src/modules/ui/table/components/EntityTableBody.tsx @@ -1,3 +1,4 @@ +import { Ref } from 'react'; import { useRecoilValue } from 'recoil'; import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState'; @@ -9,7 +10,11 @@ import { tableRowIdsState } from '../states/tableRowIdsState'; import { EntityTableRow } from './EntityTableRow'; -export function EntityTableBody() { +type OwnProps = { + tbodyRef: Ref; +}; + +export function EntityTableBody({ tbodyRef }: OwnProps) { const rowIds = useRecoilValue(tableRowIdsState); const isNavbarSwitchingSize = useRecoilValue(isNavbarSwitchingSizeState); @@ -23,7 +28,7 @@ export function EntityTableBody() { } return ( - + {rowIds.map((rowId, index) => ( diff --git a/front/src/modules/ui/table/components/EntityTableRow.tsx b/front/src/modules/ui/table/components/EntityTableRow.tsx index 5039001104..c39b7f1907 100644 --- a/front/src/modules/ui/table/components/EntityTableRow.tsx +++ b/front/src/modules/ui/table/components/EntityTableRow.tsx @@ -16,7 +16,11 @@ export function EntityTableRow({ rowId }: { rowId: string }) { const viewFields = useRecoilValue(visibleViewFieldsState); return ( - + diff --git a/front/src/modules/ui/table/hooks/useSetRowSelectedState.ts b/front/src/modules/ui/table/hooks/useSetRowSelectedState.ts new file mode 100644 index 0000000000..9d1c4f1ff3 --- /dev/null +++ b/front/src/modules/ui/table/hooks/useSetRowSelectedState.ts @@ -0,0 +1,9 @@ +import { useRecoilCallback } from 'recoil'; + +import { isRowSelectedFamilyState } from '../states/isRowSelectedFamilyState'; + +export function useSetRowSelectedState() { + return useRecoilCallback(({ set }) => (rowId: string, selected: boolean) => { + set(isRowSelectedFamilyState(rowId), selected); + }); +} diff --git a/front/src/utils/DragSelect.tsx b/front/src/utils/DragSelect.tsx new file mode 100644 index 0000000000..f56ba6b0de --- /dev/null +++ b/front/src/utils/DragSelect.tsx @@ -0,0 +1,51 @@ +import { RefObject } from 'react'; +import { + boxesIntersect, + useSelectionContainer, +} from '@air/react-drag-to-select'; + +type OwnProps = { + dragSelectable: RefObject; + onDragSelectionChange: (id: string, selected: boolean) => void; + onDragSelectionStart: () => void; +}; + +export function DragSelect({ + dragSelectable, + onDragSelectionChange, + onDragSelectionStart, +}: OwnProps) { + const { DragSelection } = useSelectionContainer({ + onSelectionStart: onDragSelectionStart, + onSelectionChange: (box) => { + const scrollAwareBox = { + ...box, + top: box.top + window.scrollY, + left: box.left + window.scrollX, + }; + Array.from( + dragSelectable.current?.querySelectorAll('[data-selectable-id]') ?? [], + ).forEach((item) => { + const id = item.getAttribute('data-selectable-id'); + if (!id) { + return; + } + if (boxesIntersect(scrollAwareBox, item.getBoundingClientRect())) { + onDragSelectionChange(id, true); + } else { + onDragSelectionChange(id, false); + } + }); + }, + selectionProps: { + style: { + border: '1px solid #4C85D8', + background: 'rgba(155, 193, 239, 0.4)', + position: `absolute`, + zIndex: 99, + }, + }, + }); + + return ; +}