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 <charlesBochet@users.noreply.github.com>
This commit is contained in:
brendanlaschke 2023-08-05 01:16:01 +02:00 committed by GitHub
parent b6e1853d9f
commit 5e6351e099
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 92 additions and 4 deletions

View File

@ -1,12 +1,16 @@
import { useRef } from 'react'; import { useRef } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface'; import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { DragSelect } from '../../../../utils/DragSelect';
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus'; import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus'; import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
import { useSetRowSelectedState } from '../hooks/useSetRowSelectedState';
import { EntityUpdateMutationHookContext } from '../states/EntityUpdateMutationHookContext'; import { EntityUpdateMutationHookContext } from '../states/EntityUpdateMutationHookContext';
import { tableRowIdsState } from '../states/tableRowIdsState';
import { TableHeader } from '../table-header/components/TableHeader'; import { TableHeader } from '../table-header/components/TableHeader';
import { EntityTableBody } from './EntityTableBody'; import { EntityTableBody } from './EntityTableBody';
@ -99,6 +103,16 @@ export function EntityTable<SortField>({
useUpdateEntityMutation, useUpdateEntityMutation,
}: OwnProps<SortField>) { }: OwnProps<SortField>) {
const tableBodyRef = useRef<HTMLDivElement>(null); const tableBodyRef = useRef<HTMLDivElement>(null);
const entityTableBodyRef = useRef<HTMLTableSectionElement>(null);
const rowIds = useRecoilValue(tableRowIdsState);
const setRowSelectedState = useSetRowSelectedState();
function resetSelections() {
for (const rowId of rowIds) {
setRowSelectedState(rowId, false);
}
}
useMapKeyboardToSoftFocus(); useMapKeyboardToSoftFocus();
@ -124,9 +138,14 @@ export function EntityTable<SortField>({
<StyledTableWrapper> <StyledTableWrapper>
<StyledTable> <StyledTable>
<EntityTableHeader /> <EntityTableHeader />
<EntityTableBody /> <EntityTableBody tbodyRef={entityTableBodyRef} />
</StyledTable> </StyledTable>
</StyledTableWrapper> </StyledTableWrapper>
<DragSelect
dragSelectable={entityTableBodyRef}
onDragSelectionStart={resetSelections}
onDragSelectionChange={setRowSelectedState}
/>
</StyledTableContainer> </StyledTableContainer>
</StyledTableWithHeader> </StyledTableWithHeader>
</EntityUpdateMutationHookContext.Provider> </EntityUpdateMutationHookContext.Provider>

View File

@ -1,3 +1,4 @@
import { Ref } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState'; import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
@ -9,7 +10,11 @@ import { tableRowIdsState } from '../states/tableRowIdsState';
import { EntityTableRow } from './EntityTableRow'; import { EntityTableRow } from './EntityTableRow';
export function EntityTableBody() { type OwnProps = {
tbodyRef: Ref<HTMLTableSectionElement>;
};
export function EntityTableBody({ tbodyRef }: OwnProps) {
const rowIds = useRecoilValue(tableRowIdsState); const rowIds = useRecoilValue(tableRowIdsState);
const isNavbarSwitchingSize = useRecoilValue(isNavbarSwitchingSizeState); const isNavbarSwitchingSize = useRecoilValue(isNavbarSwitchingSizeState);
@ -23,7 +28,7 @@ export function EntityTableBody() {
} }
return ( return (
<tbody> <tbody ref={tbodyRef}>
{rowIds.map((rowId, index) => ( {rowIds.map((rowId, index) => (
<RowIdContext.Provider value={rowId} key={rowId}> <RowIdContext.Provider value={rowId} key={rowId}>
<RowIndexContext.Provider value={index}> <RowIndexContext.Provider value={index}>

View File

@ -16,7 +16,11 @@ export function EntityTableRow({ rowId }: { rowId: string }) {
const viewFields = useRecoilValue(visibleViewFieldsState); const viewFields = useRecoilValue(visibleViewFieldsState);
return ( return (
<StyledRow data-testid={`row-id-${rowId}`} selected={false}> <StyledRow
data-testid={`row-id-${rowId}`}
selected={false}
data-selectable-id={rowId}
>
<td> <td>
<CheckboxCell /> <CheckboxCell />
</td> </td>

View File

@ -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);
});
}

View File

@ -0,0 +1,51 @@
import { RefObject } from 'react';
import {
boxesIntersect,
useSelectionContainer,
} from '@air/react-drag-to-select';
type OwnProps = {
dragSelectable: RefObject<HTMLElement>;
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 <DragSelection />;
}