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:
@ -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);
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
},
|
||||||
|
});
|
||||||
4
front/src/modules/ui/types/PositionType.tsx
Normal file
4
front/src/modules/ui/types/PositionType.tsx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface PositionType {
|
||||||
|
x: number | null;
|
||||||
|
y: number | null;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user