From c53be4febc44a9b794a42e88c2da53b2886428bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Fri, 9 Jun 2023 18:36:39 +0200 Subject: [PATCH] Add Right click to take action (#263) * Add Right click to take action * Create Position type and style row with background when selected --- .../ui/components/table/CheckboxCell.tsx | 5 +++ .../ui/components/table/EntityTable.tsx | 34 ++++++++++++-- .../table/action-bar/EntityTableActionBar.tsx | 45 +++++++++++++++---- .../tables/states/contextMenuPositionState.ts | 11 +++++ front/src/modules/ui/types/PositionType.tsx | 4 ++ 5 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 front/src/modules/ui/tables/states/contextMenuPositionState.ts create mode 100644 front/src/modules/ui/types/PositionType.tsx diff --git a/front/src/modules/ui/components/table/CheckboxCell.tsx b/front/src/modules/ui/components/table/CheckboxCell.tsx index 531ca1b7b..10ec08327 100644 --- a/front/src/modules/ui/components/table/CheckboxCell.tsx +++ b/front/src/modules/ui/components/table/CheckboxCell.tsx @@ -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); diff --git a/front/src/modules/ui/components/table/EntityTable.tsx b/front/src/modules/ui/components/table/EntityTable.tsx index fa6ed683e..478d6c24d 100644 --- a/front/src/modules/ui/components/table/EntityTable.tsx +++ b/front/src/modules/ui/components/table/EntityTable.tsx @@ -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 ( {table.getRowModel().rows.map((row, index) => ( - + {row.getVisibleCells().map((cell) => { return ( - + + handleContextMenu(event, row.original.id) + } + > {flexRender( cell.column.columnDef.cell, cell.getContext(), @@ -170,7 +196,7 @@ export function EntityTable< ); })} - + ))} diff --git a/front/src/modules/ui/components/table/action-bar/EntityTableActionBar.tsx b/front/src/modules/ui/components/table/action-bar/EntityTableActionBar.tsx index 545859338..9025187e9 100644 --- a/front/src/modules/ui/components/table/action-bar/EntityTableActionBar.tsx +++ b/front/src/modules/ui/components/table/action-bar/EntityTableActionBar.tsx @@ -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` 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 {children}; + return ( + + {children} + + ); } diff --git a/front/src/modules/ui/tables/states/contextMenuPositionState.ts b/front/src/modules/ui/tables/states/contextMenuPositionState.ts new file mode 100644 index 000000000..48561a2cf --- /dev/null +++ b/front/src/modules/ui/tables/states/contextMenuPositionState.ts @@ -0,0 +1,11 @@ +import { atom } from 'recoil'; + +import { PositionType } from '@/ui/types/PositionType'; + +export const contextMenuPositionState = atom({ + key: 'contextMenuPositionState', + default: { + x: null, + y: null, + }, +}); diff --git a/front/src/modules/ui/types/PositionType.tsx b/front/src/modules/ui/types/PositionType.tsx new file mode 100644 index 000000000..46bc3c540 --- /dev/null +++ b/front/src/modules/ui/types/PositionType.tsx @@ -0,0 +1,4 @@ +export interface PositionType { + x: number | null; + y: number | null; +}