Add Right click to take action (#263)

* Add Right click to take action

* Create Position type and style row with background when selected
This commit is contained in:
Félix Malfait 2023-06-09 18:36:39 +02:00 committed by GitHub
parent f6e1e626fd
commit c53be4febc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 12 deletions

View File

@ -1,5 +1,8 @@
import * as React from 'react';
import styled from '@emotion/styled';
import { useSetRecoilState } from 'recoil';
import { contextMenuPositionState } from '@/ui/tables/states/contextMenuPositionState';
import { Checkbox } from '../form/Checkbox';
@ -32,6 +35,7 @@ export function CheckboxCell({
indeterminate,
}: OwnProps) {
const [internalChecked, setInternalChecked] = React.useState(checked);
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
function handleContainerClick() {
handleCheckboxChange(!internalChecked);
@ -43,6 +47,7 @@ export function CheckboxCell({
function handleCheckboxChange(newCheckedValue: boolean) {
setInternalChecked(newCheckedValue);
setContextMenuPosition({ x: null, y: null });
if (onChange) {
onChange(newCheckedValue);

View File

@ -6,7 +6,7 @@ import {
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table';
import { useRecoilState } from 'recoil';
import { useRecoilState, useSetRecoilState } from 'recoil';
import {
FilterConfigType,
@ -16,6 +16,7 @@ import {
SelectedSortType,
SortType,
} from '@/filters-and-sorts/interfaces/sorts/interface';
import { contextMenuPositionState } from '@/ui/tables/states/contextMenuPositionState';
import { useResetTableRowSelection } from '../../tables/hooks/useResetTableRowSelection';
import { currentRowSelectionState } from '../../tables/states/rowSelectionState';
@ -89,6 +90,11 @@ const StyledTableScrollableContainer = styled.div`
flex: 1;
`;
const StyledRow = styled.tr<{ selected: boolean }>`
background: ${(props) =>
props.selected ? props.theme.secondaryBackground : 'none'};
`;
export function EntityTable<
TData extends { id: string; __typename: 'companies' | 'people' },
SortField,
@ -105,6 +111,7 @@ export function EntityTable<
const [currentRowSelection, setCurrentRowSelection] = useRecoilState(
currentRowSelectionState,
);
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
const resetTableRowSelection = useResetTableRowSelection();
@ -124,6 +131,16 @@ export function EntityTable<
getRowId: (row) => row.id,
});
function handleContextMenu(event: React.MouseEvent, id: string) {
event.preventDefault();
setCurrentRowSelection((prev) => ({ ...prev, [id]: true }));
setContextMenuPosition({
x: event.clientX,
y: event.clientY,
});
}
return (
<StyledTableWithHeader>
<TableHeader
@ -159,10 +176,19 @@ export function EntityTable<
</thead>
<tbody>
{table.getRowModel().rows.map((row, index) => (
<tr key={row.id} data-testid={`row-id-${row.index}`}>
<StyledRow
key={row.id}
data-testid={`row-id-${row.index}`}
selected={!!currentRowSelection[row.id]}
>
{row.getVisibleCells().map((cell) => {
return (
<td key={cell.id + row.original.id}>
<td
key={cell.id + row.original.id}
onContextMenu={(event) =>
handleContextMenu(event, row.original.id)
}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
@ -170,7 +196,7 @@ export function EntityTable<
</td>
);
})}
</tr>
</StyledRow>
))}
</tbody>
</StyledTable>

View File

@ -1,24 +1,32 @@
import React from 'react';
import React, { useEffect } from 'react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { contextMenuPositionState } from '@/ui/tables/states/contextMenuPositionState';
import { selectedRowIdsState } from '@/ui/tables/states/selectedRowIdsState';
import { PositionType } from '@/ui/types/PositionType';
type OwnProps = {
children: React.ReactNode | React.ReactNode[];
};
const StyledContainer = styled.div`
type StyledContainerProps = {
position: PositionType;
};
const StyledContainer = styled.div<StyledContainerProps>`
display: flex;
position: absolute;
position: ${(props) => (props.position.x ? 'fixed' : 'absolute')};
z-index: 1;
height: 48px;
bottom: 38px;
bottom: ${(props) => (props.position.x ? 'auto' : '38px')};
left: ${(props) => (props.position.x ? `${props.position.x}px` : '50%')};
top: ${(props) => (props.position.y ? `${props.position.y}px` : 'auto')};
background: ${(props) => props.theme.secondaryBackground};
align-items: center;
padding-left: ${(props) => props.theme.spacing(2)};
padding-right: ${(props) => props.theme.spacing(2)};
left: 50%;
transform: translateX(-50%);
border-radius: 8px;
@ -27,10 +35,31 @@ const StyledContainer = styled.div`
export function EntityTableActionBar({ children }: OwnProps) {
const selectedRowIds = useRecoilValue(selectedRowIdsState);
const position = useRecoilValue(contextMenuPositionState);
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (!(event.target as HTMLElement).closest('.action-bar')) {
setContextMenuPosition({ x: null, y: null });
}
}
document.addEventListener('mousedown', handleClickOutside);
// Cleanup the event listener when the component unmounts
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [setContextMenuPosition]);
if (selectedRowIds.length === 0) {
return <></>;
return null;
}
return <StyledContainer>{children}</StyledContainer>;
return (
<StyledContainer className="action-bar" position={position}>
{children}
</StyledContainer>
);
}

View File

@ -0,0 +1,11 @@
import { atom } from 'recoil';
import { PositionType } from '@/ui/types/PositionType';
export const contextMenuPositionState = atom<PositionType>({
key: 'contextMenuPositionState',
default: {
x: null,
y: null,
},
});

View File

@ -0,0 +1,4 @@
export interface PositionType {
x: number | null;
y: number | null;
}