From cd0f9aebc046cb033e234e67807a4ff22b95f692 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 14 Aug 2025 11:06:37 +0200 Subject: [PATCH] Refactor inline-cell inputs (#13914) Continue the work so that inline-cell do not embed their own input which is inefficient when we have thousands of inline-cell on the table --- .../RecordFieldListCellAnchoredPortal.tsx | 118 ------------------ .../RecordFieldListCellEditModePortal.tsx | 45 +++++++ ...cordFieldListCellEditModePortalContent.tsx | 10 ++ .../RecordFieldListCellHoveredPortal.tsx | 18 ++- ...ecordFieldListCellHoveredPortalContent.tsx | 57 ++++----- .../RecordFieldListInputContextProvider.tsx} | 50 +++++--- .../RecordInlineCellAnchoredPortal.tsx | 108 ++++++++++++++++ ...RecordInlineCellAnchoredPortalContext.tsx} | 6 +- .../components/RecordInlineCellContainer.tsx | 2 +- .../RecordInlineCellDisplayMode.tsx | 21 ++-- .../components/RecordInlineCellEditButton.tsx | 4 +- .../RecordInlineCellHoveredPortalContent.tsx | 23 ++-- .../components/RecordInlineCellValue.tsx | 21 +--- .../record-inline-cell/hooks/useInlineCell.ts | 2 +- .../record-show/components/FieldsCard.tsx | 12 +- 15 files changed, 279 insertions(+), 218 deletions(-) delete mode 100644 packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellAnchoredPortal.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortal.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortalContent.tsx rename packages/twenty-front/src/modules/object-record/record-field-list/{components/RecordFieldListCellFieldInput.tsx => anchored-portal/components/RecordFieldListInputContextProvider.tsx} (64%) create mode 100644 packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal.tsx rename packages/twenty-front/src/modules/object-record/{record-field-list/anchored-portal/components/RecordFieldListCellAnchorPortalContext.tsx => record-inline-cell/components/RecordInlineCellAnchoredPortalContext.tsx} (91%) diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellAnchoredPortal.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellAnchoredPortal.tsx deleted file mode 100644 index 1de8a86861..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellAnchoredPortal.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow'; -import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; -import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; -import { RecordFieldListCellAnchorPortalContext } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellAnchorPortalContext'; -import { - FieldContext, - type RecordUpdateHook, - type RecordUpdateHookParams, -} from '@/object-record/record-field/ui/contexts/FieldContext'; -import { useIsRecordFieldReadOnly } from '@/object-record/record-field/ui/hooks/read-only/useIsRecordFieldReadOnly'; -import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext'; -import { RecordInlineCellHoveredPortal } from '@/object-record/record-inline-cell/components/RecordInlineCellHoveredPortal'; -import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { createPortal } from 'react-dom'; -import { isDefined } from 'twenty-shared/utils'; - -type RecordFieldListCellAnchoredPortalProps = { - position: number; - fieldMetadataItem: Pick< - FieldMetadataItem, - 'id' | 'name' | 'type' | 'createdAt' | 'updatedAt' | 'label' - >; - children: React.ReactNode; -}; - -export const RecordFieldListCellAnchoredPortal = ({ - position, - fieldMetadataItem, - children, -}: RecordFieldListCellAnchoredPortalProps) => { - const anchorElement = document.body.querySelector( - `#record-field-list-cell-${position}`, - ); - - const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow(); - - const targetedRecordsRuleFromContextStore = useRecoilComponentValue( - contextStoreTargetedRecordsRuleComponentState, - ); - - let recordId; - - if (targetedRecordsRuleFromContextStore.mode === 'selection') { - recordId = targetedRecordsRuleFromContextStore.selectedRecordIds[0]; - } - - const INPUT_ID_PREFIX = 'fields-card'; - - const isRecordFieldReadOnly = useIsRecordFieldReadOnly({ - fieldMetadataId: fieldMetadataItem.id, - objectMetadataId: objectMetadataItem.id, - recordId: recordId ?? '', - }); - - const { updateOneRecord } = useUpdateOneRecord({ - objectNameSingular: objectMetadataItem.nameSingular, - }); - - const useUpdateOneObjectRecordMutation: RecordUpdateHook = () => { - const updateEntity = ({ variables }: RecordUpdateHookParams) => { - updateOneRecord?.({ - idToUpdate: variables.where.id as string, - updateOneRecordInput: variables.updateOneRecordInput, - }); - }; - - return [updateEntity, { loading: false }]; - }; - - if (!isDefined(anchorElement) || !isDefined(recordId)) { - return null; - } - - return ( - - <> - {createPortal( - - - - {children} - - - , - anchorElement, - )} - - - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortal.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortal.tsx new file mode 100644 index 0000000000..5c381abdc3 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortal.tsx @@ -0,0 +1,45 @@ +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; + +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { RecordFieldListCellEditModePortalContent } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortalContent'; +import { RecordFieldListInputContextProvider } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListInputContextProvider'; +import { useFieldListFieldMetadataFromPosition } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataFromPosition'; +import { recordFieldListCellEditModePositionComponentState } from '@/object-record/record-field-list/states/recordFieldListCellEditModePositionComponentState'; +import { RecordInlineCellAnchoredPortal } from '@/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal'; +import { isDefined } from 'twenty-shared/utils'; + +type RecordFieldListCellEditModePortalProps = { + objectMetadataItem: ObjectMetadataItem; + recordId: string; +}; + +export const RecordFieldListCellEditModePortal = ({ + objectMetadataItem, + recordId, +}: RecordFieldListCellEditModePortalProps) => { + const editModePosition = useRecoilComponentValue( + recordFieldListCellEditModePositionComponentState, + ); + + const { editedFieldMetadataItem } = useFieldListFieldMetadataFromPosition({ + objectNameSingular: objectMetadataItem.nameSingular, + }); + + if (!isDefined(editModePosition) || !isDefined(editedFieldMetadataItem)) { + return null; + } + + return ( + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortalContent.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortalContent.tsx new file mode 100644 index 0000000000..9cda5b1757 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortalContent.tsx @@ -0,0 +1,10 @@ +import { FieldInput } from '@/object-record/record-field/ui/components/FieldInput'; +import { RecordInlineCellEditMode } from '@/object-record/record-inline-cell/components/RecordInlineCellEditMode'; + +export const RecordFieldListCellEditModePortalContent = () => { + return ( + + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortal.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortal.tsx index 06884a0c87..874858985a 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortal.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortal.tsx @@ -1,18 +1,21 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { RecordFieldListCellAnchoredPortal } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellAnchoredPortal'; import { RecordFieldListCellHoveredPortalContent } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortalContent'; +import { RecordFieldListInputContextProvider } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListInputContextProvider'; import { useFieldListFieldMetadataFromPosition } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataFromPosition'; import { recordFieldListHoverPositionComponentState } from '@/object-record/record-field-list/states/recordFieldListHoverPositionComponentState'; +import { RecordInlineCellAnchoredPortal } from '@/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal'; import { isDefined } from 'twenty-shared/utils'; type RecordFieldListCellHoveredPortalProps = { objectMetadataItem: ObjectMetadataItem; + recordId: string; }; export const RecordFieldListCellHoveredPortal = ({ objectMetadataItem, + recordId, }: RecordFieldListCellHoveredPortalProps) => { const hoverPosition = useRecoilComponentValue( recordFieldListHoverPositionComponentState, @@ -27,13 +30,16 @@ export const RecordFieldListCellHoveredPortal = ({ } return ( - - - + + + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortalContent.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortalContent.tsx index 021c4c0c38..62b990cae9 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortalContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortalContent.tsx @@ -1,63 +1,60 @@ -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; - -import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useFieldListFieldMetadataFromPosition } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataFromPosition'; +import { recordFieldListCellEditModePositionComponentState } from '@/object-record/record-field-list/states/recordFieldListCellEditModePositionComponentState'; import { recordFieldListHoverPositionComponentState } from '@/object-record/record-field-list/states/recordFieldListHoverPositionComponentState'; import { FieldDisplay } from '@/object-record/record-field/ui/components/FieldDisplay'; import { FieldInput } from '@/object-record/record-field/ui/components/FieldInput'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; +import { useOpenFieldInputEditMode } from '@/object-record/record-field/ui/hooks/useOpenFieldInputEditMode'; import { useRecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext'; import { RecordInlineCellDisplayMode } from '@/object-record/record-inline-cell/components/RecordInlineCellDisplayMode'; import { RecordInlineCellHoveredPortalContent } from '@/object-record/record-inline-cell/components/RecordInlineCellHoveredPortalContent'; import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell'; +import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { useContext } from 'react'; -import { isDefined } from 'twenty-shared/utils'; - -type RecordFieldListCellHoveredPortalContentProps = { - objectMetadataItem: ObjectMetadataItem; -}; - -export const RecordFieldListCellHoveredPortalContent = ({ - objectMetadataItem, -}: RecordFieldListCellHoveredPortalContentProps) => { - const hoverPosition = useRecoilComponentValue( - recordFieldListHoverPositionComponentState, - ); +export const RecordFieldListCellHoveredPortalContent = () => { const { editModeContentOnly, isCentered } = useRecordInlineCellContext(); - const { hoveredFieldMetadataItem } = useFieldListFieldMetadataFromPosition({ - objectNameSingular: objectMetadataItem.nameSingular, - }); - - const { isRecordFieldReadOnly } = useContext(FieldContext); + const { isRecordFieldReadOnly, recordId, fieldDefinition } = + useContext(FieldContext); const { openInlineCell } = useInlineCell(); const shouldContainerBeClickable = !isRecordFieldReadOnly && !editModeContentOnly; - const setRecordFieldListHoverPosition = useSetRecoilComponentState( - recordFieldListHoverPositionComponentState, - 'fields-card', + const [recordFieldListHoverPosition, setRecordFieldListHoverPosition] = + useRecoilComponentState(recordFieldListHoverPositionComponentState); + + const setRecordFieldListCellEditModePosition = useSetRecoilComponentState( + recordFieldListCellEditModePositionComponentState, ); + const { openFieldInput } = useOpenFieldInputEditMode(); + + const handleClick = () => { + if (shouldContainerBeClickable) { + openInlineCell(); + setRecordFieldListCellEditModePosition(recordFieldListHoverPosition); + + openFieldInput({ + fieldDefinition, + recordId, + prefix: 'inline-cell', + }); + } + }; const handleMouseLeave = () => { setRecordFieldListHoverPosition(null); }; - if (!isDefined(hoverPosition) || !isDefined(hoveredFieldMetadataItem)) { - return null; - } - return ( - + {editModeContentOnly ? : } diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/components/RecordFieldListCellFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListInputContextProvider.tsx similarity index 64% rename from packages/twenty-front/src/modules/object-record/record-field-list/components/RecordFieldListCellFieldInput.tsx rename to packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListInputContextProvider.tsx index 4023e4b0ac..af354566a7 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/components/RecordFieldListCellFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListInputContextProvider.tsx @@ -1,20 +1,35 @@ -import { FieldInput } from '@/object-record/record-field/ui/components/FieldInput'; - import { usePersistFieldFromFieldInputContext } from '@/object-record/record-field/ui/hooks/usePersistFieldFromFieldInputContext'; import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext'; +import { recordFieldListCellEditModePositionComponentState } from '@/object-record/record-field-list/states/recordFieldListCellEditModePositionComponentState'; import { FieldInputEventContext, type FieldInputClickOutsideEvent, type FieldInputEvent, } from '@/object-record/record-field/ui/contexts/FieldInputEventContext'; -import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext'; +import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell'; import { currentFocusIdSelector } from '@/ui/utilities/focus/states/currentFocusIdSelector'; import { useAvailableComponentInstanceId } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceId'; +import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { useCallback } from 'react'; import { useRecoilCallback } from 'recoil'; -export const RecordTableCellFieldInput = () => { - const { onMoveFocus, onCloseTableCell } = useRecordTableBodyContextOrThrow(); +type RecordFieldListInputContextProviderProps = { + children: React.ReactNode; +}; + +export const RecordFieldListInputContextProvider = ({ + children, +}: RecordFieldListInputContextProviderProps) => { + const { closeInlineCell } = useInlineCell(); + const setRecordFieldListCellEditModePosition = useSetRecoilComponentState( + recordFieldListCellEditModePositionComponentState, + ); + + const closeInlineCellAndResetEditModePosition = useCallback(() => { + setRecordFieldListCellEditModePosition(null); + closeInlineCell(); + }, [closeInlineCell, setRecordFieldListCellEditModePosition]); const instanceId = useAvailableComponentInstanceId( RecordFieldComponentInstanceContext, @@ -28,8 +43,7 @@ export const RecordTableCellFieldInput = () => { persistFieldFromFieldInputContext(newValue); } - onCloseTableCell(); - onMoveFocus('down'); + closeInlineCellAndResetEditModePosition(); }; const handleSubmit: FieldInputEvent = ({ newValue, skipPersist }) => { @@ -37,11 +51,11 @@ export const RecordTableCellFieldInput = () => { persistFieldFromFieldInputContext(newValue); } - onCloseTableCell(); + closeInlineCellAndResetEditModePosition(); }; const handleCancel = () => { - onCloseTableCell(); + closeInlineCellAndResetEditModePosition(); }; const handleClickOutside: FieldInputClickOutsideEvent = useRecoilCallback( @@ -61,9 +75,13 @@ export const RecordTableCellFieldInput = () => { persistFieldFromFieldInputContext(newValue); } - onCloseTableCell(); + closeInlineCellAndResetEditModePosition(); }, - [onCloseTableCell, instanceId, persistFieldFromFieldInputContext], + [ + closeInlineCellAndResetEditModePosition, + instanceId, + persistFieldFromFieldInputContext, + ], ); const handleEscape: FieldInputEvent = ({ newValue, skipPersist }) => { @@ -71,7 +89,7 @@ export const RecordTableCellFieldInput = () => { persistFieldFromFieldInputContext(newValue); } - onCloseTableCell(); + closeInlineCellAndResetEditModePosition(); }; const handleTab: FieldInputEvent = ({ newValue, skipPersist }) => { @@ -79,8 +97,7 @@ export const RecordTableCellFieldInput = () => { persistFieldFromFieldInputContext(newValue); } - onCloseTableCell(); - onMoveFocus('right'); + closeInlineCellAndResetEditModePosition(); }; const handleShiftTab: FieldInputEvent = ({ newValue, skipPersist }) => { @@ -88,8 +105,7 @@ export const RecordTableCellFieldInput = () => { persistFieldFromFieldInputContext(newValue); } - onCloseTableCell(); - onMoveFocus('left'); + closeInlineCellAndResetEditModePosition(); }; return ( @@ -104,7 +120,7 @@ export const RecordTableCellFieldInput = () => { onTab: handleTab, }} > - + {children} ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal.tsx new file mode 100644 index 0000000000..c167cce131 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal.tsx @@ -0,0 +1,108 @@ +import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; +import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { + FieldContext, + type RecordUpdateHook, + type RecordUpdateHookParams, +} from '@/object-record/record-field/ui/contexts/FieldContext'; +import { FieldFocusContextProvider } from '@/object-record/record-field/ui/contexts/FieldFocusContextProvider'; +import { useIsRecordFieldReadOnly } from '@/object-record/record-field/ui/hooks/read-only/useIsRecordFieldReadOnly'; +import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext'; +import { RecordInlineCellAnchoredPortalContext } from '@/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortalContext'; +import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; +import { createPortal } from 'react-dom'; +import { isDefined } from 'twenty-shared/utils'; + +type RecordInlineCellAnchoredPortalProps = { + position: number; + fieldMetadataItem: Pick< + FieldMetadataItem, + 'id' | 'name' | 'type' | 'createdAt' | 'updatedAt' | 'label' + >; + objectMetadataItem: ObjectMetadataItem; + recordId: string; + anchorIdPrefix: string; + children: React.ReactNode; +}; + +export const RecordInlineCellAnchoredPortal = ({ + position, + fieldMetadataItem, + objectMetadataItem, + recordId, + anchorIdPrefix, + children, +}: RecordInlineCellAnchoredPortalProps) => { + const anchorElement = document.body.querySelector( + `#${anchorIdPrefix}-${position}`, + ); + + const isRecordFieldReadOnly = useIsRecordFieldReadOnly({ + fieldMetadataId: fieldMetadataItem.id, + objectMetadataId: objectMetadataItem.id, + recordId: recordId ?? '', + }); + + const { updateOneRecord } = useUpdateOneRecord({ + objectNameSingular: objectMetadataItem.nameSingular, + }); + + const useUpdateOneObjectRecordMutation: RecordUpdateHook = () => { + const updateEntity = ({ variables }: RecordUpdateHookParams) => { + updateOneRecord?.({ + idToUpdate: variables.where.id as string, + updateOneRecordInput: variables.updateOneRecordInput, + }); + }; + + return [updateEntity, { loading: false }]; + }; + + if (!isDefined(anchorElement) || !isDefined(recordId)) { + return null; + } + + return ( + + + <> + {createPortal( + + + {children} + + , + anchorElement, + )} + + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellAnchorPortalContext.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortalContext.tsx similarity index 91% rename from packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellAnchorPortalContext.tsx rename to packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortalContext.tsx index 7f105c7049..ff99d68d71 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellAnchorPortalContext.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortalContext.tsx @@ -10,13 +10,13 @@ import { import { useContext, type ReactNode } from 'react'; import { useIcons } from 'twenty-ui/display'; -type RecordFieldListCellAnchorPortalContextProps = { +type RecordInlineCellAnchoredPortalContextProps = { children: ReactNode; }; -export const RecordFieldListCellAnchorPortalContext = ({ +export const RecordInlineCellAnchoredPortalContext = ({ children, -}: RecordFieldListCellAnchorPortalContextProps) => { +}: RecordInlineCellAnchoredPortalContextProps) => { const { isRecordFieldReadOnly, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellContainer.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellContainer.tsx index 394c3a4261..44d3198d1a 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellContainer.tsx @@ -47,12 +47,12 @@ const StyledValueContainer = styled.div<{ readonly: boolean }>` display: flex; min-width: 0; position: relative; + width: 100%; &:hover { ${({ readonly, theme }) => readonly && ` - outline: 1px solid ${theme.border.color.medium}; border-radius: ${theme.border.radius.sm}; ${StyledIconContainer}, ${StyledLabelContainer} { diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellDisplayMode.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellDisplayMode.tsx index a411ec8289..c3e61c40e8 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellDisplayMode.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellDisplayMode.tsx @@ -1,7 +1,6 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; -import { useFieldFocus } from '@/object-record/record-field/ui/hooks/useFieldFocus'; import { useIsFieldEmpty } from '@/object-record/record-field/ui/hooks/useIsFieldEmpty'; import { useIsFieldInputOnly } from '@/object-record/record-field/ui/hooks/useIsFieldInputOnly'; import { @@ -17,6 +16,9 @@ const StyledRecordInlineCellNormalModeOuterContainer = styled.div< 'isDisplayModeFixHeight' | 'disableHoverEffect' > & { isHovered?: boolean } >` + outline: 1px solid + ${({ theme, isHovered }) => + isHovered ? theme.border.color.medium : 'transparent'}; align-items: center; border-radius: ${({ theme }) => theme.border.radius.sm}; display: flex; @@ -54,17 +56,16 @@ const StyledRecordInlineCellNormalModeInnerContainer = styled.div` `; const StyledEmptyField = styled.div` - color: ${({ theme }) => theme.font.color.light}; - height: 20px; - display: flex; align-items: center; + color: ${({ theme }) => theme.font.color.light}; + display: flex; + height: 20px; `; export const RecordInlineCellDisplayMode = ({ children, -}: React.PropsWithChildren) => { - const { isFocused } = useFieldFocus(); - + isHovered, +}: React.PropsWithChildren<{ isHovered: boolean }>) => { const { t } = useLingui(); const { editModeContentOnly, showLabel, label, buttonIcon } = @@ -73,19 +74,19 @@ export const RecordInlineCellDisplayMode = ({ const isDisplayModeContentEmpty = useIsFieldEmpty(); const showEditButton = buttonIcon && - isFocused && + isHovered && !isDisplayModeContentEmpty && !editModeContentOnly; const isFieldInputOnly = useIsFieldInputOnly(); - const shouldDisplayEditModeOnFocus = isFocused && isFieldInputOnly; + const shouldDisplayEditModeOnFocus = isHovered && isFieldInputOnly; const emptyPlaceHolder = showLabel ? t`Empty` : label; return ( <> - + {(isDisplayModeContentEmpty && !shouldDisplayEditModeOnFocus) || !children ? ( diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellEditButton.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellEditButton.tsx index c533b6eb47..64a333a2e9 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellEditButton.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellEditButton.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; -import { AnimatedContainer } from 'twenty-ui/utilities'; -import { FloatingIconButton } from 'twenty-ui/input'; import { type IconComponent } from 'twenty-ui/display'; +import { FloatingIconButton } from 'twenty-ui/input'; +import { AnimatedContainer } from 'twenty-ui/utilities'; const StyledInlineCellButtonContainer = styled.div` align-items: center; diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellHoveredPortalContent.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellHoveredPortalContent.tsx index f166cf2491..1dec2f7c00 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellHoveredPortalContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellHoveredPortalContent.tsx @@ -1,3 +1,4 @@ +import { RecordInlineCellHoveredPortal } from '@/object-record/record-inline-cell/components/RecordInlineCellHoveredPortal'; import { css } from '@emotion/react'; import styled from '@emotion/styled'; @@ -50,17 +51,19 @@ export const RecordInlineCellHoveredPortalContent = ({ onMouseLeave, }: RecordInlineCellHoveredPortalContentProps) => { return ( - - + - {children} - - + + {children} + + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellValue.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellValue.tsx index 5d369a42eb..96b6cf2ff5 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellValue.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellValue.tsx @@ -1,11 +1,10 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; +import { FieldDisplay } from '@/object-record/record-field/ui/components/FieldDisplay'; import { useRecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext'; import { RecordInlineCellDisplayMode } from '@/object-record/record-inline-cell/components/RecordInlineCellDisplayMode'; -import { RecordInlineCellEditMode } from '@/object-record/record-inline-cell/components/RecordInlineCellEditMode'; import { RecordInlineCellSkeletonLoader } from '@/object-record/record-inline-cell/components/RecordInlineCellSkeletonLoader'; -import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell'; const StyledClickableContainer = styled.div<{ readonly?: boolean; @@ -30,16 +29,7 @@ const StyledClickableContainer = styled.div<{ `; export const RecordInlineCellValue = () => { - const { - displayModeContent, - editModeContent, - editModeContentOnly, - readonly, - loading, - isCentered, - } = useRecordInlineCellContext(); - - const { isInlineCellInEditMode } = useInlineCell(); + const { readonly, loading, isCentered } = useRecordInlineCellContext(); if (loading === true) { return ; @@ -47,12 +37,9 @@ export const RecordInlineCellValue = () => { return ( <> - {!readonly && isInlineCellInEditMode && ( - {editModeContent} - )} - - {editModeContentOnly ? editModeContent : displayModeContent} + + diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts b/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts index a4914de115..ed2e4f610e 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts @@ -6,11 +6,11 @@ import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldCont import { useInitDraftValue } from '@/object-record/record-field/ui/hooks/useInitDraftValue'; import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext'; import { useRecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext'; +import { isInlineCellInEditModeFamilyState } from '@/object-record/record-inline-cell/states/isInlineCellInEditModeFamilyState'; import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField'; import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId'; import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; -import { isInlineCellInEditModeFamilyState } from '../states/isInlineCellInEditModeFamilyState'; export const useInlineCell = ( recordFieldComponentInstanceIdFromProps?: string, diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx index 49f0cdf45d..3345d65811 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx @@ -3,6 +3,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata import { type CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; +import { RecordFieldListCellEditModePortal } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortal'; import { RecordFieldListCellHoveredPortal } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortal'; import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems'; import { RecordFieldListComponentInstanceContext } from '@/object-record/record-field-list/states/contexts/RecordFieldListComponentInstanceContext'; @@ -60,7 +61,7 @@ export const FieldsCard = ({ const setRecordFieldListHoverPosition = useSetRecoilComponentState( recordFieldListHoverPositionComponentState, - 'fields-card', + `fields-list-${objectRecordId}`, ); const handleMouseEnter = (index: number) => { @@ -78,7 +79,7 @@ export const FieldsCard = ({ return ( @@ -171,7 +172,7 @@ export const FieldsCard = ({ handleMouseEnter( index + (inlineRelationFieldMetadataItems?.length ?? 0), ), - anchorId: `record-field-list-cell-${ + anchorId: `record-field-list-inline-cell-${ index + (inlineRelationFieldMetadataItems?.length ?? 0) }`, }} @@ -236,6 +237,11 @@ export const FieldsCard = ({ + );