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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 705 additions and 92 deletions

View File

@ -0,0 +1,31 @@
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
import { useSetEntityTableData } from '../hooks/useSetEntityTableData';
import { defaultOrderBy } from '../queries';
export function GenericEntityTableData({
useGetRequest,
getRequestResultKey,
orderBy = defaultOrderBy,
whereFilters,
fieldMetadataArray,
}: {
useGetRequest: any;
getRequestResultKey: string;
orderBy?: any;
whereFilters?: any;
fieldMetadataArray: EntityFieldMetadata[];
}) {
const setEntityTableData = useSetEntityTableData();
useGetRequest({
variables: { orderBy, where: whereFilters },
onCompleted: (data: any) => {
const entities = data[getRequestResultKey] ?? [];
setEntityTableData(entities, fieldMetadataArray);
},
});
return <></>;
}

View File

@ -1,7 +1,3 @@
import { useRecoilState } from 'recoil';
import { isFetchingEntityTableDataState } from '@/ui/table/states/isFetchingEntityTableDataState';
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import {
PersonOrderByWithRelationInput,
useGetPeopleQuery,
@ -17,12 +13,6 @@ export function PeopleEntityTableData({
orderBy?: PersonOrderByWithRelationInput[];
whereFilters?: any;
}) {
const [, setTableRowIds] = useRecoilState(tableRowIdsState);
const [, setIsFetchingEntityTableData] = useRecoilState(
isFetchingEntityTableDataState,
);
const setPeopleEntityTable = useSetPeopleEntityTable();
useGetPeopleQuery({
@ -30,19 +20,7 @@ export function PeopleEntityTableData({
onCompleted: (data) => {
const people = data.people ?? [];
const peopleIds = people.map((person) => person.id);
setTableRowIds((currentRowIds) => {
if (JSON.stringify(currentRowIds) !== JSON.stringify(peopleIds)) {
return peopleIds;
}
return currentRowIds;
});
setPeopleEntityTable(people);
setIsFetchingEntityTableData(false);
},
});

View File

@ -0,0 +1,20 @@
import { IconBriefcase, IconMap } from '@tabler/icons-react';
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
export const peopleFieldMetadataArray: EntityFieldMetadata[] = [
{
fieldName: 'city',
label: 'City',
icon: <IconMap size={16} />,
columnSize: 150,
type: 'text',
},
{
fieldName: 'jobTitle',
label: 'Job title',
icon: <IconBriefcase size={16} />,
columnSize: 150,
type: 'text',
},
];

View File

@ -0,0 +1,72 @@
import { useRecoilCallback } from 'recoil';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { entityFieldMetadataArrayState } from '@/ui/table/states/entityFieldMetadataArrayState';
import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState';
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
import { availableFiltersScopedState } from '../../ui/filter-n-sort/states/availableFiltersScopedState';
import { useContextScopeId } from '../../ui/recoil-scope/hooks/useContextScopeId';
import { useResetTableRowSelection } from '../../ui/table/hooks/useResetTableRowSelection';
import { entityTableDimensionsState } from '../../ui/table/states/entityTableDimensionsState';
import { isFetchingEntityTableDataState } from '../../ui/table/states/isFetchingEntityTableDataState';
import { TableContext } from '../../ui/table/states/TableContext';
import { tableRowIdsState } from '../../ui/table/states/tableRowIdsState';
export function useSetEntityTableData() {
const resetTableRowSelection = useResetTableRowSelection();
const tableContextScopeId = useContextScopeId(TableContext);
return useRecoilCallback(
({ set, snapshot }) =>
<T extends { id: string }>(
newEntityArray: T[],
entityFieldMetadataArray: EntityFieldMetadata[],
) => {
for (const entity of newEntityArray) {
const currentEntity = snapshot
.getLoadable(tableEntitiesFamilyState(entity.id))
.valueOrThrow();
if (JSON.stringify(currentEntity) !== JSON.stringify(entity)) {
set(tableEntitiesFamilyState(entity.id), entity);
}
}
const entityIds = newEntityArray.map((entity) => entity.id);
set(tableRowIdsState, (currentRowIds) => {
if (JSON.stringify(currentRowIds) !== JSON.stringify(entityIds)) {
return entityIds;
}
return currentRowIds;
});
resetTableRowSelection();
set(entityTableDimensionsState, {
numberOfColumns: entityFieldMetadataArray.length,
numberOfRows: entityIds.length,
});
const filters = entityFieldMetadataArray.map(
(fieldMetadata) =>
({
field: fieldMetadata.fieldName,
icon: fieldMetadata.filterIcon,
label: fieldMetadata.label,
type: fieldMetadata.type,
} as FilterDefinition),
);
set(availableFiltersScopedState(tableContextScopeId), filters);
set(entityFieldMetadataArrayState, entityFieldMetadataArray);
set(isFetchingEntityTableDataState, false);
},
[],
);
}

View File

@ -0,0 +1,18 @@
import { useUpdateOnePersonMutation } from '~/generated/graphql';
export function useUpdatePeopleField() {
const [updatePeople] = useUpdateOnePersonMutation();
return function updatePeopleField(
peopleId: string,
fieldName: string,
fieldValue: unknown,
) {
updatePeople({
variables: {
where: { id: peopleId },
data: { [fieldName]: fieldValue },
},
});
};
}

View File

@ -0,0 +1,23 @@
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
import { GenericEditableTextCell } from './GenericEditableTextCell';
type OwnProps = {
entityFieldMetadata: EntityFieldMetadata;
};
export function GenericEditableCell({ entityFieldMetadata }: OwnProps) {
switch (entityFieldMetadata.type) {
case 'text':
return (
<GenericEditableTextCell
fieldName={entityFieldMetadata.fieldName}
placeholder={entityFieldMetadata.label}
editModeHorizontalAlign="left"
/>
);
default:
return <></>;
}
}

View File

@ -0,0 +1,44 @@
import { useRecoilValue } from 'recoil';
import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode';
type OwnProps = {
fieldName: string;
editModeHorizontalAlign?: 'left' | 'right';
placeholder?: string;
};
export function GenericEditableTextCell({
fieldName,
editModeHorizontalAlign,
placeholder,
}: OwnProps) {
const currentRowEntityId = useCurrentRowEntityId();
const fieldValue = useRecoilValue<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName,
}),
);
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<GenericEditableTextCellEditMode
fieldName={fieldName}
placeholder={placeholder}
/>
}
nonEditModeContent={
<InplaceInputTextDisplayMode>{fieldValue}</InplaceInputTextDisplayMode>
}
></EditableCell>
);
}

View File

@ -0,0 +1,47 @@
import { useRecoilState } from 'recoil';
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
import { useEntityUpdateFieldHook } from '@/ui/table/hooks/useCellUpdateFieldHook';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
type OwnProps = {
fieldName: string;
placeholder?: string;
};
export function GenericEditableTextCellEditMode({
fieldName,
placeholder,
}: OwnProps) {
const currentRowEntityId = useCurrentRowEntityId();
const [fieldValue, setFieldValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName,
}),
);
const useUpdateField = useEntityUpdateFieldHook();
const updateField = useUpdateField?.();
function handleSubmit(newText: string) {
if (newText === fieldValue) return;
setFieldValue(newText);
if (currentRowEntityId && updateField) {
updateField(currentRowEntityId, fieldName, newText);
}
}
return (
<InplaceInputTextEditMode
placeholder={placeholder ?? ''}
autoFocus
value={fieldValue ?? ''}
onSubmit={handleSubmit}
/>
);
}

View File

@ -0,0 +1,53 @@
import { useCallback, useMemo, useState } from 'react';
import { defaultOrderBy } from '@/companies/queries';
import { GenericEntityTableData } from '@/people/components/GenericEntityTableData';
import { peopleFieldMetadataArray } from '@/people/constants/peopleFieldMetadataArray';
import { useUpdatePeopleField } from '@/people/hooks/useUpdatePeopleField';
import { PeopleSelectedSortType } from '@/people/queries';
import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
import { IconList } from '@/ui/icon';
import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue';
import { EntityTable } from '@/ui/table/components/EntityTableV2';
import { TableContext } from '@/ui/table/states/TableContext';
import {
PersonOrderByWithRelationInput,
useGetPeopleQuery,
} from '~/generated/graphql';
import { availableSorts } from '~/pages/people/people-sorts';
export function PeopleTable() {
const [orderBy, setOrderBy] =
useState<PersonOrderByWithRelationInput[]>(defaultOrderBy);
const updateSorts = useCallback((sorts: Array<PeopleSelectedSortType>) => {
setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy);
}, []);
const filters = useRecoilScopedValue(filtersScopedState, TableContext);
const whereFilters = useMemo(() => {
return { AND: filters.map(turnFilterIntoWhereClause) };
}, [filters]) as any;
return (
<>
<GenericEntityTableData
getRequestResultKey="people"
useGetRequest={useGetPeopleQuery}
orderBy={orderBy}
whereFilters={whereFilters}
fieldMetadataArray={peopleFieldMetadataArray}
/>
<EntityTable
viewName="All People"
viewIcon={<IconList size={16} />}
availableSorts={availableSorts}
onSortsUpdate={updateSorts}
useUpdateField={useUpdatePeopleField}
/>
</>
);
}

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>
);
}

View File

@ -1,12 +1,10 @@
import { useContext } from 'react';
import { useRecoilCallback } from 'recoil';
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
import { useCloseCurrentCellInEditMode } from '../../hooks/useClearCellInEditMode';
import { CellHotkeyScopeContext } from '../../states/CellHotkeyScopeContext';
import { isSomeInputInEditModeState } from '../../states/isSomeInputInEditModeState';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentCellEditMode } from './useCurrentCellEditMode';
@ -29,33 +27,18 @@ export function useEditableCell() {
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
}
const openEditableCell = useRecoilCallback(
({ snapshot, set }) =>
() => {
const isSomeInputInEditMode = snapshot
.getLoadable(isSomeInputInEditModeState)
.valueOrThrow();
function openEditableCell() {
setCurrentCellInEditMode();
if (!isSomeInputInEditMode) {
set(isSomeInputInEditModeState, true);
setCurrentCellInEditMode();
if (customCellHotkeyScope) {
setHotkeyScope(
customCellHotkeyScope.scope,
customCellHotkeyScope.customScopes,
);
} else {
setHotkeyScope(
DEFAULT_CELL_SCOPE.scope,
DEFAULT_CELL_SCOPE.customScopes,
);
}
}
},
[setCurrentCellInEditMode, setHotkeyScope, customCellHotkeyScope],
);
if (customCellHotkeyScope) {
setHotkeyScope(
customCellHotkeyScope.scope,
customCellHotkeyScope.customScopes,
);
} else {
setHotkeyScope(DEFAULT_CELL_SCOPE.scope, DEFAULT_CELL_SCOPE.customScopes);
}
}
return {
closeEditableCell,

View File

@ -0,0 +1,7 @@
import { useContext } from 'react';
import { EntityUpdateFieldHookContext } from '../states/EntityUpdateFieldHookContext';
export function useEntityUpdateFieldHook() {
return useContext(EntityUpdateFieldHookContext);
}

View File

@ -2,7 +2,6 @@ import { useRecoilCallback } from 'recoil';
import { currentCellInEditModePositionState } from '../states/currentCellInEditModePositionState';
import { isCellInEditModeFamilyState } from '../states/isCellInEditModeFamilyState';
import { isSomeInputInEditModeState } from '../states/isSomeInputInEditModeState';
export function useCloseCurrentCellInEditMode() {
return useRecoilCallback(({ set, snapshot }) => {
@ -12,11 +11,6 @@ export function useCloseCurrentCellInEditMode() {
);
set(isCellInEditModeFamilyState(currentCellInEditModePosition), false);
// TODO: find a way to remove this
await new Promise((resolve) => setTimeout(resolve, 20));
set(isSomeInputInEditModeState, false);
};
}, []);
}

View File

@ -4,7 +4,6 @@ import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
import { currentHotkeyScopeState } from '@/ui/hotkey/states/internal/currentHotkeyScopeState';
import { isSoftFocusActiveState } from '../states/isSoftFocusActiveState';
import { isSomeInputInEditModeState } from '../states/isSomeInputInEditModeState';
import { TableHotkeyScope } from '../types/TableHotkeyScope';
import { useCloseCurrentCellInEditMode } from './useClearCellInEditMode';
@ -23,19 +22,11 @@ export function useLeaveTableFocus() {
.getLoadable(isSoftFocusActiveState)
.valueOrThrow();
const isSomeInputInEditMode = snapshot
.getLoadable(isSomeInputInEditModeState)
.valueOrThrow();
const currentHotkeyScope = snapshot
.getLoadable(currentHotkeyScopeState)
.valueOrThrow();
if (isSomeInputInEditMode) {
return;
}
if (!isSoftFocusActive && !isSomeInputInEditMode) {
if (!isSoftFocusActive) {
return;
}

View File

@ -1,10 +1,8 @@
import { useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
import { isSomeInputInEditModeState } from '../states/isSomeInputInEditModeState';
import { TableHotkeyScope } from '../types/TableHotkeyScope';
import { useDisableSoftFocus } from './useDisableSoftFocus';
@ -16,50 +14,40 @@ export function useMapKeyboardToSoftFocus() {
const disableSoftFocus = useDisableSoftFocus();
const setHotkeyScope = useSetHotkeyScope();
const [isSomeInputInEditMode] = useRecoilState(isSomeInputInEditModeState);
useScopedHotkeys(
[Key.ArrowUp, `${Key.Shift}+${Key.Enter}`],
() => {
if (!isSomeInputInEditMode) {
moveUp();
}
moveUp();
},
TableHotkeyScope.TableSoftFocus,
[moveUp, isSomeInputInEditMode],
[moveUp],
);
useScopedHotkeys(
Key.ArrowDown,
() => {
if (!isSomeInputInEditMode) {
moveDown();
}
moveDown();
},
TableHotkeyScope.TableSoftFocus,
[moveDown, isSomeInputInEditMode],
[moveDown],
);
useScopedHotkeys(
[Key.ArrowLeft, `${Key.Shift}+${Key.Tab}`],
() => {
if (!isSomeInputInEditMode) {
moveLeft();
}
moveLeft();
},
TableHotkeyScope.TableSoftFocus,
[moveLeft, isSomeInputInEditMode],
[moveLeft],
);
useScopedHotkeys(
[Key.ArrowRight, Key.Tab],
() => {
if (!isSomeInputInEditMode) {
moveRight();
}
moveRight();
},
TableHotkeyScope.TableSoftFocus,
[moveRight, isSomeInputInEditMode],
[moveRight],
);
useScopedHotkeys(

View File

@ -0,0 +1,6 @@
import { createContext } from 'react';
import { EntityFieldMetadata } from '../types/EntityFieldMetadata';
export const EntityFieldMetadataContext =
createContext<EntityFieldMetadata | null>(null);

View File

@ -0,0 +1,6 @@
import { createContext } from 'react';
import { EntityUpdateFieldHook } from '../types/CellUpdateFieldHook';
export const EntityUpdateFieldHookContext =
createContext<EntityUpdateFieldHook | null>(null);

View File

@ -0,0 +1,8 @@
import { atom } from 'recoil';
import { EntityFieldMetadata } from '../types/EntityFieldMetadata';
export const entityFieldMetadataArrayState = atom<EntityFieldMetadata[]>({
key: 'entityFieldMetadataArrayState',
default: [],
});

View File

@ -1,6 +0,0 @@
import { atom } from 'recoil';
export const isSomeInputInEditModeState = atom<boolean>({
key: 'isSomeInputInEditModeState',
default: false,
});

View File

@ -0,0 +1,9 @@
import { atomFamily } from 'recoil';
export const tableEntitiesFamilyState = atomFamily<
Record<string, unknown> | null,
string
>({
key: 'tableEntitiesFamilyState',
default: null,
});

View File

@ -0,0 +1,18 @@
import { selectorFamily } from 'recoil';
import { tableEntitiesFamilyState } from './tableEntitiesFamilyState';
export const tableEntityFieldFamilySelector = selectorFamily({
key: 'tableEntityFieldFamilySelector',
get:
<T>({ fieldName, entityId }: { fieldName: string; entityId: string }) =>
({ get }) =>
get(tableEntitiesFamilyState(entityId))?.[fieldName] as T,
set:
<T>({ fieldName, entityId }: { fieldName: string; entityId: string }) =>
({ set }, newValue: T) =>
set(tableEntitiesFamilyState(entityId), (prevState) => ({
...prevState,
[fieldName]: newValue,
})),
});

View File

@ -0,0 +1,5 @@
export type EntityUpdateFieldHook = () => <T>(
entityId: string,
fieldName: string,
value: T,
) => void | Promise<void>;

View File

@ -0,0 +1,16 @@
export type EntityFieldType =
| 'text'
| 'number'
| 'date'
| 'select'
| 'checkbox'
| 'icon';
export type EntityFieldMetadata = {
fieldName: string;
label: string;
type: EntityFieldType;
icon: JSX.Element;
columnSize: number;
filterIcon?: JSX.Element;
};