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
5 changed files with 87 additions and 12 deletions

View File

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

View File

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

View File

@ -1,24 +1,32 @@
import React from 'react'; import React, { useEffect } from 'react';
import styled from '@emotion/styled'; 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 { selectedRowIdsState } from '@/ui/tables/states/selectedRowIdsState';
import { PositionType } from '@/ui/types/PositionType';
type OwnProps = { type OwnProps = {
children: React.ReactNode | React.ReactNode[]; children: React.ReactNode | React.ReactNode[];
}; };
const StyledContainer = styled.div` type StyledContainerProps = {
position: PositionType;
};
const StyledContainer = styled.div<StyledContainerProps>`
display: flex; display: flex;
position: absolute; position: ${(props) => (props.position.x ? 'fixed' : 'absolute')};
z-index: 1; z-index: 1;
height: 48px; 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}; background: ${(props) => props.theme.secondaryBackground};
align-items: center; align-items: center;
padding-left: ${(props) => props.theme.spacing(2)}; padding-left: ${(props) => props.theme.spacing(2)};
padding-right: ${(props) => props.theme.spacing(2)}; padding-right: ${(props) => props.theme.spacing(2)};
left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
border-radius: 8px; border-radius: 8px;
@ -27,10 +35,31 @@ const StyledContainer = styled.div`
export function EntityTableActionBar({ children }: OwnProps) { export function EntityTableActionBar({ children }: OwnProps) {
const selectedRowIds = useRecoilValue(selectedRowIdsState); 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) { 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;
}