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 { 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 { Company, User } from '~/generated/graphql';
|
||||||
|
|
||||||
import { CompanyAccountOwnerPicker } from './CompanyAccountOwnerPicker';
|
import { CompanyAccountOwnerPicker } from './CompanyAccountOwnerPicker';
|
||||||
@ -12,7 +12,7 @@ export type OwnProps = {
|
|||||||
|
|
||||||
export function CompanyAccountOwnerCell({ company }: OwnProps) {
|
export function CompanyAccountOwnerCell({ company }: OwnProps) {
|
||||||
return (
|
return (
|
||||||
<EditableCellV2
|
<EditableCell
|
||||||
editModeContent={<CompanyAccountOwnerPicker company={company} />}
|
editModeContent={<CompanyAccountOwnerPicker company={company} />}
|
||||||
nonEditModeContent={
|
nonEditModeContent={
|
||||||
company.accountOwner?.displayName ? (
|
company.accountOwner?.displayName ? (
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
|
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||||
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
|
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
|
||||||
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
|
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
|
||||||
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||||
import { EntityForSelect } from '@/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/relation-picker/types/EntityForSelect';
|
||||||
import { Entity } from '@/relation-picker/types/EntityTypeForSelect';
|
import { Entity } from '@/relation-picker/types/EntityTypeForSelect';
|
||||||
import { useCloseEditableCell } from '@/ui/components/editable-cell/hooks/useCloseEditableCell';
|
import { useEditableCell } from '@/ui/components/editable-cell/hooks/useCloseEditableCell';
|
||||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
|
||||||
import {
|
import {
|
||||||
Company,
|
Company,
|
||||||
User,
|
User,
|
||||||
@ -28,7 +28,7 @@ export function CompanyAccountOwnerPicker({ company }: OwnProps) {
|
|||||||
);
|
);
|
||||||
const [updateCompany] = useUpdateCompanyMutation();
|
const [updateCompany] = useUpdateCompanyMutation();
|
||||||
|
|
||||||
const closeEditableCell = useCloseEditableCell();
|
const { closeEditableCell } = useEditableCell();
|
||||||
|
|
||||||
const companies = useFilteredSearchEntityQuery({
|
const companies = useFilteredSearchEntityQuery({
|
||||||
queryHook: useSearchUserQuery,
|
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';
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
export const pendingHotkeyState = atom<string | null>({
|
export const pendingHotkeyState = atom<string | null>({
|
||||||
key: 'command-menu/pendingHotkeyState',
|
key: 'pendingHotkeyState',
|
||||||
default: null,
|
default: null,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import { useCallback, useState } from 'react';
|
|||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
|
||||||
import { Column } from '@/ui/components/board/Board';
|
import { Column } from '@/ui/components/board/Board';
|
||||||
import { NewButton as UINewButton } from '@/ui/components/board/NewButton';
|
import { NewButton as UINewButton } from '@/ui/components/board/NewButton';
|
||||||
import { RecoilScope } from '@/ui/hooks/RecoilScope';
|
|
||||||
import {
|
import {
|
||||||
Company,
|
Company,
|
||||||
PipelineProgressableType,
|
PipelineProgressableType,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||||
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
|
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
|
||||||
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
|
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
|
||||||
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
|
||||||
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
||||||
import {
|
import {
|
||||||
CommentableType,
|
CommentableType,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import CompanyChip from '@/companies/components/CompanyChip';
|
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 { isCreateModeScopedState } from '@/ui/components/editable-cell/states/isCreateModeScopedState';
|
||||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
|
||||||
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
||||||
import { Company, Person } from '~/generated/graphql';
|
import { Company, Person } from '~/generated/graphql';
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ export function PeopleCompanyCell({ people }: OwnProps) {
|
|||||||
const [isCreating] = useRecoilScopedState(isCreateModeScopedState);
|
const [isCreating] = useRecoilScopedState(isCreateModeScopedState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellV2
|
<EditableCell
|
||||||
editModeContent={
|
editModeContent={
|
||||||
isCreating ? (
|
isCreating ? (
|
||||||
<PeopleCompanyCreateCell people={people} />
|
<PeopleCompanyCreateCell people={people} />
|
||||||
|
|||||||
@ -2,11 +2,11 @@ import { useRef, useState } from 'react';
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||||
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||||
import { isCreateModeScopedState } from '@/ui/components/editable-cell/states/isCreateModeScopedState';
|
import { isCreateModeScopedState } from '@/ui/components/editable-cell/states/isCreateModeScopedState';
|
||||||
import { DoubleTextInput } from '@/ui/components/inputs/DoubleTextInput';
|
import { DoubleTextInput } from '@/ui/components/inputs/DoubleTextInput';
|
||||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
|
||||||
import { logError } from '@/utils/logs/logError';
|
import { logError } from '@/utils/logs/logError';
|
||||||
import {
|
import {
|
||||||
Person,
|
Person,
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
|
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||||
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
|
import { SingleEntitySelect } from '@/relation-picker/components/SingleEntitySelect';
|
||||||
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
|
import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery';
|
||||||
import { relationPickerSearchFilterScopedState } from '@/relation-picker/states/relationPickerSearchFilterScopedState';
|
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 { isCreateModeScopedState } from '@/ui/components/editable-cell/states/isCreateModeScopedState';
|
||||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
|
||||||
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
||||||
import {
|
import {
|
||||||
CommentableType,
|
CommentableType,
|
||||||
@ -25,7 +25,7 @@ export function PeopleCompanyPicker({ people }: OwnProps) {
|
|||||||
);
|
);
|
||||||
const [updatePeople] = useUpdatePeopleMutation();
|
const [updatePeople] = useUpdatePeopleMutation();
|
||||||
|
|
||||||
const closeEditableCell = useCloseEditableCell();
|
const { closeEditableCell } = useEditableCell();
|
||||||
|
|
||||||
const companies = useFilteredSearchEntityQuery({
|
const companies = useFilteredSearchEntityQuery({
|
||||||
queryHook: useSearchCompanyQuery,
|
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 { useContext } from 'react';
|
||||||
import { RecoilState, useRecoilValue } from 'recoil';
|
import { RecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { RecoilScopeContext } from './RecoilScopeContext';
|
import { RecoilScopeContext } from '../states/RecoilScopeContext';
|
||||||
|
|
||||||
export function useRecoilScopedValue<T>(
|
export function useRecoilScopedValue<T>(
|
||||||
recoilState: (param: string) => RecoilState<T>,
|
recoilState: (param: string) => RecoilState<T>,
|
||||||
@ -3,7 +3,7 @@ import { debounce } from 'lodash';
|
|||||||
import scrollIntoView from 'scroll-into-view';
|
import scrollIntoView from 'scroll-into-view';
|
||||||
|
|
||||||
import { useUpDownHotkeys } from '@/hotkeys/hooks/useUpDownHotkeys';
|
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 { relationPickerSearchFilterScopedState } from '../states/relationPickerSearchFilterScopedState';
|
||||||
import { EntityForSelect } from '../types/EntityForSelect';
|
import { EntityForSelect } from '../types/EntityForSelect';
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
import styled from '@emotion/styled';
|
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 { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
||||||
import { EditableCellEditMode } from './EditableCellEditMode';
|
import { EditableCellEditMode } from './EditableCellEditMode';
|
||||||
|
import { EditableCellSoftFocusMode } from './EditableCellSoftFocusMode';
|
||||||
|
|
||||||
export const CellBaseContainer = styled.div`
|
export const CellBaseContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -23,43 +27,48 @@ type OwnProps = {
|
|||||||
nonEditModeContent: ReactElement;
|
nonEditModeContent: ReactElement;
|
||||||
editModeHorizontalAlign?: 'left' | 'right';
|
editModeHorizontalAlign?: 'left' | 'right';
|
||||||
editModeVerticalPosition?: 'over' | 'below';
|
editModeVerticalPosition?: 'over' | 'below';
|
||||||
isEditMode?: boolean;
|
|
||||||
isCreateMode?: boolean;
|
|
||||||
onOutsideClick?: () => void;
|
|
||||||
onInsideClick?: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditableCell({
|
export function EditableCell({
|
||||||
editModeContent,
|
|
||||||
nonEditModeContent,
|
|
||||||
editModeHorizontalAlign = 'left',
|
editModeHorizontalAlign = 'left',
|
||||||
editModeVerticalPosition = 'over',
|
editModeVerticalPosition = 'over',
|
||||||
isEditMode = false,
|
editModeContent,
|
||||||
onOutsideClick,
|
nonEditModeContent,
|
||||||
onInsideClick,
|
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const [isSomeInputInEditMode, setIsSomeInputInEditMode] = useRecoilState(
|
const [isEditMode] = useRecoilScopedState(isEditModeScopedState);
|
||||||
isSomeInputInEditModeState,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
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() {
|
function handleOnClick() {
|
||||||
if (!isSomeInputInEditMode) {
|
openEditableCell();
|
||||||
onInsideClick?.();
|
setSoftFocusOnCurrentCell();
|
||||||
setIsSomeInputInEditMode(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleOnOutsideClick() {
|
||||||
|
closeEditableCell();
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CellBaseContainer onClick={handleOnClick}>
|
<CellBaseContainer onClick={handleOnClick}>
|
||||||
{isEditMode ? (
|
{isEditMode ? (
|
||||||
<EditableCellEditMode
|
<EditableCellEditMode
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
editModeVerticalPosition={editModeVerticalPosition}
|
editModeVerticalPosition={editModeVerticalPosition}
|
||||||
isEditMode={isEditMode}
|
onOutsideClick={handleOnOutsideClick}
|
||||||
onOutsideClick={onOutsideClick}
|
|
||||||
>
|
>
|
||||||
{editModeContent}
|
{editModeContent}
|
||||||
</EditableCellEditMode>
|
</EditableCellEditMode>
|
||||||
|
) : hasSoftFocus ? (
|
||||||
|
<EditableCellSoftFocusMode>
|
||||||
|
{nonEditModeContent}
|
||||||
|
</EditableCellSoftFocusMode>
|
||||||
) : (
|
) : (
|
||||||
<EditableCellDisplayMode>{nonEditModeContent}</EditableCellDisplayMode>
|
<EditableCellDisplayMode>{nonEditModeContent}</EditableCellDisplayMode>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
import { ReactElement } from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
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;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -11,17 +16,12 @@ export const EditableCellNormalModeOuterContainer = styled.div`
|
|||||||
padding-right: ${({ theme }) => theme.spacing(1)};
|
padding-right: ${({ theme }) => theme.spacing(1)};
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&:hover {
|
${(props) =>
|
||||||
-moz-box-shadow: inset 0 0 0 1px
|
props.softFocus
|
||||||
${({ theme }) => theme.font.color.extraLight};
|
? `background: ${props.theme.background.transparent.secondary};
|
||||||
|
border-radius: ${props.theme.border.radius.md};
|
||||||
-webkit-box-shadow: inset 0 0 0 1px
|
box-shadow: inset 0 0 0 1px ${props.theme.grayScale.gray30};`
|
||||||
${({ 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};
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const EditableCellNormalModeInnerContainer = styled.div`
|
export const EditableCellNormalModeInnerContainer = styled.div`
|
||||||
@ -32,13 +32,13 @@ export const EditableCellNormalModeInnerContainer = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
export function EditableCellDisplayMode({
|
||||||
children: ReactElement;
|
children,
|
||||||
};
|
}: React.PropsWithChildren<unknown>) {
|
||||||
|
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
||||||
|
|
||||||
export function EditableCellDisplayMode({ children }: OwnProps) {
|
|
||||||
return (
|
return (
|
||||||
<EditableCellNormalModeOuterContainer>
|
<EditableCellNormalModeOuterContainer softFocus={hasSoftFocus}>
|
||||||
<EditableCellNormalModeInnerContainer>
|
<EditableCellNormalModeInnerContainer>
|
||||||
{children}
|
{children}
|
||||||
</EditableCellNormalModeInnerContainer>
|
</EditableCellNormalModeInnerContainer>
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import { ReactElement, useMemo, useRef } from 'react';
|
import { ReactElement, useRef } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import styled from '@emotion/styled';
|
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 { overlayBackground } from '@/ui/themes/effects';
|
||||||
import { debounce } from '@/utils/debounce';
|
|
||||||
|
|
||||||
import { useListenClickOutsideArrayOfRef } from '../../hooks/useListenClickOutsideArrayOfRef';
|
import { useEditableCell } from './hooks/useCloseEditableCell';
|
||||||
import { isSomeInputInEditModeState } from '../../tables/states/isSomeInputInEditModeState';
|
|
||||||
|
|
||||||
export const EditableCellEditModeContainer = styled.div<OwnProps>`
|
export const EditableCellEditModeContainer = styled.div<OwnProps>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -32,67 +31,77 @@ type OwnProps = {
|
|||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
editModeHorizontalAlign?: 'left' | 'right';
|
editModeHorizontalAlign?: 'left' | 'right';
|
||||||
editModeVerticalPosition?: 'over' | 'below';
|
editModeVerticalPosition?: 'over' | 'below';
|
||||||
isEditMode?: boolean;
|
|
||||||
onOutsideClick?: () => void;
|
onOutsideClick?: () => void;
|
||||||
onInsideClick?: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditableCellEditMode({
|
export function EditableCellEditMode({
|
||||||
editModeHorizontalAlign,
|
editModeHorizontalAlign,
|
||||||
editModeVerticalPosition,
|
editModeVerticalPosition,
|
||||||
children,
|
children,
|
||||||
isEditMode,
|
|
||||||
onOutsideClick,
|
onOutsideClick,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const wrapperRef = useRef(null);
|
const wrapperRef = useRef(null);
|
||||||
|
|
||||||
const [, setIsSomeInputInEditMode] = useRecoilState(
|
const { closeEditableCell } = useEditableCell();
|
||||||
isSomeInputInEditModeState,
|
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
|
||||||
);
|
|
||||||
|
|
||||||
const debouncedSetIsSomeInputInEditMode = useMemo(() => {
|
|
||||||
return debounce(setIsSomeInputInEditMode, 20);
|
|
||||||
}, [setIsSomeInputInEditMode]);
|
|
||||||
|
|
||||||
useListenClickOutsideArrayOfRef([wrapperRef], () => {
|
useListenClickOutsideArrayOfRef([wrapperRef], () => {
|
||||||
if (isEditMode) {
|
onOutsideClick?.();
|
||||||
debouncedSetIsSomeInputInEditMode(false);
|
|
||||||
onOutsideClick?.();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useHotkeys(
|
|
||||||
'esc',
|
|
||||||
() => {
|
|
||||||
if (isEditMode) {
|
|
||||||
onOutsideClick?.();
|
|
||||||
|
|
||||||
debouncedSetIsSomeInputInEditMode(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
preventDefault: true,
|
|
||||||
enableOnContentEditable: true,
|
|
||||||
enableOnFormTags: true,
|
|
||||||
},
|
|
||||||
[isEditMode, onOutsideClick, debouncedSetIsSomeInputInEditMode],
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'enter',
|
'enter',
|
||||||
() => {
|
() => {
|
||||||
if (isEditMode) {
|
closeEditableCell();
|
||||||
onOutsideClick?.();
|
moveDown();
|
||||||
|
|
||||||
debouncedSetIsSomeInputInEditMode(false);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
preventDefault: true,
|
|
||||||
enableOnContentEditable: true,
|
enableOnContentEditable: true,
|
||||||
enableOnFormTags: 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 (
|
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 { useRecoilCallback } from 'recoil';
|
||||||
import { useRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import { useRecoilScopedState } from '@/ui/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||||
import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState';
|
import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState';
|
||||||
|
|
||||||
import { isEditModeScopedState } from '../states/isEditModeScopedState';
|
import { isEditModeScopedState } from '../states/isEditModeScopedState';
|
||||||
|
|
||||||
export function useCloseEditableCell() {
|
export function useEditableCell() {
|
||||||
const [, setIsSomeInputInEditMode] = useRecoilState(
|
|
||||||
isSomeInputInEditModeState,
|
|
||||||
);
|
|
||||||
const [, setIsEditMode] = useRecoilScopedState(isEditModeScopedState);
|
const [, setIsEditMode] = useRecoilScopedState(isEditModeScopedState);
|
||||||
|
|
||||||
return useCallback(() => {
|
const closeEditableCell = useRecoilCallback(
|
||||||
setIsSomeInputInEditMode(false);
|
({ set }) =>
|
||||||
setIsEditMode(false);
|
async () => {
|
||||||
}, [setIsEditMode, setIsSomeInputInEditMode]);
|
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) {
|
}: EditableChipProps) {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [inputValue, setInputValue] = useState(value);
|
const [inputValue, setInputValue] = useState(value);
|
||||||
const [isEditMode, setIsEditMode] = useState(false);
|
|
||||||
|
|
||||||
const handleRightEndContentClick = (
|
const handleRightEndContentClick = (
|
||||||
event: React.MouseEvent<HTMLDivElement>,
|
event: React.MouseEvent<HTMLDivElement>,
|
||||||
@ -60,9 +59,6 @@ function EditableChip({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
onOutsideClick={() => setIsEditMode(false)}
|
|
||||||
onInsideClick={() => setIsEditMode(true)}
|
|
||||||
isEditMode={isEditMode}
|
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<StyledInplaceInput
|
<StyledInplaceInput
|
||||||
|
|||||||
@ -38,7 +38,6 @@ export function EditableDate({
|
|||||||
editModeHorizontalAlign,
|
editModeHorizontalAlign,
|
||||||
}: EditableDateProps) {
|
}: EditableDateProps) {
|
||||||
const [inputValue, setInputValue] = useState(value);
|
const [inputValue, setInputValue] = useState(value);
|
||||||
const [isEditMode, setIsEditMode] = useState(false);
|
|
||||||
|
|
||||||
type DivProps = React.HTMLProps<HTMLDivElement>;
|
type DivProps = React.HTMLProps<HTMLDivElement>;
|
||||||
|
|
||||||
@ -60,9 +59,6 @@ export function EditableDate({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
isEditMode={isEditMode}
|
|
||||||
onOutsideClick={() => setIsEditMode(false)}
|
|
||||||
onInsideClick={() => setIsEditMode(true)}
|
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ChangeEvent, ReactElement, useRef, useState } from 'react';
|
import { ChangeEvent, ReactElement, useRef } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { textInputStyle } from '@/ui/themes/effects';
|
import { textInputStyle } from '@/ui/themes/effects';
|
||||||
@ -42,13 +42,9 @@ export function EditableDoubleText({
|
|||||||
onChange,
|
onChange,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const firstValueInputRef = useRef<HTMLInputElement>(null);
|
const firstValueInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [isEditMode, setIsEditMode] = useState(false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
onInsideClick={() => setIsEditMode(true)}
|
|
||||||
onOutsideClick={() => setIsEditMode(false)}
|
|
||||||
isEditMode={isEditMode}
|
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<StyledEditInplaceInput
|
<StyledEditInplaceInput
|
||||||
|
|||||||
@ -13,10 +13,6 @@ type OwnProps = {
|
|||||||
changeHandler: (updated: string) => void;
|
changeHandler: (updated: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyledEditModeProps = {
|
|
||||||
isEditMode: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const StyledRawLink = styled(RawLink)`
|
const StyledRawLink = styled(RawLink)`
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@ -28,7 +24,7 @@ const StyledRawLink = styled(RawLink)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// TODO: refactor
|
// TODO: refactor
|
||||||
const StyledEditInplaceInput = styled.input<StyledEditModeProps>`
|
const StyledEditInplaceInput = styled.input`
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
${textInputStyle}
|
${textInputStyle}
|
||||||
@ -37,17 +33,12 @@ const StyledEditInplaceInput = styled.input<StyledEditModeProps>`
|
|||||||
export function EditablePhone({ value, placeholder, changeHandler }: OwnProps) {
|
export function EditablePhone({ value, placeholder, changeHandler }: OwnProps) {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [inputValue, setInputValue] = useState(value);
|
const [inputValue, setInputValue] = useState(value);
|
||||||
const [isEditMode, setIsEditMode] = useState(false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
isEditMode={isEditMode}
|
|
||||||
onOutsideClick={() => setIsEditMode(false)}
|
|
||||||
onInsideClick={() => setIsEditMode(true)}
|
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<StyledEditInplaceInput
|
<StyledEditInplaceInput
|
||||||
autoFocus
|
autoFocus
|
||||||
isEditMode={isEditMode}
|
|
||||||
placeholder={placeholder || ''}
|
placeholder={placeholder || ''}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
|
|||||||
@ -12,12 +12,8 @@ type OwnProps = {
|
|||||||
editModeHorizontalAlign?: 'left' | 'right';
|
editModeHorizontalAlign?: 'left' | 'right';
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyledEditModeProps = {
|
|
||||||
isEditMode: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: refactor
|
// TODO: refactor
|
||||||
const StyledInplaceInput = styled.input<StyledEditModeProps>`
|
const StyledInplaceInput = styled.input`
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
${textInputStyle}
|
${textInputStyle}
|
||||||
@ -38,17 +34,12 @@ export function EditableText({
|
|||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [inputValue, setInputValue] = useState(content);
|
const [inputValue, setInputValue] = useState(content);
|
||||||
const [isEditMode, setIsEditMode] = useState(false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
isEditMode={isEditMode}
|
|
||||||
onOutsideClick={() => setIsEditMode(false)}
|
|
||||||
onInsideClick={() => setIsEditMode(true)}
|
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<StyledInplaceInput
|
<StyledInplaceInput
|
||||||
isEditMode={isEditMode}
|
|
||||||
placeholder={placeholder || ''}
|
placeholder={placeholder || ''}
|
||||||
autoFocus
|
autoFocus
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FilterConfigType,
|
FilterConfigType,
|
||||||
@ -16,12 +16,13 @@ import {
|
|||||||
SelectedSortType,
|
SelectedSortType,
|
||||||
SortType,
|
SortType,
|
||||||
} from '@/filters-and-sorts/interfaces/sorts/interface';
|
} 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 { currentRowSelectionState } from '../../tables/states/rowSelectionState';
|
||||||
|
|
||||||
import { TableHeader } from './table-header/TableHeader';
|
import { TableHeader } from './table-header/TableHeader';
|
||||||
|
import { EntityTableRow } from './EntityTableRow';
|
||||||
|
|
||||||
type OwnProps<TData extends { id: string }, SortField> = {
|
type OwnProps<TData extends { id: string }, SortField> = {
|
||||||
data: Array<TData>;
|
data: Array<TData>;
|
||||||
@ -100,11 +101,6 @@ const StyledTableScrollableContainer = styled.div`
|
|||||||
overflow: auto;
|
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>({
|
export function EntityTable<TData extends { id: string }, SortField>({
|
||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
@ -118,13 +114,6 @@ export function EntityTable<TData extends { id: string }, SortField>({
|
|||||||
const [currentRowSelection, setCurrentRowSelection] = useRecoilState(
|
const [currentRowSelection, setCurrentRowSelection] = useRecoilState(
|
||||||
currentRowSelectionState,
|
currentRowSelectionState,
|
||||||
);
|
);
|
||||||
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
|
||||||
|
|
||||||
const resetTableRowSelection = useResetTableRowSelection();
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
resetTableRowSelection();
|
|
||||||
}, [resetTableRowSelection]);
|
|
||||||
|
|
||||||
const table = useReactTable<TData>({
|
const table = useReactTable<TData>({
|
||||||
data,
|
data,
|
||||||
@ -138,16 +127,6 @@ export function EntityTable<TData extends { id: string }, SortField>({
|
|||||||
getRowId: (row) => row.id,
|
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 (
|
return (
|
||||||
<StyledTableWithHeader>
|
<StyledTableWithHeader>
|
||||||
<TableHeader
|
<TableHeader
|
||||||
@ -186,33 +165,9 @@ export function EntityTable<TData extends { id: string }, SortField>({
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{table.getRowModel().rows.map((row, index) => (
|
{table.getRowModel().rows.map((row, index) => (
|
||||||
<StyledRow
|
<RecoilScope SpecificContext={RowContext} key={row.id}>
|
||||||
key={row.id}
|
<EntityTableRow row={row} index={index} />
|
||||||
data-testid={`row-id-${row.index}`}
|
</RecoilScope>
|
||||||
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>
|
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</StyledTable>
|
</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 { ReactNode, useRef } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
|
||||||
import { IconChevronDown } from '@/ui/icons/index';
|
import { IconChevronDown } from '@/ui/icons/index';
|
||||||
import { overlayBackground, textInputStyle } from '@/ui/themes/effects';
|
import { overlayBackground, textInputStyle } from '@/ui/themes/effects';
|
||||||
|
|
||||||
@ -159,11 +161,17 @@ function DropdownButton({
|
|||||||
setIsUnfolded,
|
setIsUnfolded,
|
||||||
resetState,
|
resetState,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
|
const [, setCaptureHotkeyTypeInFocus] = useRecoilState(
|
||||||
|
captureHotkeyTypeInFocusState,
|
||||||
|
);
|
||||||
|
|
||||||
const onButtonClick = () => {
|
const onButtonClick = () => {
|
||||||
setIsUnfolded && setIsUnfolded(!isUnfolded);
|
setIsUnfolded && setIsUnfolded(!isUnfolded);
|
||||||
|
setCaptureHotkeyTypeInFocus(!isUnfolded);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOutsideClick = () => {
|
const onOutsideClick = () => {
|
||||||
|
setCaptureHotkeyTypeInFocus(false);
|
||||||
setIsUnfolded && setIsUnfolded(false);
|
setIsUnfolded && setIsUnfolded(false);
|
||||||
resetState && resetState();
|
resetState && resetState();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ApolloProvider } from '@apollo/client';
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { userEvent, within } from '@storybook/testing-library';
|
import { userEvent, within } from '@storybook/testing-library';
|
||||||
|
|
||||||
import { IconList } from '@/ui/icons/index';
|
import { IconList } from '@/ui/icons/index';
|
||||||
import { FullHeightStorybookLayout } from '~/testing/FullHeightStorybookLayout';
|
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
||||||
import { mockedClient } from '~/testing/mockedClient';
|
|
||||||
|
|
||||||
import { availableFilters } from '../../../../../../pages/companies/companies-filters';
|
import { availableFilters } from '../../../../../../pages/companies/companies-filters';
|
||||||
import { availableSorts } from '../../../../../../pages/companies/companies-sorts';
|
import { availableSorts } from '../../../../../../pages/companies/companies-sorts';
|
||||||
@ -20,32 +18,24 @@ export default meta;
|
|||||||
type Story = StoryObj<typeof TableHeader>;
|
type Story = StoryObj<typeof TableHeader>;
|
||||||
|
|
||||||
export const Empty: Story = {
|
export const Empty: Story = {
|
||||||
render: () => (
|
render: getRenderWrapperForComponent(
|
||||||
<ApolloProvider client={mockedClient}>
|
<TableHeader
|
||||||
<FullHeightStorybookLayout>
|
viewName="ViewName"
|
||||||
<TableHeader
|
viewIcon={<IconList />}
|
||||||
viewName="ViewName"
|
availableSorts={availableSorts}
|
||||||
viewIcon={<IconList />}
|
availableFilters={availableFilters}
|
||||||
availableSorts={availableSorts}
|
/>,
|
||||||
availableFilters={availableFilters}
|
|
||||||
/>
|
|
||||||
</FullHeightStorybookLayout>
|
|
||||||
</ApolloProvider>
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithSortsAndFilters: Story = {
|
export const WithSortsAndFilters: Story = {
|
||||||
render: () => (
|
render: getRenderWrapperForComponent(
|
||||||
<ApolloProvider client={mockedClient}>
|
<TableHeader
|
||||||
<FullHeightStorybookLayout>
|
viewName="ViewName"
|
||||||
<TableHeader
|
viewIcon={<IconList />}
|
||||||
viewName="ViewName"
|
availableSorts={availableSorts}
|
||||||
viewIcon={<IconList />}
|
availableFilters={availableFilters}
|
||||||
availableSorts={availableSorts}
|
/>,
|
||||||
availableFilters={availableFilters}
|
|
||||||
/>
|
|
||||||
</FullHeightStorybookLayout>
|
|
||||||
</ApolloProvider>
|
|
||||||
),
|
),
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(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 { SelectedFilterType } from '@/filters-and-sorts/interfaces/filters/interface';
|
||||||
import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTableActionBar';
|
import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTableActionBar';
|
||||||
import { EntityTable } from '@/ui/components/table/EntityTable';
|
import { EntityTable } from '@/ui/components/table/EntityTable';
|
||||||
|
import { HooksEntityTable } from '@/ui/components/table/HooksEntityTable';
|
||||||
import { IconBuildingSkyscraper } from '@/ui/icons/index';
|
import { IconBuildingSkyscraper } from '@/ui/icons/index';
|
||||||
import { IconList } from '@/ui/icons/index';
|
import { IconList } from '@/ui/icons/index';
|
||||||
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
||||||
@ -85,6 +86,10 @@ export function Companies() {
|
|||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<StyledCompaniesContainer>
|
<StyledCompaniesContainer>
|
||||||
|
<HooksEntityTable
|
||||||
|
numberOfColumns={companiesColumns.length}
|
||||||
|
numberOfRows={companies.length}
|
||||||
|
/>
|
||||||
<EntityTable
|
<EntityTable
|
||||||
data={companies}
|
data={companies}
|
||||||
columns={companiesColumns}
|
columns={companiesColumns}
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { CompanyEditableNameChipCell } from '@/companies/components/CompanyEdita
|
|||||||
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
|
import { EditableDate } from '@/ui/components/editable-cell/types/EditableDate';
|
||||||
import { EditableText } from '@/ui/components/editable-cell/types/EditableText';
|
import { EditableText } from '@/ui/components/editable-cell/types/EditableText';
|
||||||
import { ColumnHead } from '@/ui/components/table/ColumnHead';
|
import { ColumnHead } from '@/ui/components/table/ColumnHead';
|
||||||
import { RecoilScope } from '@/ui/hooks/RecoilScope';
|
|
||||||
import {
|
import {
|
||||||
IconBuildingSkyscraper,
|
IconBuildingSkyscraper,
|
||||||
IconCalendarEvent,
|
IconCalendarEvent,
|
||||||
@ -143,9 +142,7 @@ export const useCompaniesColumns = () => {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
cell: (props) => (
|
cell: (props) => (
|
||||||
<RecoilScope>
|
<CompanyAccountOwnerCell company={props.row.original} />
|
||||||
<CompanyAccountOwnerCell company={props.row.original} />
|
|
||||||
</RecoilScope>
|
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
} from '@/people/services';
|
} from '@/people/services';
|
||||||
import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTableActionBar';
|
import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTableActionBar';
|
||||||
import { EntityTable } from '@/ui/components/table/EntityTable';
|
import { EntityTable } from '@/ui/components/table/EntityTable';
|
||||||
|
import { HooksEntityTable } from '@/ui/components/table/HooksEntityTable';
|
||||||
import { IconList, IconUser } from '@/ui/icons/index';
|
import { IconList, IconUser } from '@/ui/icons/index';
|
||||||
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
||||||
import {
|
import {
|
||||||
@ -76,6 +77,7 @@ export function People() {
|
|||||||
const peopleColumns = usePeopleColumns();
|
const peopleColumns = usePeopleColumns();
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WithTopBarContainer
|
<WithTopBarContainer
|
||||||
title="People"
|
title="People"
|
||||||
@ -84,6 +86,10 @@ export function People() {
|
|||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<StyledPeopleContainer>
|
<StyledPeopleContainer>
|
||||||
|
<HooksEntityTable
|
||||||
|
numberOfColumns={peopleColumns.length}
|
||||||
|
numberOfRows={people.length}
|
||||||
|
/>
|
||||||
<EntityTable
|
<EntityTable
|
||||||
data={people}
|
data={people}
|
||||||
columns={peopleColumns}
|
columns={peopleColumns}
|
||||||
|
|||||||
@ -52,11 +52,15 @@ export const InteractWithManyRows: Story = {
|
|||||||
|
|
||||||
await sleep(25);
|
await sleep(25);
|
||||||
|
|
||||||
|
const secondRowEmailCellFocused = await canvas.findByText(
|
||||||
|
mockedPeopleData[1].email,
|
||||||
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
canvas.queryByTestId('editable-cell-edit-mode-container'),
|
canvas.queryByTestId('editable-cell-edit-mode-container'),
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
|
|
||||||
await userEvent.click(secondRowEmailCell);
|
await userEvent.click(secondRowEmailCellFocused);
|
||||||
|
|
||||||
await sleep(25);
|
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 { EditablePhone } from '@/ui/components/editable-cell/types/EditablePhone';
|
||||||
import { EditableText } from '@/ui/components/editable-cell/types/EditableText';
|
import { EditableText } from '@/ui/components/editable-cell/types/EditableText';
|
||||||
import { ColumnHead } from '@/ui/components/table/ColumnHead';
|
import { ColumnHead } from '@/ui/components/table/ColumnHead';
|
||||||
import { RecoilScope } from '@/ui/hooks/RecoilScope';
|
|
||||||
import {
|
import {
|
||||||
IconBuildingSkyscraper,
|
IconBuildingSkyscraper,
|
||||||
IconCalendarEvent,
|
IconCalendarEvent,
|
||||||
@ -80,11 +79,7 @@ export const usePeopleColumns = () => {
|
|||||||
viewIcon={<IconBuildingSkyscraper size={16} />}
|
viewIcon={<IconBuildingSkyscraper size={16} />}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
cell: (props) => (
|
cell: (props) => <PeopleCompanyCell people={props.row.original} />,
|
||||||
<RecoilScope>
|
|
||||||
<PeopleCompanyCell people={props.row.original} />
|
|
||||||
</RecoilScope>
|
|
||||||
),
|
|
||||||
size: 150,
|
size: 150,
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('phone', {
|
columnHelper.accessor('phone', {
|
||||||
|
|||||||
Reference in New Issue
Block a user