Add tab hotkey on table page (#457)
* wip * wip * - Added scopes on useHotkeys - Use new EditableCellV2 - Implemented Recoil Scoped State with specific context - Implemented soft focus position - Factorized open/close editable cell - Removed editable relation old components - Broke down entity table into multiple components - Added Recoil Scope by CellContext - Added Recoil Scope by RowContext * First working version * Use a new EditableCellSoftFocusMode * Fixed initialize soft focus * Fixed enter mode * Added TODO * Fix * Fixes * Fix tests * Fix lint * Fixes --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import { PersonChip } from '@/people/components/PersonChip';
|
||||
import { EditableCellV2 } from '@/ui/components/editable-cell/EditableCellV2';
|
||||
import { EditableCell } from '@/ui/components/editable-cell/EditableCell';
|
||||
import { Company, User } from '~/generated/graphql';
|
||||
|
||||
import { CompanyAccountOwnerPicker } from './CompanyAccountOwnerPicker';
|
||||
@ -12,7 +12,7 @@ export type OwnProps = {
|
||||
|
||||
export function CompanyAccountOwnerCell({ company }: OwnProps) {
|
||||
return (
|
||||
<EditableCellV2
|
||||
<EditableCell
|
||||
editModeContent={<CompanyAccountOwnerPicker company={company} />}
|
||||
nonEditModeContent={
|
||||
company.accountOwner?.displayName ? (
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
|
||||
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
|
||||
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||
import { EntityForSelect } from '@/relation-picker/types/EntityForSelect';
|
||||
import { Entity } from '@/relation-picker/types/EntityTypeForSelect';
|
||||
import { useCloseEditableCell } from '@/ui/components/editable-cell/hooks/useCloseEditableCell';
|
||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
||||
import { useEditableCell } from '@/ui/components/editable-cell/hooks/useCloseEditableCell';
|
||||
import {
|
||||
Company,
|
||||
User,
|
||||
@ -28,7 +28,7 @@ export function CompanyAccountOwnerPicker({ company }: OwnProps) {
|
||||
);
|
||||
const [updateCompany] = useUpdateCompanyMutation();
|
||||
|
||||
const closeEditableCell = useCloseEditableCell();
|
||||
const { closeEditableCell } = useEditableCell();
|
||||
|
||||
const companies = useFilteredSearchEntityQuery({
|
||||
queryHook: useSearchUserQuery,
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const captureHotkeyTypeInFocusState = atom<boolean>({
|
||||
key: 'captureHotkeyTypeInFocusState',
|
||||
default: false,
|
||||
});
|
||||
@ -1,6 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const pendingHotkeyState = atom<string | null>({
|
||||
key: 'command-menu/pendingHotkeyState',
|
||||
key: 'pendingHotkeyState',
|
||||
default: null,
|
||||
});
|
||||
|
||||
@ -2,9 +2,9 @@ import { useCallback, useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
|
||||
import { Column } from '@/ui/components/board/Board';
|
||||
import { NewButton as UINewButton } from '@/ui/components/board/NewButton';
|
||||
import { RecoilScope } from '@/ui/hooks/RecoilScope';
|
||||
import {
|
||||
Company,
|
||||
PipelineProgressableType,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
|
||||
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
|
||||
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
||||
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
||||
import {
|
||||
CommentableType,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import CompanyChip from '@/companies/components/CompanyChip';
|
||||
import { EditableCellV2 } from '@/ui/components/editable-cell/EditableCellV2';
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { EditableCell } from '@/ui/components/editable-cell/EditableCell';
|
||||
import { isCreateModeScopedState } from '@/ui/components/editable-cell/states/isCreateModeScopedState';
|
||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
||||
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
||||
import { Company, Person } from '~/generated/graphql';
|
||||
|
||||
@ -18,7 +18,7 @@ export function PeopleCompanyCell({ people }: OwnProps) {
|
||||
const [isCreating] = useRecoilScopedState(isCreateModeScopedState);
|
||||
|
||||
return (
|
||||
<EditableCellV2
|
||||
<EditableCell
|
||||
editModeContent={
|
||||
isCreating ? (
|
||||
<PeopleCompanyCreateCell people={people} />
|
||||
|
||||
@ -2,11 +2,11 @@ import { useRef, useState } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||
import { isCreateModeScopedState } from '@/ui/components/editable-cell/states/isCreateModeScopedState';
|
||||
import { DoubleTextInput } from '@/ui/components/inputs/DoubleTextInput';
|
||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
||||
import { logError } from '@/utils/logs/logError';
|
||||
import {
|
||||
Person,
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
|
||||
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
|
||||
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||
import { useCloseEditableCell } from '@/ui/components/editable-cell/hooks/useCloseEditableCell';
|
||||
import { useEditableCell } from '@/ui/components/editable-cell/hooks/useCloseEditableCell';
|
||||
import { isCreateModeScopedState } from '@/ui/components/editable-cell/states/isCreateModeScopedState';
|
||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
||||
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
||||
import {
|
||||
CommentableType,
|
||||
@ -25,7 +25,7 @@ export function PeopleCompanyPicker({ people }: OwnProps) {
|
||||
);
|
||||
const [updatePeople] = useUpdatePeopleMutation();
|
||||
|
||||
const closeEditableCell = useCloseEditableCell();
|
||||
const { closeEditableCell } = useEditableCell();
|
||||
|
||||
const companies = useFilteredSearchEntityQuery({
|
||||
queryHook: useSearchCompanyQuery,
|
||||
|
||||
24
front/src/modules/recoil-scope/components/RecoilScope.tsx
Normal file
24
front/src/modules/recoil-scope/components/RecoilScope.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { Context, useRef } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { RecoilScopeContext } from '../states/RecoilScopeContext';
|
||||
|
||||
export function RecoilScope({
|
||||
SpecificContext,
|
||||
children,
|
||||
}: {
|
||||
SpecificContext?: Context<string | null>;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const currentScopeId = useRef(v4());
|
||||
|
||||
return SpecificContext ? (
|
||||
<SpecificContext.Provider value={currentScopeId.current}>
|
||||
{children}
|
||||
</SpecificContext.Provider>
|
||||
) : (
|
||||
<RecoilScopeContext.Provider value={currentScopeId.current}>
|
||||
{children}
|
||||
</RecoilScopeContext.Provider>
|
||||
);
|
||||
}
|
||||
20
front/src/modules/recoil-scope/hooks/useRecoilScopedState.ts
Normal file
20
front/src/modules/recoil-scope/hooks/useRecoilScopedState.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Context, useContext } from 'react';
|
||||
import { RecoilState, useRecoilState } from 'recoil';
|
||||
|
||||
import { RecoilScopeContext } from '../states/RecoilScopeContext';
|
||||
|
||||
export function useRecoilScopedState<StateType>(
|
||||
recoilState: (param: string) => RecoilState<StateType>,
|
||||
SpecificContext?: Context<string | null>,
|
||||
) {
|
||||
const recoilScopeId = useContext(SpecificContext ?? RecoilScopeContext);
|
||||
|
||||
if (!recoilScopeId)
|
||||
throw new Error(
|
||||
`Using a scoped atom without a RecoilScope : ${
|
||||
recoilState('').key
|
||||
}, verify that you are using a RecoilScope with a specific context if you intended to do so.`,
|
||||
);
|
||||
|
||||
return useRecoilState<StateType>(recoilState(recoilScopeId));
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { useContext } from 'react';
|
||||
import { RecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { RecoilScopeContext } from './RecoilScopeContext';
|
||||
import { RecoilScopeContext } from '../states/RecoilScopeContext';
|
||||
|
||||
export function useRecoilScopedValue<T>(
|
||||
recoilState: (param: string) => RecoilState<T>,
|
||||
@ -3,7 +3,7 @@ import { debounce } from 'lodash';
|
||||
import scrollIntoView from 'scroll-into-view';
|
||||
|
||||
import { useUpDownHotkeys } from '@/hotkeys/hooks/useUpDownHotkeys';
|
||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
|
||||
import { relationPickerSearchFilterScopedState } from '../states/relationPickerSearchFilterScopedState';
|
||||
import { EntityForSelect } from '../types/EntityForSelect';
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
import { ReactElement } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { isSomeInputInEditModeState } from '../../tables/states/isSomeInputInEditModeState';
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
|
||||
import { useEditableCell } from './hooks/useCloseEditableCell';
|
||||
import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell';
|
||||
import { useSetSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell';
|
||||
import { isEditModeScopedState } from './states/isEditModeScopedState';
|
||||
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
||||
import { EditableCellEditMode } from './EditableCellEditMode';
|
||||
import { EditableCellSoftFocusMode } from './EditableCellSoftFocusMode';
|
||||
|
||||
export const CellBaseContainer = styled.div`
|
||||
align-items: center;
|
||||
@ -23,43 +27,48 @@ type OwnProps = {
|
||||
nonEditModeContent: ReactElement;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
editModeVerticalPosition?: 'over' | 'below';
|
||||
isEditMode?: boolean;
|
||||
isCreateMode?: boolean;
|
||||
onOutsideClick?: () => void;
|
||||
onInsideClick?: () => void;
|
||||
};
|
||||
|
||||
export function EditableCell({
|
||||
editModeContent,
|
||||
nonEditModeContent,
|
||||
editModeHorizontalAlign = 'left',
|
||||
editModeVerticalPosition = 'over',
|
||||
isEditMode = false,
|
||||
onOutsideClick,
|
||||
onInsideClick,
|
||||
editModeContent,
|
||||
nonEditModeContent,
|
||||
}: OwnProps) {
|
||||
const [isSomeInputInEditMode, setIsSomeInputInEditMode] = useRecoilState(
|
||||
isSomeInputInEditModeState,
|
||||
);
|
||||
const [isEditMode] = useRecoilScopedState(isEditModeScopedState);
|
||||
|
||||
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
|
||||
|
||||
const { closeEditableCell, openEditableCell } = useEditableCell();
|
||||
|
||||
// TODO: we might have silent problematic behavior because of the setTimeout in openEditableCell, investigate
|
||||
// Maybe we could build a switchEditableCell to handle the case where we go from one cell to another.
|
||||
// See https://github.com/twentyhq/twenty/issues/446
|
||||
function handleOnClick() {
|
||||
if (!isSomeInputInEditMode) {
|
||||
onInsideClick?.();
|
||||
setIsSomeInputInEditMode(true);
|
||||
}
|
||||
openEditableCell();
|
||||
setSoftFocusOnCurrentCell();
|
||||
}
|
||||
|
||||
function handleOnOutsideClick() {
|
||||
closeEditableCell();
|
||||
}
|
||||
|
||||
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
||||
|
||||
return (
|
||||
<CellBaseContainer onClick={handleOnClick}>
|
||||
{isEditMode ? (
|
||||
<EditableCellEditMode
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeVerticalPosition={editModeVerticalPosition}
|
||||
isEditMode={isEditMode}
|
||||
onOutsideClick={onOutsideClick}
|
||||
onOutsideClick={handleOnOutsideClick}
|
||||
>
|
||||
{editModeContent}
|
||||
</EditableCellEditMode>
|
||||
) : hasSoftFocus ? (
|
||||
<EditableCellSoftFocusMode>
|
||||
{nonEditModeContent}
|
||||
</EditableCellSoftFocusMode>
|
||||
) : (
|
||||
<EditableCellDisplayMode>{nonEditModeContent}</EditableCellDisplayMode>
|
||||
)}
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import { ReactElement } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const EditableCellNormalModeOuterContainer = styled.div`
|
||||
import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell';
|
||||
|
||||
type Props = {
|
||||
softFocus: boolean;
|
||||
};
|
||||
|
||||
export const EditableCellNormalModeOuterContainer = styled.div<Props>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
@ -11,17 +16,12 @@ export const EditableCellNormalModeOuterContainer = styled.div`
|
||||
padding-right: ${({ theme }) => theme.spacing(1)};
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
-moz-box-shadow: inset 0 0 0 1px
|
||||
${({ theme }) => theme.font.color.extraLight};
|
||||
|
||||
-webkit-box-shadow: inset 0 0 0 1px
|
||||
${({ theme }) => theme.font.color.extraLight};
|
||||
background: ${({ theme }) => theme.background.transparent.secondary};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
|
||||
box-shadow: inset 0 0 0 1px ${({ theme }) => theme.font.color.extraLight};
|
||||
}
|
||||
${(props) =>
|
||||
props.softFocus
|
||||
? `background: ${props.theme.background.transparent.secondary};
|
||||
border-radius: ${props.theme.border.radius.md};
|
||||
box-shadow: inset 0 0 0 1px ${props.theme.grayScale.gray30};`
|
||||
: ''}
|
||||
`;
|
||||
|
||||
export const EditableCellNormalModeInnerContainer = styled.div`
|
||||
@ -32,13 +32,13 @@ export const EditableCellNormalModeInnerContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
children: ReactElement;
|
||||
};
|
||||
export function EditableCellDisplayMode({
|
||||
children,
|
||||
}: React.PropsWithChildren<unknown>) {
|
||||
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
||||
|
||||
export function EditableCellDisplayMode({ children }: OwnProps) {
|
||||
return (
|
||||
<EditableCellNormalModeOuterContainer>
|
||||
<EditableCellNormalModeOuterContainer softFocus={hasSoftFocus}>
|
||||
<EditableCellNormalModeInnerContainer>
|
||||
{children}
|
||||
</EditableCellNormalModeInnerContainer>
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { ReactElement, useMemo, useRef } from 'react';
|
||||
import { ReactElement, useRef } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||
import { useMoveSoftFocus } from '@/ui/tables/hooks/useMoveSoftFocus';
|
||||
import { overlayBackground } from '@/ui/themes/effects';
|
||||
import { debounce } from '@/utils/debounce';
|
||||
|
||||
import { useListenClickOutsideArrayOfRef } from '../../hooks/useListenClickOutsideArrayOfRef';
|
||||
import { isSomeInputInEditModeState } from '../../tables/states/isSomeInputInEditModeState';
|
||||
import { useEditableCell } from './hooks/useCloseEditableCell';
|
||||
|
||||
export const EditableCellEditModeContainer = styled.div<OwnProps>`
|
||||
align-items: center;
|
||||
@ -32,67 +31,77 @@ type OwnProps = {
|
||||
children: ReactElement;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
editModeVerticalPosition?: 'over' | 'below';
|
||||
isEditMode?: boolean;
|
||||
onOutsideClick?: () => void;
|
||||
onInsideClick?: () => void;
|
||||
};
|
||||
|
||||
export function EditableCellEditMode({
|
||||
editModeHorizontalAlign,
|
||||
editModeVerticalPosition,
|
||||
children,
|
||||
isEditMode,
|
||||
onOutsideClick,
|
||||
}: OwnProps) {
|
||||
const wrapperRef = useRef(null);
|
||||
|
||||
const [, setIsSomeInputInEditMode] = useRecoilState(
|
||||
isSomeInputInEditModeState,
|
||||
);
|
||||
|
||||
const debouncedSetIsSomeInputInEditMode = useMemo(() => {
|
||||
return debounce(setIsSomeInputInEditMode, 20);
|
||||
}, [setIsSomeInputInEditMode]);
|
||||
const { closeEditableCell } = useEditableCell();
|
||||
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
|
||||
|
||||
useListenClickOutsideArrayOfRef([wrapperRef], () => {
|
||||
if (isEditMode) {
|
||||
debouncedSetIsSomeInputInEditMode(false);
|
||||
onOutsideClick?.();
|
||||
}
|
||||
onOutsideClick?.();
|
||||
});
|
||||
|
||||
useHotkeys(
|
||||
'esc',
|
||||
() => {
|
||||
if (isEditMode) {
|
||||
onOutsideClick?.();
|
||||
|
||||
debouncedSetIsSomeInputInEditMode(false);
|
||||
}
|
||||
},
|
||||
{
|
||||
preventDefault: true,
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
},
|
||||
[isEditMode, onOutsideClick, debouncedSetIsSomeInputInEditMode],
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'enter',
|
||||
() => {
|
||||
if (isEditMode) {
|
||||
onOutsideClick?.();
|
||||
|
||||
debouncedSetIsSomeInputInEditMode(false);
|
||||
}
|
||||
closeEditableCell();
|
||||
moveDown();
|
||||
},
|
||||
{
|
||||
preventDefault: true,
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
preventDefault: true,
|
||||
},
|
||||
[isEditMode, onOutsideClick, debouncedSetIsSomeInputInEditMode],
|
||||
[closeEditableCell],
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'esc',
|
||||
() => {
|
||||
closeEditableCell();
|
||||
},
|
||||
{
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
preventDefault: true,
|
||||
},
|
||||
[closeEditableCell],
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'tab',
|
||||
() => {
|
||||
closeEditableCell();
|
||||
moveRight();
|
||||
},
|
||||
{
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
preventDefault: true,
|
||||
},
|
||||
[closeEditableCell, moveRight],
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'shift+tab',
|
||||
() => {
|
||||
closeEditableCell();
|
||||
moveLeft();
|
||||
},
|
||||
{
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
preventDefault: true,
|
||||
},
|
||||
[closeEditableCell, moveRight],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
|
||||
import { isNonTextWritingKey } from '@/utils/hotkeys/isNonTextWritingKey';
|
||||
|
||||
import { useEditableCell } from './hooks/useCloseEditableCell';
|
||||
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
||||
|
||||
export function EditableCellSoftFocusMode({
|
||||
children,
|
||||
}: React.PropsWithChildren<unknown>) {
|
||||
const { closeEditableCell, openEditableCell } = useEditableCell();
|
||||
const [captureHotkeyTypeInFocus] = useRecoilState(
|
||||
captureHotkeyTypeInFocusState,
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'enter',
|
||||
() => {
|
||||
openEditableCell();
|
||||
},
|
||||
{
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
preventDefault: true,
|
||||
},
|
||||
[closeEditableCell],
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'*',
|
||||
(keyboardEvent) => {
|
||||
const isWritingText =
|
||||
!isNonTextWritingKey(keyboardEvent.key) &&
|
||||
!keyboardEvent.ctrlKey &&
|
||||
!keyboardEvent.metaKey;
|
||||
|
||||
if (!isWritingText) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (captureHotkeyTypeInFocus) {
|
||||
return;
|
||||
}
|
||||
openEditableCell();
|
||||
},
|
||||
{
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
preventDefault: false,
|
||||
},
|
||||
);
|
||||
|
||||
return <EditableCellDisplayMode>{children}</EditableCellDisplayMode>;
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
import { ReactElement } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
||||
|
||||
import { isSomeInputInEditModeState } from '../../tables/states/isSomeInputInEditModeState';
|
||||
|
||||
import { isEditModeScopedState } from './states/isEditModeScopedState';
|
||||
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
||||
import { EditableCellEditMode } from './EditableCellEditMode';
|
||||
|
||||
export const CellBaseContainer = styled.div`
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 32px;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
editModeContent: ReactElement;
|
||||
nonEditModeContent: ReactElement;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
editModeVerticalPosition?: 'over' | 'below';
|
||||
};
|
||||
|
||||
export function EditableCellV2({
|
||||
editModeHorizontalAlign = 'left',
|
||||
editModeVerticalPosition = 'over',
|
||||
editModeContent,
|
||||
nonEditModeContent,
|
||||
}: OwnProps) {
|
||||
const [isEditMode, setIsEditMode] = useRecoilScopedState(
|
||||
isEditModeScopedState,
|
||||
);
|
||||
const [isSomeInputInEditMode, setIsSomeInputInEditMode] = useRecoilState(
|
||||
isSomeInputInEditModeState,
|
||||
);
|
||||
|
||||
function handleOnClick() {
|
||||
if (!isSomeInputInEditMode) {
|
||||
setIsSomeInputInEditMode(true);
|
||||
setIsEditMode(true);
|
||||
}
|
||||
}
|
||||
|
||||
function handleOnOutsideClick() {
|
||||
setIsEditMode(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<CellBaseContainer onClick={handleOnClick}>
|
||||
{isEditMode ? (
|
||||
<EditableCellEditMode
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeVerticalPosition={editModeVerticalPosition}
|
||||
isEditMode={isEditMode}
|
||||
onOutsideClick={handleOnOutsideClick}
|
||||
>
|
||||
{editModeContent}
|
||||
</EditableCellEditMode>
|
||||
) : (
|
||||
<EditableCellDisplayMode>{nonEditModeContent}</EditableCellDisplayMode>
|
||||
)}
|
||||
</CellBaseContainer>
|
||||
);
|
||||
}
|
||||
@ -1,19 +1,43 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState';
|
||||
|
||||
import { isEditModeScopedState } from '../states/isEditModeScopedState';
|
||||
|
||||
export function useCloseEditableCell() {
|
||||
const [, setIsSomeInputInEditMode] = useRecoilState(
|
||||
isSomeInputInEditModeState,
|
||||
);
|
||||
export function useEditableCell() {
|
||||
const [, setIsEditMode] = useRecoilScopedState(isEditModeScopedState);
|
||||
|
||||
return useCallback(() => {
|
||||
setIsSomeInputInEditMode(false);
|
||||
setIsEditMode(false);
|
||||
}, [setIsEditMode, setIsSomeInputInEditMode]);
|
||||
const closeEditableCell = useRecoilCallback(
|
||||
({ set }) =>
|
||||
async () => {
|
||||
setIsEditMode(false);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
|
||||
set(isSomeInputInEditModeState, false);
|
||||
},
|
||||
[setIsEditMode],
|
||||
);
|
||||
|
||||
const openEditableCell = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
() => {
|
||||
const isSomeInputInEditMode = snapshot
|
||||
.getLoadable(isSomeInputInEditModeState)
|
||||
.valueOrThrow();
|
||||
|
||||
if (!isSomeInputInEditMode) {
|
||||
set(isSomeInputInEditModeState, true);
|
||||
|
||||
setIsEditMode(true);
|
||||
}
|
||||
},
|
||||
[setIsEditMode],
|
||||
);
|
||||
|
||||
return {
|
||||
closeEditableCell,
|
||||
openEditableCell,
|
||||
};
|
||||
}
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { CellContext } from '@/ui/tables/states/CellContext';
|
||||
import { currentColumnNumberScopedState } from '@/ui/tables/states/currentColumnNumberScopedState';
|
||||
import { currentRowNumberScopedState } from '@/ui/tables/states/currentRowNumberScopedState';
|
||||
import { isSoftFocusOnCellFamilyState } from '@/ui/tables/states/isSoftFocusOnCellFamilyState';
|
||||
import { RowContext } from '@/ui/tables/states/RowContext';
|
||||
import { TablePosition } from '@/ui/tables/types/TablePosition';
|
||||
|
||||
export function useIsSoftFocusOnCurrentCell() {
|
||||
const [currentRowNumber] = useRecoilScopedState(
|
||||
currentRowNumberScopedState,
|
||||
RowContext,
|
||||
);
|
||||
|
||||
const [currentColumnNumber] = useRecoilScopedState(
|
||||
currentColumnNumberScopedState,
|
||||
CellContext,
|
||||
);
|
||||
|
||||
const currentTablePosition: TablePosition = useMemo(
|
||||
() => ({
|
||||
column: currentColumnNumber,
|
||||
row: currentRowNumber,
|
||||
}),
|
||||
[currentColumnNumber, currentRowNumber],
|
||||
);
|
||||
|
||||
const isSoftFocusOnCell = useRecoilValue(
|
||||
isSoftFocusOnCellFamilyState(currentTablePosition),
|
||||
);
|
||||
|
||||
return isSoftFocusOnCell;
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useSetSoftFocusPosition } from '@/ui/tables/hooks/useSetSoftFocusPosition';
|
||||
import { CellContext } from '@/ui/tables/states/CellContext';
|
||||
import { currentColumnNumberScopedState } from '@/ui/tables/states/currentColumnNumberScopedState';
|
||||
import { currentRowNumberScopedState } from '@/ui/tables/states/currentRowNumberScopedState';
|
||||
import { RowContext } from '@/ui/tables/states/RowContext';
|
||||
import { TablePosition } from '@/ui/tables/types/TablePosition';
|
||||
|
||||
export function useSetSoftFocusOnCurrentCell() {
|
||||
const setSoftFocusPosition = useSetSoftFocusPosition();
|
||||
const [currentRowNumber] = useRecoilScopedState(
|
||||
currentRowNumberScopedState,
|
||||
RowContext,
|
||||
);
|
||||
|
||||
const [currentColumnNumber] = useRecoilScopedState(
|
||||
currentColumnNumberScopedState,
|
||||
CellContext,
|
||||
);
|
||||
|
||||
const currentTablePosition: TablePosition = useMemo(
|
||||
() => ({
|
||||
column: currentColumnNumber,
|
||||
row: currentRowNumber,
|
||||
}),
|
||||
[currentColumnNumber, currentRowNumber],
|
||||
);
|
||||
|
||||
return useCallback(() => {
|
||||
setSoftFocusPosition(currentTablePosition);
|
||||
}, [setSoftFocusPosition, currentTablePosition]);
|
||||
}
|
||||
@ -50,7 +50,6 @@ function EditableChip({
|
||||
}: EditableChipProps) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
|
||||
const handleRightEndContentClick = (
|
||||
event: React.MouseEvent<HTMLDivElement>,
|
||||
@ -60,9 +59,6 @@ function EditableChip({
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
onOutsideClick={() => setIsEditMode(false)}
|
||||
onInsideClick={() => setIsEditMode(true)}
|
||||
isEditMode={isEditMode}
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<StyledInplaceInput
|
||||
|
||||
@ -38,7 +38,6 @@ export function EditableDate({
|
||||
editModeHorizontalAlign,
|
||||
}: EditableDateProps) {
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
|
||||
type DivProps = React.HTMLProps<HTMLDivElement>;
|
||||
|
||||
@ -60,9 +59,6 @@ export function EditableDate({
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
isEditMode={isEditMode}
|
||||
onOutsideClick={() => setIsEditMode(false)}
|
||||
onInsideClick={() => setIsEditMode(true)}
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<StyledContainer>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeEvent, ReactElement, useRef, useState } from 'react';
|
||||
import { ChangeEvent, ReactElement, useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { textInputStyle } from '@/ui/themes/effects';
|
||||
@ -42,13 +42,9 @@ export function EditableDoubleText({
|
||||
onChange,
|
||||
}: OwnProps) {
|
||||
const firstValueInputRef = useRef<HTMLInputElement>(null);
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
onInsideClick={() => setIsEditMode(true)}
|
||||
onOutsideClick={() => setIsEditMode(false)}
|
||||
isEditMode={isEditMode}
|
||||
editModeContent={
|
||||
<StyledContainer>
|
||||
<StyledEditInplaceInput
|
||||
|
||||
@ -13,10 +13,6 @@ type OwnProps = {
|
||||
changeHandler: (updated: string) => void;
|
||||
};
|
||||
|
||||
type StyledEditModeProps = {
|
||||
isEditMode: boolean;
|
||||
};
|
||||
|
||||
const StyledRawLink = styled(RawLink)`
|
||||
overflow: hidden;
|
||||
|
||||
@ -28,7 +24,7 @@ const StyledRawLink = styled(RawLink)`
|
||||
`;
|
||||
|
||||
// TODO: refactor
|
||||
const StyledEditInplaceInput = styled.input<StyledEditModeProps>`
|
||||
const StyledEditInplaceInput = styled.input`
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
${textInputStyle}
|
||||
@ -37,17 +33,12 @@ const StyledEditInplaceInput = styled.input<StyledEditModeProps>`
|
||||
export function EditablePhone({ value, placeholder, changeHandler }: OwnProps) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
isEditMode={isEditMode}
|
||||
onOutsideClick={() => setIsEditMode(false)}
|
||||
onInsideClick={() => setIsEditMode(true)}
|
||||
editModeContent={
|
||||
<StyledEditInplaceInput
|
||||
autoFocus
|
||||
isEditMode={isEditMode}
|
||||
placeholder={placeholder || ''}
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
|
||||
@ -12,12 +12,8 @@ type OwnProps = {
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
type StyledEditModeProps = {
|
||||
isEditMode: boolean;
|
||||
};
|
||||
|
||||
// TODO: refactor
|
||||
const StyledInplaceInput = styled.input<StyledEditModeProps>`
|
||||
const StyledInplaceInput = styled.input`
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
${textInputStyle}
|
||||
@ -38,17 +34,12 @@ export function EditableText({
|
||||
}: OwnProps) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [inputValue, setInputValue] = useState(content);
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
isEditMode={isEditMode}
|
||||
onOutsideClick={() => setIsEditMode(false)}
|
||||
onInsideClick={() => setIsEditMode(true)}
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<StyledInplaceInput
|
||||
isEditMode={isEditMode}
|
||||
placeholder={placeholder || ''}
|
||||
autoFocus
|
||||
ref={inputRef}
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import {
|
||||
FilterConfigType,
|
||||
@ -16,12 +16,13 @@ import {
|
||||
SelectedSortType,
|
||||
SortType,
|
||||
} from '@/filters-and-sorts/interfaces/sorts/interface';
|
||||
import { contextMenuPositionState } from '@/ui/tables/states/contextMenuPositionState';
|
||||
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
|
||||
import { RowContext } from '@/ui/tables/states/RowContext';
|
||||
|
||||
import { useResetTableRowSelection } from '../../tables/hooks/useResetTableRowSelection';
|
||||
import { currentRowSelectionState } from '../../tables/states/rowSelectionState';
|
||||
|
||||
import { TableHeader } from './table-header/TableHeader';
|
||||
import { EntityTableRow } from './EntityTableRow';
|
||||
|
||||
type OwnProps<TData extends { id: string }, SortField> = {
|
||||
data: Array<TData>;
|
||||
@ -100,11 +101,6 @@ const StyledTableScrollableContainer = styled.div`
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
const StyledRow = styled.tr<{ selected: boolean }>`
|
||||
background: ${(props) =>
|
||||
props.selected ? props.theme.background.secondary : 'none'};
|
||||
`;
|
||||
|
||||
export function EntityTable<TData extends { id: string }, SortField>({
|
||||
data,
|
||||
columns,
|
||||
@ -118,13 +114,6 @@ export function EntityTable<TData extends { id: string }, SortField>({
|
||||
const [currentRowSelection, setCurrentRowSelection] = useRecoilState(
|
||||
currentRowSelectionState,
|
||||
);
|
||||
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
||||
|
||||
const resetTableRowSelection = useResetTableRowSelection();
|
||||
|
||||
React.useEffect(() => {
|
||||
resetTableRowSelection();
|
||||
}, [resetTableRowSelection]);
|
||||
|
||||
const table = useReactTable<TData>({
|
||||
data,
|
||||
@ -138,16 +127,6 @@ export function EntityTable<TData extends { id: string }, SortField>({
|
||||
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
|
||||
@ -186,33 +165,9 @@ export function EntityTable<TData extends { id: string }, SortField>({
|
||||
</thead>
|
||||
<tbody>
|
||||
{table.getRowModel().rows.map((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}
|
||||
onContextMenu={(event) =>
|
||||
handleContextMenu(event, row.original.id)
|
||||
}
|
||||
style={{
|
||||
width: cell.column.getSize(),
|
||||
minWidth: cell.column.getSize(),
|
||||
maxWidth: cell.column.getSize(),
|
||||
}}
|
||||
>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
<td></td>
|
||||
</StyledRow>
|
||||
<RecoilScope SpecificContext={RowContext} key={row.id}>
|
||||
<EntityTableRow row={row} index={index} />
|
||||
</RecoilScope>
|
||||
))}
|
||||
</tbody>
|
||||
</StyledTable>
|
||||
|
||||
56
front/src/modules/ui/components/table/EntityTableCell.tsx
Normal file
56
front/src/modules/ui/components/table/EntityTableCell.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import { useEffect } from 'react';
|
||||
import { flexRender } from '@tanstack/react-table';
|
||||
import { Cell, Row } from '@tanstack/table-core';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { CellContext } from '@/ui/tables/states/CellContext';
|
||||
import { contextMenuPositionState } from '@/ui/tables/states/contextMenuPositionState';
|
||||
import { currentColumnNumberScopedState } from '@/ui/tables/states/currentColumnNumberScopedState';
|
||||
import { currentRowSelectionState } from '@/ui/tables/states/rowSelectionState';
|
||||
|
||||
export function EntityTableCell<TData extends { id: string }>({
|
||||
row,
|
||||
cell,
|
||||
cellIndex,
|
||||
}: {
|
||||
row: Row<TData>;
|
||||
cell: Cell<TData, unknown>;
|
||||
cellIndex: number;
|
||||
}) {
|
||||
const [, setCurrentRowSelection] = useRecoilState(currentRowSelectionState);
|
||||
|
||||
const [, setCurrentColumnNumber] = useRecoilScopedState(
|
||||
currentColumnNumberScopedState,
|
||||
CellContext,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentColumnNumber(cellIndex);
|
||||
}, [cellIndex, setCurrentColumnNumber]);
|
||||
|
||||
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
||||
|
||||
function handleContextMenu(event: React.MouseEvent, id: string) {
|
||||
event.preventDefault();
|
||||
setCurrentRowSelection((prev) => ({ ...prev, [id]: true }));
|
||||
|
||||
setContextMenuPosition({
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<td
|
||||
onContextMenu={(event) => handleContextMenu(event, row.original.id)}
|
||||
style={{
|
||||
width: cell.column.getSize(),
|
||||
minWidth: cell.column.getSize(),
|
||||
maxWidth: cell.column.getSize(),
|
||||
}}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
63
front/src/modules/ui/components/table/EntityTableRow.tsx
Normal file
63
front/src/modules/ui/components/table/EntityTableRow.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import { useEffect } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Row } from '@tanstack/table-core';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
|
||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { CellContext } from '@/ui/tables/states/CellContext';
|
||||
import { currentRowNumberScopedState } from '@/ui/tables/states/currentRowNumberScopedState';
|
||||
import { RowContext } from '@/ui/tables/states/RowContext';
|
||||
import { currentRowSelectionState } from '@/ui/tables/states/rowSelectionState';
|
||||
|
||||
import { EntityTableCell } from './EntityTableCell';
|
||||
|
||||
const StyledRow = styled.tr<{ selected: boolean }>`
|
||||
background: ${(props) =>
|
||||
props.selected ? props.theme.background.secondary : 'none'};
|
||||
`;
|
||||
|
||||
export function EntityTableRow<TData extends { id: string }>({
|
||||
row,
|
||||
index,
|
||||
}: {
|
||||
row: Row<TData>;
|
||||
index: number;
|
||||
}) {
|
||||
const [currentRowSelection] = useRecoilState(currentRowSelectionState);
|
||||
|
||||
const [, setCurrentRowNumber] = useRecoilScopedState(
|
||||
currentRowNumberScopedState,
|
||||
RowContext,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentRowNumber(index);
|
||||
}, [index, setCurrentRowNumber]);
|
||||
|
||||
return (
|
||||
<StyledRow
|
||||
key={row.id}
|
||||
data-testid={`row-id-${row.index}`}
|
||||
selected={!!currentRowSelection[row.id]}
|
||||
>
|
||||
{row.getVisibleCells().map((cell, cellIndex) => {
|
||||
return (
|
||||
<RecoilScope
|
||||
SpecificContext={CellContext}
|
||||
key={cell.id + row.original.id}
|
||||
>
|
||||
<RecoilScope>
|
||||
<EntityTableCell<TData>
|
||||
row={row}
|
||||
cell={cell}
|
||||
cellIndex={cellIndex}
|
||||
/>
|
||||
</RecoilScope>
|
||||
</RecoilScope>
|
||||
);
|
||||
})}
|
||||
<td></td>
|
||||
</StyledRow>
|
||||
);
|
||||
}
|
||||
19
front/src/modules/ui/components/table/HooksEntityTable.tsx
Normal file
19
front/src/modules/ui/components/table/HooksEntityTable.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { useInitializeEntityTable } from '@/ui/tables/hooks/useInitializeEntityTable';
|
||||
import { useMapKeyboardToSoftFocus } from '@/ui/tables/hooks/useMapKeyboardToSoftFocus';
|
||||
|
||||
export function HooksEntityTable({
|
||||
numberOfColumns,
|
||||
numberOfRows,
|
||||
}: {
|
||||
numberOfColumns: number;
|
||||
numberOfRows: number;
|
||||
}) {
|
||||
useMapKeyboardToSoftFocus();
|
||||
|
||||
useInitializeEntityTable({
|
||||
numberOfColumns,
|
||||
numberOfRows,
|
||||
});
|
||||
|
||||
return <></>;
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
import { ReactNode, useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
|
||||
import { IconChevronDown } from '@/ui/icons/index';
|
||||
import { overlayBackground, textInputStyle } from '@/ui/themes/effects';
|
||||
|
||||
@ -159,11 +161,17 @@ function DropdownButton({
|
||||
setIsUnfolded,
|
||||
resetState,
|
||||
}: OwnProps) {
|
||||
const [, setCaptureHotkeyTypeInFocus] = useRecoilState(
|
||||
captureHotkeyTypeInFocusState,
|
||||
);
|
||||
|
||||
const onButtonClick = () => {
|
||||
setIsUnfolded && setIsUnfolded(!isUnfolded);
|
||||
setCaptureHotkeyTypeInFocus(!isUnfolded);
|
||||
};
|
||||
|
||||
const onOutsideClick = () => {
|
||||
setCaptureHotkeyTypeInFocus(false);
|
||||
setIsUnfolded && setIsUnfolded(false);
|
||||
resetState && resetState();
|
||||
};
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import React from 'react';
|
||||
import { ApolloProvider } from '@apollo/client';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { IconList } from '@/ui/icons/index';
|
||||
import { FullHeightStorybookLayout } from '~/testing/FullHeightStorybookLayout';
|
||||
import { mockedClient } from '~/testing/mockedClient';
|
||||
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
||||
|
||||
import { availableFilters } from '../../../../../../pages/companies/companies-filters';
|
||||
import { availableSorts } from '../../../../../../pages/companies/companies-sorts';
|
||||
@ -20,32 +18,24 @@ export default meta;
|
||||
type Story = StoryObj<typeof TableHeader>;
|
||||
|
||||
export const Empty: Story = {
|
||||
render: () => (
|
||||
<ApolloProvider client={mockedClient}>
|
||||
<FullHeightStorybookLayout>
|
||||
<TableHeader
|
||||
viewName="ViewName"
|
||||
viewIcon={<IconList />}
|
||||
availableSorts={availableSorts}
|
||||
availableFilters={availableFilters}
|
||||
/>
|
||||
</FullHeightStorybookLayout>
|
||||
</ApolloProvider>
|
||||
render: getRenderWrapperForComponent(
|
||||
<TableHeader
|
||||
viewName="ViewName"
|
||||
viewIcon={<IconList />}
|
||||
availableSorts={availableSorts}
|
||||
availableFilters={availableFilters}
|
||||
/>,
|
||||
),
|
||||
};
|
||||
|
||||
export const WithSortsAndFilters: Story = {
|
||||
render: () => (
|
||||
<ApolloProvider client={mockedClient}>
|
||||
<FullHeightStorybookLayout>
|
||||
<TableHeader
|
||||
viewName="ViewName"
|
||||
viewIcon={<IconList />}
|
||||
availableSorts={availableSorts}
|
||||
availableFilters={availableFilters}
|
||||
/>
|
||||
</FullHeightStorybookLayout>
|
||||
</ApolloProvider>
|
||||
render: getRenderWrapperForComponent(
|
||||
<TableHeader
|
||||
viewName="ViewName"
|
||||
viewIcon={<IconList />}
|
||||
availableSorts={availableSorts}
|
||||
availableFilters={availableFilters}
|
||||
/>,
|
||||
),
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { RecoilScopeContext } from './RecoilScopeContext';
|
||||
|
||||
export function RecoilScope({ children }: { children: React.ReactNode }) {
|
||||
const [currentScopeId] = useState(v4());
|
||||
|
||||
return (
|
||||
<RecoilScopeContext.Provider value={currentScopeId}>
|
||||
{children}
|
||||
</RecoilScopeContext.Provider>
|
||||
);
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import { useContext } from 'react';
|
||||
import { RecoilState, useRecoilState } from 'recoil';
|
||||
|
||||
import { RecoilScopeContext } from './RecoilScopeContext';
|
||||
|
||||
export function useRecoilScopedState<T>(
|
||||
recoilState: (param: string) => RecoilState<T>,
|
||||
) {
|
||||
const recoilScopeId = useContext(RecoilScopeContext);
|
||||
|
||||
if (!recoilScopeId)
|
||||
throw new Error(
|
||||
`Using a scoped atom without a RecoilScope : ${recoilState('').key}`,
|
||||
);
|
||||
|
||||
return useRecoilState<T>(recoilState(recoilScopeId));
|
||||
}
|
||||
1
front/src/modules/ui/tables/constants/index.ts
Normal file
1
front/src/modules/ui/tables/constants/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export const TABLE_MIN_COLUMN_NUMBER_BECAUSE_OF_CHECKBOX_COLUMN = 1;
|
||||
@ -0,0 +1,42 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { TABLE_MIN_COLUMN_NUMBER_BECAUSE_OF_CHECKBOX_COLUMN } from '../constants';
|
||||
import { entityTableDimensionsState } from '../states/entityTableDimensionsState';
|
||||
|
||||
import { useResetTableRowSelection } from './useResetTableRowSelection';
|
||||
import { useSetSoftFocusPosition } from './useSetSoftFocusPosition';
|
||||
|
||||
export type TableDimensions = {
|
||||
numberOfRows: number;
|
||||
numberOfColumns: number;
|
||||
};
|
||||
|
||||
export function useInitializeEntityTable({
|
||||
numberOfRows,
|
||||
numberOfColumns,
|
||||
}: TableDimensions) {
|
||||
const resetTableRowSelection = useResetTableRowSelection();
|
||||
|
||||
useEffect(() => {
|
||||
resetTableRowSelection();
|
||||
}, [resetTableRowSelection]);
|
||||
|
||||
const [, setTableDimensions] = useRecoilState(entityTableDimensionsState);
|
||||
|
||||
useEffect(() => {
|
||||
setTableDimensions({
|
||||
numberOfColumns,
|
||||
numberOfRows,
|
||||
});
|
||||
}, [numberOfRows, numberOfColumns, setTableDimensions]);
|
||||
|
||||
const setSoftFocusPosition = useSetSoftFocusPosition();
|
||||
|
||||
useEffect(() => {
|
||||
setSoftFocusPosition({
|
||||
row: 0,
|
||||
column: TABLE_MIN_COLUMN_NUMBER_BECAUSE_OF_CHECKBOX_COLUMN,
|
||||
});
|
||||
}, [setSoftFocusPosition]);
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { isSomeInputInEditModeState } from '../states/isSomeInputInEditModeState';
|
||||
|
||||
import { useMoveSoftFocus } from './useMoveSoftFocus';
|
||||
|
||||
export function useMapKeyboardToSoftFocus() {
|
||||
const { moveDown, moveLeft, moveRight, moveUp } = useMoveSoftFocus();
|
||||
|
||||
const [isSomeInputInEditMode] = useRecoilState(isSomeInputInEditModeState);
|
||||
|
||||
useHotkeys(
|
||||
'up, shift+enter',
|
||||
() => {
|
||||
if (!isSomeInputInEditMode) {
|
||||
moveUp();
|
||||
}
|
||||
},
|
||||
[moveUp, isSomeInputInEditMode],
|
||||
{
|
||||
preventDefault: true,
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
},
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'down',
|
||||
() => {
|
||||
if (!isSomeInputInEditMode) {
|
||||
moveDown();
|
||||
}
|
||||
},
|
||||
[moveDown, isSomeInputInEditMode],
|
||||
{
|
||||
preventDefault: true,
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
},
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
['left', 'shift+tab'],
|
||||
() => {
|
||||
if (!isSomeInputInEditMode) {
|
||||
moveLeft();
|
||||
}
|
||||
},
|
||||
[moveLeft, isSomeInputInEditMode],
|
||||
{
|
||||
preventDefault: true,
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
},
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
['right', 'tab'],
|
||||
() => {
|
||||
if (!isSomeInputInEditMode) {
|
||||
moveRight();
|
||||
}
|
||||
},
|
||||
[moveRight, isSomeInputInEditMode],
|
||||
{
|
||||
preventDefault: true,
|
||||
enableOnContentEditable: true,
|
||||
enableOnFormTags: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
161
front/src/modules/ui/tables/hooks/useMoveSoftFocus.ts
Normal file
161
front/src/modules/ui/tables/hooks/useMoveSoftFocus.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { TABLE_MIN_COLUMN_NUMBER_BECAUSE_OF_CHECKBOX_COLUMN } from '../constants';
|
||||
import { numberOfTableColumnsSelectorState } from '../states/numberOfTableColumnsSelectorState';
|
||||
import { numberOfTableRowsSelectorState } from '../states/numberOfTableRowsSelectorState';
|
||||
import { softFocusPositionState } from '../states/softFocusPositionState';
|
||||
|
||||
import { useSetSoftFocusPosition } from './useSetSoftFocusPosition';
|
||||
|
||||
// TODO: stories
|
||||
export function useMoveSoftFocus() {
|
||||
const setSoftFocusPosition = useSetSoftFocusPosition();
|
||||
|
||||
const moveUp = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const softFocusPosition = snapshot
|
||||
.getLoadable(softFocusPositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
let newRowNumber = softFocusPosition.row - 1;
|
||||
|
||||
if (newRowNumber < 0) {
|
||||
newRowNumber = 0;
|
||||
}
|
||||
|
||||
setSoftFocusPosition({
|
||||
...softFocusPosition,
|
||||
row: newRowNumber,
|
||||
});
|
||||
},
|
||||
[setSoftFocusPosition],
|
||||
);
|
||||
|
||||
const moveDown = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const softFocusPosition = snapshot
|
||||
.getLoadable(softFocusPositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
const numberOfTableRows = snapshot
|
||||
.getLoadable(numberOfTableRowsSelectorState)
|
||||
.valueOrThrow();
|
||||
|
||||
let newRowNumber = softFocusPosition.row + 1;
|
||||
|
||||
if (newRowNumber >= numberOfTableRows) {
|
||||
newRowNumber = numberOfTableRows - 1;
|
||||
}
|
||||
|
||||
setSoftFocusPosition({
|
||||
...softFocusPosition,
|
||||
row: newRowNumber,
|
||||
});
|
||||
},
|
||||
[setSoftFocusPosition],
|
||||
);
|
||||
|
||||
const moveRight = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const softFocusPosition = snapshot
|
||||
.getLoadable(softFocusPositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
const numberOfTableColumns = snapshot
|
||||
.getLoadable(numberOfTableColumnsSelectorState)
|
||||
.valueOrThrow();
|
||||
|
||||
const numberOfTableRows = snapshot
|
||||
.getLoadable(numberOfTableRowsSelectorState)
|
||||
.valueOrThrow();
|
||||
|
||||
const currentColumnNumber = softFocusPosition.column;
|
||||
const currentRowNumber = softFocusPosition.row;
|
||||
|
||||
const isLastRowAndLastColumn =
|
||||
currentColumnNumber === numberOfTableColumns - 1 &&
|
||||
currentRowNumber === numberOfTableRows - 1;
|
||||
|
||||
const isLastColumnButNotLastRow =
|
||||
currentColumnNumber === numberOfTableColumns - 1 &&
|
||||
currentRowNumber !== numberOfTableRows - 1;
|
||||
|
||||
const isNotLastColumn =
|
||||
currentColumnNumber !== numberOfTableColumns - 1;
|
||||
|
||||
if (isLastRowAndLastColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNotLastColumn) {
|
||||
setSoftFocusPosition({
|
||||
row: currentRowNumber,
|
||||
column: currentColumnNumber + 1,
|
||||
});
|
||||
} else if (isLastColumnButNotLastRow) {
|
||||
setSoftFocusPosition({
|
||||
row: currentRowNumber + 1,
|
||||
column: TABLE_MIN_COLUMN_NUMBER_BECAUSE_OF_CHECKBOX_COLUMN,
|
||||
});
|
||||
}
|
||||
},
|
||||
[setSoftFocusPosition],
|
||||
);
|
||||
|
||||
const moveLeft = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const softFocusPosition = snapshot
|
||||
.getLoadable(softFocusPositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
const numberOfTableColumns = snapshot
|
||||
.getLoadable(numberOfTableColumnsSelectorState)
|
||||
.valueOrThrow();
|
||||
|
||||
const currentColumnNumber = softFocusPosition.column;
|
||||
const currentRowNumber = softFocusPosition.row;
|
||||
|
||||
const isFirstRowAndFirstColumn =
|
||||
currentColumnNumber ===
|
||||
TABLE_MIN_COLUMN_NUMBER_BECAUSE_OF_CHECKBOX_COLUMN &&
|
||||
currentRowNumber === 0;
|
||||
|
||||
const isFirstColumnButNotFirstRow =
|
||||
currentColumnNumber ===
|
||||
TABLE_MIN_COLUMN_NUMBER_BECAUSE_OF_CHECKBOX_COLUMN &&
|
||||
currentRowNumber > 0;
|
||||
|
||||
const isNotFirstColumn =
|
||||
currentColumnNumber >
|
||||
TABLE_MIN_COLUMN_NUMBER_BECAUSE_OF_CHECKBOX_COLUMN;
|
||||
|
||||
if (isFirstRowAndFirstColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNotFirstColumn) {
|
||||
setSoftFocusPosition({
|
||||
row: currentRowNumber,
|
||||
column: currentColumnNumber - 1,
|
||||
});
|
||||
} else if (isFirstColumnButNotFirstRow) {
|
||||
setSoftFocusPosition({
|
||||
row: currentRowNumber - 1,
|
||||
column: numberOfTableColumns - 1,
|
||||
});
|
||||
}
|
||||
},
|
||||
[setSoftFocusPosition, TABLE_MIN_COLUMN_NUMBER_BECAUSE_OF_CHECKBOX_COLUMN],
|
||||
);
|
||||
|
||||
return {
|
||||
moveDown,
|
||||
moveLeft,
|
||||
moveRight,
|
||||
moveUp,
|
||||
};
|
||||
}
|
||||
21
front/src/modules/ui/tables/hooks/useSetSoftFocusPosition.ts
Normal file
21
front/src/modules/ui/tables/hooks/useSetSoftFocusPosition.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { isSoftFocusOnCellFamilyState } from '../states/isSoftFocusOnCellFamilyState';
|
||||
import { softFocusPositionState } from '../states/softFocusPositionState';
|
||||
import { TablePosition } from '../types/TablePosition';
|
||||
|
||||
export function useSetSoftFocusPosition() {
|
||||
return useRecoilCallback(({ set, snapshot }) => {
|
||||
return (newPosition: TablePosition) => {
|
||||
const currentPosition = snapshot
|
||||
.getLoadable(softFocusPositionState)
|
||||
.valueOrThrow();
|
||||
|
||||
set(isSoftFocusOnCellFamilyState(currentPosition), false);
|
||||
|
||||
set(softFocusPositionState, newPosition);
|
||||
|
||||
set(isSoftFocusOnCellFamilyState(newPosition), true);
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
3
front/src/modules/ui/tables/states/CellContext.ts
Normal file
3
front/src/modules/ui/tables/states/CellContext.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const CellContext = createContext<string | null>(null);
|
||||
3
front/src/modules/ui/tables/states/RowContext.ts
Normal file
3
front/src/modules/ui/tables/states/RowContext.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const RowContext = createContext<string | null>(null);
|
||||
@ -0,0 +1,6 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const currentColumnNumberScopedState = atomFamily<number, string>({
|
||||
key: 'currentColumnNumberScopedState',
|
||||
default: 0,
|
||||
});
|
||||
@ -0,0 +1,6 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const currentRowNumberScopedState = atomFamily<number, string>({
|
||||
key: 'currentRowNumberScopedState',
|
||||
default: 0,
|
||||
});
|
||||
@ -0,0 +1,11 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { TableDimensions } from '../hooks/useInitializeEntityTable';
|
||||
|
||||
export const entityTableDimensionsState = atom<TableDimensions>({
|
||||
key: 'entityTableDimensionsState',
|
||||
default: {
|
||||
numberOfRows: 0,
|
||||
numberOfColumns: 0,
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
import { TablePosition } from '../types/TablePosition';
|
||||
|
||||
export const isSoftFocusOnCellFamilyState = atomFamily<boolean, TablePosition>({
|
||||
key: 'isSoftFocusOnCellFamilyState',
|
||||
default: false,
|
||||
});
|
||||
@ -0,0 +1,12 @@
|
||||
import { selector } from 'recoil';
|
||||
|
||||
import { entityTableDimensionsState } from './entityTableDimensionsState';
|
||||
|
||||
export const numberOfTableColumnsSelectorState = selector<number>({
|
||||
key: 'numberOfTableColumnsState',
|
||||
get: ({ get }) => {
|
||||
const { numberOfColumns } = get(entityTableDimensionsState);
|
||||
|
||||
return numberOfColumns;
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,12 @@
|
||||
import { selector } from 'recoil';
|
||||
|
||||
import { entityTableDimensionsState } from './entityTableDimensionsState';
|
||||
|
||||
export const numberOfTableRowsSelectorState = selector<number>({
|
||||
key: 'numberOfTableRowsState',
|
||||
get: ({ get }) => {
|
||||
const { numberOfRows } = get(entityTableDimensionsState);
|
||||
|
||||
return numberOfRows;
|
||||
},
|
||||
});
|
||||
11
front/src/modules/ui/tables/states/softFocusPositionState.ts
Normal file
11
front/src/modules/ui/tables/states/softFocusPositionState.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { TablePosition } from '../types/TablePosition';
|
||||
|
||||
export const softFocusPositionState = atom<TablePosition>({
|
||||
key: 'softFocusPositionState',
|
||||
default: {
|
||||
row: 0,
|
||||
column: 1,
|
||||
},
|
||||
});
|
||||
4
front/src/modules/ui/tables/types/TableDimensions.ts
Normal file
4
front/src/modules/ui/tables/types/TableDimensions.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type TablePosition = {
|
||||
numberOfRows: number;
|
||||
numberOfColumns: number;
|
||||
};
|
||||
4
front/src/modules/ui/tables/types/TablePosition.ts
Normal file
4
front/src/modules/ui/tables/types/TablePosition.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type TablePosition = {
|
||||
row: number;
|
||||
column: number;
|
||||
};
|
||||
@ -0,0 +1,7 @@
|
||||
import { TablePosition } from '../TablePosition';
|
||||
|
||||
export function isTablePosition(value: any): value is TablePosition {
|
||||
return (
|
||||
value && typeof value.row === 'number' && typeof value.column === 'number'
|
||||
);
|
||||
}
|
||||
57
front/src/modules/utils/hotkeys/isNonTextWritingKey.ts
Normal file
57
front/src/modules/utils/hotkeys/isNonTextWritingKey.ts
Normal file
@ -0,0 +1,57 @@
|
||||
export function isNonTextWritingKey(key: string) {
|
||||
const nonTextWritingKeys = [
|
||||
'Enter',
|
||||
'Tab',
|
||||
'Shift',
|
||||
'Escape',
|
||||
'ArrowUp',
|
||||
'ArrowDown',
|
||||
'ArrowLeft',
|
||||
'ArrowRight',
|
||||
'Delete',
|
||||
'Backspace',
|
||||
'F1',
|
||||
'F2',
|
||||
'F3',
|
||||
'F4',
|
||||
'F5',
|
||||
'F6',
|
||||
'F7',
|
||||
'F8',
|
||||
'F9',
|
||||
'F10',
|
||||
'F11',
|
||||
'F12',
|
||||
'Meta',
|
||||
'Alt',
|
||||
'Control',
|
||||
'CapsLock',
|
||||
'NumLock',
|
||||
'ScrollLock',
|
||||
'Pause',
|
||||
'Insert',
|
||||
'Home',
|
||||
'PageUp',
|
||||
'Delete',
|
||||
'End',
|
||||
'PageDown',
|
||||
'ContextMenu',
|
||||
'PrintScreen',
|
||||
'BrowserBack',
|
||||
'BrowserForward',
|
||||
'BrowserRefresh',
|
||||
'BrowserStop',
|
||||
'BrowserSearch',
|
||||
'BrowserFavorites',
|
||||
'BrowserHome',
|
||||
'VolumeMute',
|
||||
'VolumeDown',
|
||||
'VolumeUp',
|
||||
'MediaTrackNext',
|
||||
'MediaTrackPrevious',
|
||||
'MediaStop',
|
||||
'MediaPlayPause',
|
||||
];
|
||||
|
||||
return nonTextWritingKeys.includes(key);
|
||||
}
|
||||
@ -17,6 +17,7 @@ import {
|
||||
import { SelectedFilterType } from '@/filters-and-sorts/interfaces/filters/interface';
|
||||
import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTableActionBar';
|
||||
import { EntityTable } from '@/ui/components/table/EntityTable';
|
||||
import { HooksEntityTable } from '@/ui/components/table/HooksEntityTable';
|
||||
import { IconBuildingSkyscraper } from '@/ui/icons/index';
|
||||
import { IconList } from '@/ui/icons/index';
|
||||
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
||||
@ -85,6 +86,10 @@ export function Companies() {
|
||||
>
|
||||
<>
|
||||
<StyledCompaniesContainer>
|
||||
<HooksEntityTable
|
||||
numberOfColumns={companiesColumns.length}
|
||||
numberOfRows={companies.length}
|
||||
/>
|
||||
<EntityTable
|
||||
data={companies}
|
||||
columns={companiesColumns}
|
||||
|
||||
@ -6,7 +6,6 @@ import { CompanyEditableNameChipCell } from '@/companies/components/CompanyEdita
|
||||
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
|
||||
import { EditableText } from '@/ui/components/editable-cell/types/EditableText';
|
||||
import { ColumnHead } from '@/ui/components/table/ColumnHead';
|
||||
import { RecoilScope } from '@/ui/hooks/RecoilScope';
|
||||
import {
|
||||
IconBuildingSkyscraper,
|
||||
IconCalendarEvent,
|
||||
@ -143,9 +142,7 @@ export const useCompaniesColumns = () => {
|
||||
/>
|
||||
),
|
||||
cell: (props) => (
|
||||
<RecoilScope>
|
||||
<CompanyAccountOwnerCell company={props.row.original} />
|
||||
</RecoilScope>
|
||||
<CompanyAccountOwnerCell company={props.row.original} />
|
||||
),
|
||||
}),
|
||||
];
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
} from '@/people/services';
|
||||
import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTableActionBar';
|
||||
import { EntityTable } from '@/ui/components/table/EntityTable';
|
||||
import { HooksEntityTable } from '@/ui/components/table/HooksEntityTable';
|
||||
import { IconList, IconUser } from '@/ui/icons/index';
|
||||
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
||||
import {
|
||||
@ -76,6 +77,7 @@ export function People() {
|
||||
const peopleColumns = usePeopleColumns();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<WithTopBarContainer
|
||||
title="People"
|
||||
@ -84,6 +86,10 @@ export function People() {
|
||||
>
|
||||
<>
|
||||
<StyledPeopleContainer>
|
||||
<HooksEntityTable
|
||||
numberOfColumns={peopleColumns.length}
|
||||
numberOfRows={people.length}
|
||||
/>
|
||||
<EntityTable
|
||||
data={people}
|
||||
columns={peopleColumns}
|
||||
|
||||
@ -52,11 +52,15 @@ export const InteractWithManyRows: Story = {
|
||||
|
||||
await sleep(25);
|
||||
|
||||
const secondRowEmailCellFocused = await canvas.findByText(
|
||||
mockedPeopleData[1].email,
|
||||
);
|
||||
|
||||
expect(
|
||||
canvas.queryByTestId('editable-cell-edit-mode-container'),
|
||||
).toBeNull();
|
||||
|
||||
await userEvent.click(secondRowEmailCell);
|
||||
await userEvent.click(secondRowEmailCellFocused);
|
||||
|
||||
await sleep(25);
|
||||
|
||||
|
||||
@ -7,7 +7,6 @@ import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
|
||||
import { EditablePhone } from '@/ui/components/editable-cell/types/EditablePhone';
|
||||
import { EditableText } from '@/ui/components/editable-cell/types/EditableText';
|
||||
import { ColumnHead } from '@/ui/components/table/ColumnHead';
|
||||
import { RecoilScope } from '@/ui/hooks/RecoilScope';
|
||||
import {
|
||||
IconBuildingSkyscraper,
|
||||
IconCalendarEvent,
|
||||
@ -80,11 +79,7 @@ export const usePeopleColumns = () => {
|
||||
viewIcon={<IconBuildingSkyscraper size={16} />}
|
||||
/>
|
||||
),
|
||||
cell: (props) => (
|
||||
<RecoilScope>
|
||||
<PeopleCompanyCell people={props.row.original} />
|
||||
</RecoilScope>
|
||||
),
|
||||
cell: (props) => <PeopleCompanyCell people={props.row.original} />,
|
||||
size: 150,
|
||||
}),
|
||||
columnHelper.accessor('phone', {
|
||||
|
||||
Reference in New Issue
Block a user