mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-18 09:02:11 +03:00
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:
parent
b6e1853d9f
commit
5e6351e099
@ -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>
|
||||||
|
@ -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}>
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
51
front/src/utils/DragSelect.tsx
Normal file
51
front/src/utils/DragSelect.tsx
Normal 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 />;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user