mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-29 15:25:45 +03:00
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:
parent
f6e1e626fd
commit
c53be4febc
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user