Feat/improve editable cell (#959)

* Removed isSomeInputInEditMode

* Removed console.log

* Added a first version of generic cell text

* Removed metadata from entity table  V1

* Fix

* Fix

* Fix
This commit is contained in:
Lucas Bordeau
2023-07-27 07:53:57 +02:00
committed by GitHub
parent 13f415a859
commit 011d9e840f
27 changed files with 705 additions and 92 deletions

View File

@ -0,0 +1,36 @@
import { useRecoilValue } from 'recoil';
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
import { isFetchingEntityTableDataState } from '../states/isFetchingEntityTableDataState';
import { RowIdContext } from '../states/RowIdContext';
import { RowIndexContext } from '../states/RowIndexContext';
import { tableRowIdsState } from '../states/tableRowIdsState';
import { EntityTableRow } from './EntityTableRowV2';
export function EntityTableBody() {
const rowIds = useRecoilValue(tableRowIdsState);
const isNavbarSwitchingSize = useRecoilValue(isNavbarSwitchingSizeState);
const isFetchingEntityTableData = useRecoilValue(
isFetchingEntityTableDataState,
);
if (isFetchingEntityTableData || isNavbarSwitchingSize) {
return null;
}
return (
<tbody>
{rowIds.map((rowId, index) => (
<RowIdContext.Provider value={rowId} key={rowId}>
<RowIndexContext.Provider value={index}>
<EntityTableRow rowId={rowId} />
</RowIndexContext.Provider>
</RowIdContext.Provider>
))}
</tbody>
);
}

View File

@ -0,0 +1,50 @@
import { useContext } from 'react';
import { useSetRecoilState } from 'recoil';
import { GenericEditableCell } from '@/people/table/components/GenericEditableCell';
import { RecoilScope } from '../../recoil-scope/components/RecoilScope';
import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected';
import { ColumnIndexContext } from '../states/ColumnIndexContext';
import { contextMenuPositionState } from '../states/contextMenuPositionState';
import { EntityFieldMetadataContext } from '../states/EntityFieldMetadataContext';
export function EntityTableCell({ cellIndex }: { cellIndex: number }) {
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
const { setCurrentRowSelected } = useCurrentRowSelected();
function handleContextMenu(event: React.MouseEvent) {
event.preventDefault();
setCurrentRowSelected(true);
setContextMenuPosition({
x: event.clientX,
y: event.clientY,
});
}
const entityFieldMetadata = useContext(EntityFieldMetadataContext);
if (!entityFieldMetadata) {
return null;
}
return (
<RecoilScope>
<ColumnIndexContext.Provider value={cellIndex}>
<td
onContextMenu={(event) => handleContextMenu(event)}
style={{
width: entityFieldMetadata.columnSize,
minWidth: entityFieldMetadata.columnSize,
maxWidth: entityFieldMetadata.columnSize,
}}
>
<GenericEditableCell entityFieldMetadata={entityFieldMetadata} />
</td>
</ColumnIndexContext.Provider>
</RecoilScope>
);
}

View File

@ -0,0 +1,42 @@
import { useRecoilValue } from 'recoil';
import { entityFieldMetadataArrayState } from '../states/entityFieldMetadataArrayState';
import { ColumnHead } from './ColumnHead';
import { SelectAllCheckbox } from './SelectAllCheckbox';
export function EntityTableHeader() {
const fieldMetadataArray = useRecoilValue(entityFieldMetadataArrayState);
return (
<thead>
<tr>
<th
style={{
width: 30,
minWidth: 30,
maxWidth: 30,
}}
>
<SelectAllCheckbox />
</th>
{fieldMetadataArray.map((fieldMetadata) => (
<th
key={fieldMetadata.fieldName.toString()}
style={{
width: fieldMetadata.columnSize,
minWidth: fieldMetadata.columnSize,
maxWidth: fieldMetadata.columnSize,
}}
>
<ColumnHead
viewName={fieldMetadata.label}
viewIcon={fieldMetadata.icon}
/>
</th>
))}
<th></th>
</tr>
</thead>
);
}

View File

@ -0,0 +1,38 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { entityFieldMetadataArrayState } from '../states/entityFieldMetadataArrayState';
import { EntityFieldMetadataContext } from '../states/EntityFieldMetadataContext';
import { CheckboxCell } from './CheckboxCell';
import { EntityTableCell } from './EntityTableCellV2';
const StyledRow = styled.tr<{ selected: boolean }>`
background: ${(props) =>
props.selected ? props.theme.background.secondary : 'none'};
`;
export function EntityTableRow({ rowId }: { rowId: string }) {
const entityFieldMetadataArray = useRecoilValue(
entityFieldMetadataArrayState,
);
return (
<StyledRow data-testid={`row-id-${rowId}`} selected={false}>
<td>
<CheckboxCell />
</td>
{entityFieldMetadataArray.map((entityFieldMetadata, columnIndex) => {
return (
<EntityFieldMetadataContext.Provider
value={entityFieldMetadata}
key={entityFieldMetadata.fieldName}
>
<EntityTableCell cellIndex={columnIndex} />
</EntityFieldMetadataContext.Provider>
);
})}
<td></td>
</StyledRow>
);
}

View File

@ -0,0 +1,136 @@
import * as React from 'react';
import styled from '@emotion/styled';
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
import { EntityUpdateFieldHookContext } from '../states/EntityUpdateFieldHookContext';
import { TableHeader } from '../table-header/components/TableHeader';
import { EntityUpdateFieldHook } from '../types/CellUpdateFieldHook';
import { EntityTableBody } from './EntityTableBodyV2';
import { EntityTableHeader } from './EntityTableHeaderV2';
const StyledTable = styled.table`
border-collapse: collapse;
border-radius: ${({ theme }) => theme.border.radius.sm};
border-spacing: 0;
margin-left: ${({ theme }) => theme.table.horizontalCellMargin};
margin-right: ${({ theme }) => theme.table.horizontalCellMargin};
table-layout: fixed;
width: calc(100% - ${({ theme }) => theme.table.horizontalCellMargin} * 2);
th {
border: 1px solid ${({ theme }) => theme.border.color.light};
border-collapse: collapse;
color: ${({ theme }) => theme.font.color.tertiary};
padding: 0;
text-align: left;
:last-child {
border-right-color: transparent;
}
:first-of-type {
border-left-color: transparent;
border-right-color: transparent;
}
:last-of-type {
min-width: 0;
width: 100%;
}
}
td {
border: 1px solid ${({ theme }) => theme.border.color.light};
border-collapse: collapse;
color: ${({ theme }) => theme.font.color.primary};
padding: 0;
text-align: left;
:last-child {
border-right-color: transparent;
}
:first-of-type {
border-left-color: transparent;
border-right-color: transparent;
}
:last-of-type {
min-width: 0;
width: 100%;
}
}
`;
const StyledTableWithHeader = styled.div`
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
`;
const StyledTableContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
`;
const StyledTableWrapper = styled.div`
flex: 1;
overflow: auto;
`;
type OwnProps<SortField> = {
viewName: string;
viewIcon?: React.ReactNode;
availableSorts?: Array<SortType<SortField>>;
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
onRowSelectionChange?: (rowSelection: string[]) => void;
useUpdateField: EntityUpdateFieldHook;
};
export function EntityTable<SortField>({
viewName,
viewIcon,
availableSorts,
onSortsUpdate,
useUpdateField,
}: OwnProps<SortField>) {
const tableBodyRef = React.useRef<HTMLDivElement>(null);
useMapKeyboardToSoftFocus();
const leaveTableFocus = useLeaveTableFocus();
useListenClickOutside({
refs: [tableBodyRef],
callback: () => {
leaveTableFocus();
},
});
return (
<EntityUpdateFieldHookContext.Provider value={useUpdateField}>
<StyledTableWithHeader>
<StyledTableContainer ref={tableBodyRef}>
<TableHeader
viewName={viewName}
viewIcon={viewIcon}
availableSorts={availableSorts}
onSortsUpdate={onSortsUpdate}
/>
<StyledTableWrapper>
<StyledTable>
<EntityTableHeader />
<EntityTableBody />
</StyledTable>
</StyledTableWrapper>
</StyledTableContainer>
</StyledTableWithHeader>
</EntityUpdateFieldHookContext.Provider>
);
}