FieldDisplay & FieldInput (#1708)

* Removed view field duplicate types

* wip

* wip 2

* wip 3

* Unified state for fields

* Renaming

* Wip

* Post merge

* Post post merge

* wip

* Delete unused file

* Boolean and Probability

* Finished InlineCell

* Renamed EditableCell to TableCell

* Finished double texts

* Finished MoneyField

* Fixed bug inline cell click outside

* Fixed hotkey scope

* Final fixes

* Phone

* Fix url and number input validation

* Fix

* Fix position

* wip refactor activity editor

* Fixed activity editor

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-09-27 18:18:02 +02:00
committed by GitHub
parent d9feabbc63
commit cbadcba188
290 changed files with 3152 additions and 4481 deletions

View File

@ -1,24 +0,0 @@
import { useEditableCell } from '../hooks/useEditableCell';
import { useSetSoftFocusOnCurrentCell } from '../hooks/useSetSoftFocusOnCurrentCell';
import { EditableCellDisplayContainer } from './EditableCellDisplayContainer';
export const EditableCellDisplayMode = ({
children,
isHovered,
}: React.PropsWithChildren<unknown> & { isHovered?: boolean }) => {
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
const { openEditableCell } = useEditableCell();
const handleClick = () => {
setSoftFocusOnCurrentCell();
openEditableCell();
};
return (
<EditableCellDisplayContainer isHovered={isHovered} onClick={handleClick}>
{children}
</EditableCellDisplayContainer>
);
};

View File

@ -1,68 +0,0 @@
import { isViewFieldBoolean } from '@/ui/editable-field/types/guards/isViewFieldBoolean';
import { isViewFieldChip } from '@/ui/editable-field/types/guards/isViewFieldChip';
import { isViewFieldDate } from '@/ui/editable-field/types/guards/isViewFieldDate';
import { isViewFieldDoubleText } from '@/ui/editable-field/types/guards/isViewFieldDoubleText';
import { isViewFieldDoubleTextChip } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextChip';
import { isViewFieldEmail } from '@/ui/editable-field/types/guards/isViewFieldEmail';
import { isViewFieldMoney } from '@/ui/editable-field/types/guards/isViewFieldMoney';
import { isViewFieldNumber } from '@/ui/editable-field/types/guards/isViewFieldNumber';
import { isViewFieldPhone } from '@/ui/editable-field/types/guards/isViewFieldPhone';
import { isViewFieldRelation } from '@/ui/editable-field/types/guards/isViewFieldRelation';
import { isViewFieldText } from '@/ui/editable-field/types/guards/isViewFieldText';
import { isViewFieldURL } from '@/ui/editable-field/types/guards/isViewFieldURL';
import { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
import { ColumnDefinition } from '../../types/ColumnDefinition';
import { GenericEditableBooleanCell } from '../type/components/GenericEditableBooleanCell';
import { GenericEditableChipCell } from '../type/components/GenericEditableChipCell';
import { GenericEditableDateCell } from '../type/components/GenericEditableDateCell';
import { GenericEditableDoubleTextCell } from '../type/components/GenericEditableDoubleTextCell';
import { GenericEditableDoubleTextChipCell } from '../type/components/GenericEditableDoubleTextChipCell';
import { GenericEditableEmailCell } from '../type/components/GenericEditableEmailCell';
import { GenericEditableMoneyCell } from '../type/components/GenericEditableMoneyCell';
import { GenericEditableNumberCell } from '../type/components/GenericEditableNumberCell';
import { GenericEditablePhoneCell } from '../type/components/GenericEditablePhoneCell';
import { GenericEditableRelationCell } from '../type/components/GenericEditableRelationCell';
import { GenericEditableTextCell } from '../type/components/GenericEditableTextCell';
import { GenericEditableURLCell } from '../type/components/GenericEditableURLCell';
type OwnProps = {
columnDefinition: ColumnDefinition<ViewFieldMetadata>;
};
export const GenericEditableCell = ({ columnDefinition }: OwnProps) => {
if (isViewFieldEmail(columnDefinition)) {
return <GenericEditableEmailCell columnDefinition={columnDefinition} />;
} else if (isViewFieldText(columnDefinition)) {
return <GenericEditableTextCell columnDefinition={columnDefinition} />;
} else if (isViewFieldRelation(columnDefinition)) {
return <GenericEditableRelationCell columnDefinition={columnDefinition} />;
} else if (isViewFieldDoubleTextChip(columnDefinition)) {
return (
<GenericEditableDoubleTextChipCell columnDefinition={columnDefinition} />
);
} else if (isViewFieldDoubleText(columnDefinition)) {
return (
<GenericEditableDoubleTextCell columnDefinition={columnDefinition} />
);
} else if (isViewFieldPhone(columnDefinition)) {
return <GenericEditablePhoneCell columnDefinition={columnDefinition} />;
} else if (isViewFieldURL(columnDefinition)) {
return <GenericEditableURLCell columnDefinition={columnDefinition} />;
} else if (isViewFieldDate(columnDefinition)) {
return <GenericEditableDateCell columnDefinition={columnDefinition} />;
} else if (isViewFieldNumber(columnDefinition)) {
return <GenericEditableNumberCell columnDefinition={columnDefinition} />;
} else if (isViewFieldBoolean(columnDefinition)) {
return <GenericEditableBooleanCell columnDefinition={columnDefinition} />;
} else if (isViewFieldChip(columnDefinition)) {
return <GenericEditableChipCell columnDefinition={columnDefinition} />;
} else if (isViewFieldMoney(columnDefinition)) {
return <GenericEditableMoneyCell columnDefinition={columnDefinition} />;
} else {
console.warn(
`Unknown field metadata type: ${columnDefinition.metadata.type} in GenericEditableCell`,
);
return <></>;
}
};

View File

@ -0,0 +1,74 @@
import { useContext } from 'react';
import { FieldDisplay } from '@/ui/field/components/FieldDisplay';
import { FieldInput } from '@/ui/field/components/FieldInput';
import { FieldContext } from '@/ui/field/contexts/FieldContext';
import { FieldInputEvent } from '@/ui/field/types/FieldInputEvent';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus';
import { useTableCell } from '../hooks/useTableCell';
import { TableCellContainer } from './TableCellContainer';
export const TableCell = ({
customHotkeyScope,
}: {
customHotkeyScope: HotkeyScope;
}) => {
const { fieldDefinition } = useContext(FieldContext);
const { closeTableCell } = useTableCell();
const { moveLeft, moveRight, moveDown } = useMoveSoftFocus();
const handleEnter: FieldInputEvent = (persistField) => {
persistField();
closeTableCell();
moveDown();
};
const handleSubmit: FieldInputEvent = (persistField) => {
persistField();
closeTableCell();
};
const handleCancel = () => {
closeTableCell();
};
const handleEscape = () => {
closeTableCell();
};
const handleTab: FieldInputEvent = (persistField) => {
persistField();
closeTableCell();
moveRight();
};
const handleShiftTab: FieldInputEvent = (persistField) => {
persistField();
closeTableCell();
moveLeft();
};
return (
<TableCellContainer
editHotkeyScope={customHotkeyScope}
editModeContent={
<FieldInput
onCancel={handleCancel}
onClickOutside={handleCancel}
onEnter={handleEnter}
onEscape={handleEscape}
onShiftTab={handleShiftTab}
onSubmit={handleSubmit}
onTab={handleTab}
/>
}
nonEditModeContent={<FieldDisplay />}
useEditButton={fieldDefinition.useEditButton}
></TableCellContainer>
);
};

View File

@ -1,19 +1,20 @@
import { ReactElement, useState } from 'react';
import styled from '@emotion/styled';
import { useIsFieldInputOnly } from '@/ui/field/hooks/useIsFieldInputOnly';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentCellEditMode } from '../hooks/useCurrentCellEditMode';
import { useEditableCell } from '../hooks/useEditableCell';
import { useIsSoftFocusOnCurrentCell } from '../hooks/useIsSoftFocusOnCurrentCell';
import { useSetSoftFocusOnCurrentCell } from '../hooks/useSetSoftFocusOnCurrentCell';
import { useCurrentTableCellEditMode } from '../hooks/useCurrentTableCellEditMode';
import { useIsSoftFocusOnCurrentTableCell } from '../hooks/useIsSoftFocusOnCurrentTableCell';
import { useSetSoftFocusOnCurrentTableCell } from '../hooks/useSetSoftFocusOnCurrentTableCell';
import { useTableCell } from '../hooks/useTableCell';
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
import { EditableCellEditButton } from './EditableCellEditButton';
import { EditableCellEditMode } from './EditableCellEditMode';
import { EditableCellSoftFocusMode } from './EditableCellSoftFocusMode';
import { TableCellDisplayMode } from './TableCellDisplayMode';
import { TableCellEditButton } from './TableCellEditButton';
import { TableCellEditMode } from './TableCellEditMode';
import { TableCellSoftFocusMode } from './TableCellSoftFocusMode';
const StyledCellBaseContainer = styled.div`
align-items: center;
@ -43,7 +44,7 @@ const DEFAULT_CELL_SCOPE: HotkeyScope = {
scope: TableHotkeyScope.CellEditMode,
};
export const EditableCell = ({
export const TableCellContainer = ({
editModeHorizontalAlign = 'left',
editModeVerticalPosition = 'over',
editModeContent,
@ -53,16 +54,16 @@ export const EditableCell = ({
maxContentWidth,
useEditButton,
}: EditableCellProps) => {
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
const { isCurrentTableCellInEditMode } = useCurrentTableCellEditMode();
const [isHovered, setIsHovered] = useState(false);
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
const setSoftFocusOnCurrentTableCell = useSetSoftFocusOnCurrentTableCell();
const { openEditableCell } = useEditableCell();
const { openTableCell } = useTableCell();
const handlePenClick = () => {
setSoftFocusOnCurrentCell();
openEditableCell();
setSoftFocusOnCurrentTableCell();
openTableCell();
};
const handleContainerMouseEnter = () => {
@ -73,9 +74,15 @@ export const EditableCell = ({
setIsHovered(false);
};
const showEditButton = useEditButton && isHovered && !isCurrentCellInEditMode;
const editModeContentOnly = useIsFieldInputOnly();
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
const showEditButton =
useEditButton &&
isHovered &&
!isCurrentTableCellInEditMode &&
!editModeContentOnly;
const hasSoftFocus = useIsSoftFocusOnCurrentTableCell();
return (
<CellHotkeyScopeContext.Provider
@ -85,32 +92,28 @@ export const EditableCell = ({
onMouseEnter={handleContainerMouseEnter}
onMouseLeave={handleContainerMouseLeave}
>
{isCurrentCellInEditMode ? (
<EditableCellEditMode
{isCurrentTableCellInEditMode ? (
<TableCellEditMode
maxContentWidth={maxContentWidth}
transparent={transparent}
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
>
{editModeContent}
</EditableCellEditMode>
</TableCellEditMode>
) : hasSoftFocus ? (
<>
{showEditButton && (
<EditableCellEditButton onClick={handlePenClick} />
)}
<EditableCellSoftFocusMode>
{nonEditModeContent}
</EditableCellSoftFocusMode>
{showEditButton && <TableCellEditButton onClick={handlePenClick} />}
<TableCellSoftFocusMode>
{editModeContentOnly ? editModeContent : nonEditModeContent}
</TableCellSoftFocusMode>
</>
) : (
<>
{showEditButton && (
<EditableCellEditButton onClick={handlePenClick} />
)}
<EditableCellDisplayMode isHovered={isHovered}>
{nonEditModeContent}
</EditableCellDisplayMode>
{showEditButton && <TableCellEditButton onClick={handlePenClick} />}
<TableCellDisplayMode isHovered={isHovered}>
{editModeContentOnly ? editModeContent : nonEditModeContent}
</TableCellDisplayMode>
</>
)}
</StyledCellBaseContainer>

View File

@ -36,7 +36,7 @@ const StyledEditableCellDisplayModeInnerContainer = styled.div`
width: 100%;
`;
export const EditableCellDisplayContainer = ({
export const TableCellDisplayContainer = ({
children,
softFocus,
onClick,

View File

@ -0,0 +1,31 @@
import { useIsFieldInputOnly } from '@/ui/field/hooks/useIsFieldInputOnly';
import { useSetSoftFocusOnCurrentTableCell } from '../hooks/useSetSoftFocusOnCurrentTableCell';
import { useTableCell } from '../hooks/useTableCell';
import { TableCellDisplayContainer } from './TableCellDisplayContainer';
export const TableCellDisplayMode = ({
children,
isHovered,
}: React.PropsWithChildren<unknown> & { isHovered?: boolean }) => {
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentTableCell();
const isFieldInputOnly = useIsFieldInputOnly();
const { openTableCell } = useTableCell();
const handleClick = () => {
setSoftFocusOnCurrentCell();
if (!isFieldInputOnly) {
openTableCell();
}
};
return (
<TableCellDisplayContainer isHovered={isHovered} onClick={handleClick}>
{children}
</TableCellDisplayContainer>
);
};

View File

@ -13,7 +13,7 @@ type EditableCellEditButtonProps = {
onClick?: () => void;
};
export const EditableCellEditButton = ({
export const TableCellEditButton = ({
onClick,
}: EditableCellEditButtonProps) => (
<StyledEditButtonContainer

View File

@ -37,7 +37,7 @@ export type EditableCellEditModeProps = {
initialValue?: string;
};
export const EditableCellEditMode = ({
export const TableCellEditMode = ({
editModeHorizontalAlign,
editModeVerticalPosition,
children,

View File

@ -1,17 +1,20 @@
import { PropsWithChildren, useEffect, useRef } from 'react';
import { useIsFieldInputOnly } from '@/ui/field/hooks/useIsFieldInputOnly';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useEditableCell } from '../hooks/useEditableCell';
import { useTableCell } from '../hooks/useTableCell';
import { EditableCellDisplayContainer } from './EditableCellDisplayContainer';
import { TableCellDisplayContainer } from './TableCellDisplayContainer';
type OwnProps = PropsWithChildren<unknown>;
export const EditableCellSoftFocusMode = ({ children }: OwnProps) => {
const { openEditableCell } = useEditableCell();
export const TableCellSoftFocusMode = ({ children }: OwnProps) => {
const { openTableCell } = useTableCell();
const isFieldInputOnly = useIsFieldInputOnly();
const scrollRef = useRef<HTMLDivElement>(null);
@ -19,17 +22,16 @@ export const EditableCellSoftFocusMode = ({ children }: OwnProps) => {
scrollRef.current?.scrollIntoView({ block: 'nearest' });
}, []);
const openEditMode = () => {
openEditableCell();
};
useScopedHotkeys(
'enter',
() => {
openEditMode();
openTableCell();
},
TableHotkeyScope.TableSoftFocus,
[openEditMode],
[openTableCell],
{
enabled: !isFieldInputOnly,
},
);
useScopedHotkeys(
@ -44,26 +46,29 @@ export const EditableCellSoftFocusMode = ({ children }: OwnProps) => {
return;
}
openEditMode();
openTableCell();
},
TableHotkeyScope.TableSoftFocus,
[openEditMode],
[openTableCell],
{
preventDefault: false,
enabled: !isFieldInputOnly,
},
);
const handleClick = () => {
openEditMode();
if (!isFieldInputOnly) {
openTableCell();
}
};
return (
<EditableCellDisplayContainer
<TableCellDisplayContainer
onClick={handleClick}
softFocus
scrollRef={scrollRef}
>
{children}
</EditableCellDisplayContainer>
</TableCellDisplayContainer>
);
};