From 5c61daa2017a15aaf9ebab3c1b392e4310b6d518 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 14 Aug 2025 17:43:27 +0200 Subject: [PATCH] Refactor board inline-cell input (#13928) Makes board use the Input portals for Inline Cells --- .../components/CreateRelatedRecordAction.tsx | 7 +- .../components/ActivityRichTextEditor.tsx | 14 +- .../RecordBoardCardCellEditModePortal.tsx | 44 ++++++ .../RecordBoardCardCellHoveredPortal.tsx | 41 ++++++ ...ecordBoardCardCellHoveredPortalContent.tsx | 70 ++++++++++ .../RecordBoardCardInputContextProvider.tsx | 126 ++++++++++++++++++ .../components/RecordBoardCard.tsx | 87 +++++++----- .../components/RecordBoardCardBody.tsx | 14 +- .../useRecordBoardCardMetadataFromPosition.ts | 54 ++++++++ ...RecordBoardCardComponentInstanceContext.ts | 4 + ...BoardCardEditModePositionComponentState.ts | 9 ++ ...ordBoardCardHoverPositionComponentState.ts | 10 ++ .../RecordFieldListCellEditModePortal.tsx | 9 +- ...cordFieldListCellEditModePortalContent.tsx | 10 -- .../RecordFieldListCellHoveredPortal.tsx | 2 +- .../components/RecordFieldList.tsx | 14 +- .../components/RecordInlineCell.tsx | 20 +-- .../RecordInlineCellAnchoredPortal.tsx | 21 +-- .../components/RecordInlineCellContainer.tsx | 22 +-- .../record-inline-cell/hooks/useInlineCell.ts | 9 -- .../isInlineCellInEditModeFamilyState.ts | 9 -- .../hooks/useCreateNewIndexRecord.ts | 15 ++- .../components/RecordTitleCell.tsx | 29 ++-- .../components/RecordTitleCellContainer.tsx | 9 +- .../RecordTitleCellTextFieldDisplay.tsx | 7 +- .../hooks/useRecordTitleCell.tsx | 104 ++++++++------- ...RecordTitleCellComponentInstanceContext.ts | 4 + .../isTitleCellInEditModeComponentState.ts | 9 ++ 28 files changed, 583 insertions(+), 190 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellEditModePortal.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellHoveredPortal.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellHoveredPortalContent.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardInputContextProvider.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-card/hooks/useRecordBoardCardMetadataFromPosition.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-card/states/contexts/RecordBoardCardComponentInstanceContext.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-card/states/recordBoardCardEditModePositionComponentState.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-card/states/recordBoardCardHoverPositionComponentState.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortalContent.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/record-inline-cell/states/isInlineCellInEditModeFamilyState.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-title-cell/states/contexts/RecordTitleCellComponentInstanceContext.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-title-cell/states/isTitleCellInEditModeComponentState.ts diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/CreateRelatedRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/CreateRelatedRecordAction.tsx index 406867c220..329da09050 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/CreateRelatedRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/CreateRelatedRecordAction.tsx @@ -10,6 +10,7 @@ import { useRecordTitleCell } from '@/object-record/record-title-cell/hooks/useR import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getForeignKeyNameFromRelationFieldName } from '@/object-record/utils/getForeignKeyNameFromRelationFieldName'; +import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; import { isDefined } from 'twenty-shared/utils'; interface CreateRelatedRecordActionProps { @@ -106,7 +107,11 @@ export const CreateRelatedRecordAction = ({ openRecordTitleCell({ recordId: createdRecord.id, fieldName: labelIdentifierFieldMetadataItem.name, - containerType: RecordTitleCellContainerType.ShowPage, + instanceId: getRecordFieldInputInstanceId({ + recordId: createdRecord.id, + fieldName: labelIdentifierFieldMetadataItem.name, + prefix: RecordTitleCellContainerType.ShowPage, + }), }); } }; diff --git a/packages/twenty-front/src/modules/activities/components/ActivityRichTextEditor.tsx b/packages/twenty-front/src/modules/activities/components/ActivityRichTextEditor.tsx index ea6d02ccf4..2c2fd5a1de 100644 --- a/packages/twenty-front/src/modules/activities/components/ActivityRichTextEditor.tsx +++ b/packages/twenty-front/src/modules/activities/components/ActivityRichTextEditor.tsx @@ -30,7 +30,7 @@ import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRec import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useIsRecordFieldReadOnly } from '@/object-record/record-field/ui/hooks/read-only/useIsRecordFieldReadOnly'; -import { isInlineCellInEditModeFamilyState } from '@/object-record/record-inline-cell/states/isInlineCellInEditModeFamilyState'; +import { isTitleCellInEditModeComponentState } from '@/object-record/record-title-cell/states/isTitleCellInEditModeComponentState'; import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType'; import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; import { BlockEditor } from '@/ui/input/editor/components/BlockEditor'; @@ -391,7 +391,11 @@ export const ActivityRichTextEditor = ({ // Because that is how we do in the app, for example with stacked dropdowns, we always close what's open before // letting the click being captured by a button or input that can capture it. const isRecordTitleCellOpen = snapshot - .getLoadable(isInlineCellInEditModeFamilyState(recordTitleCellId)) + .getLoadable( + isTitleCellInEditModeComponentState.atomFamily({ + instanceId: recordTitleCellId, + }), + ) .getValue(); if (isRecordTitleCellOpen) { @@ -417,7 +421,11 @@ export const ActivityRichTextEditor = ({ ({ snapshot }) => () => { const isRecordTitleCellOpen = snapshot - .getLoadable(isInlineCellInEditModeFamilyState(recordTitleCellId)) + .getLoadable( + isTitleCellInEditModeComponentState.atomFamily({ + instanceId: recordTitleCellId, + }), + ) .getValue(); if (isRecordTitleCellOpen) { diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellEditModePortal.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellEditModePortal.tsx new file mode 100644 index 0000000000..d00fe91ff9 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellEditModePortal.tsx @@ -0,0 +1,44 @@ +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; + +import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { RecordBoardCardInputContextProvider } from '@/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardInputContextProvider'; +import { RECORD_BOARD_CARD_INPUT_ID_PREFIX } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardInputIdPrefix'; +import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext'; +import { useRecordBoardCardMetadataFromPosition } from '@/object-record/record-board/record-board-card/hooks/useRecordBoardCardMetadataFromPosition'; +import { recordBoardCardEditModePositionComponentState } from '@/object-record/record-board/record-board-card/states/recordBoardCardEditModePositionComponentState'; +import { FieldInput } from '@/object-record/record-field/ui/components/FieldInput'; +import { RecordInlineCellAnchoredPortal } from '@/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal'; +import { RecordInlineCellEditMode } from '@/object-record/record-inline-cell/components/RecordInlineCellEditMode'; +import { useContext } from 'react'; +import { isDefined } from 'twenty-shared/utils'; + +export const RecordBoardCardCellEditModePortal = () => { + const { objectMetadataItem } = useContext(RecordBoardContext); + const { recordId } = useContext(RecordBoardCardContext); + + const editModePosition = useRecoilComponentValue( + recordBoardCardEditModePositionComponentState, + ); + + const { editedFieldMetadataItem } = useRecordBoardCardMetadataFromPosition(); + + if (!isDefined(editModePosition) || !isDefined(editedFieldMetadataItem)) { + return null; + } + + return ( + + + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellHoveredPortal.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellHoveredPortal.tsx new file mode 100644 index 0000000000..87db581e1d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellHoveredPortal.tsx @@ -0,0 +1,41 @@ +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; + +import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { RecordBoardCardCellHoveredPortalContent } from '@/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellHoveredPortalContent'; +import { RecordBoardCardInputContextProvider } from '@/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardInputContextProvider'; +import { RECORD_BOARD_CARD_INPUT_ID_PREFIX } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardInputIdPrefix'; +import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext'; +import { useRecordBoardCardMetadataFromPosition } from '@/object-record/record-board/record-board-card/hooks/useRecordBoardCardMetadataFromPosition'; +import { recordBoardCardHoverPositionComponentState } from '@/object-record/record-board/record-board-card/states/recordBoardCardHoverPositionComponentState'; +import { RecordInlineCellAnchoredPortal } from '@/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal'; +import { useContext } from 'react'; +import { isDefined } from 'twenty-shared/utils'; + +export const RecordBoardCardCellHoveredPortal = () => { + const { objectMetadataItem } = useContext(RecordBoardContext); + const { recordId } = useContext(RecordBoardCardContext); + + const hoverPosition = useRecoilComponentValue( + recordBoardCardHoverPositionComponentState, + ); + + const { hoveredFieldMetadataItem } = useRecordBoardCardMetadataFromPosition(); + + if (!isDefined(hoverPosition) || !isDefined(hoveredFieldMetadataItem)) { + return null; + } + + return ( + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellHoveredPortalContent.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellHoveredPortalContent.tsx new file mode 100644 index 0000000000..aa27092557 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellHoveredPortalContent.tsx @@ -0,0 +1,70 @@ +import { RECORD_BOARD_CARD_INPUT_ID_PREFIX } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardInputIdPrefix'; +import { recordBoardCardEditModePositionComponentState } from '@/object-record/record-board/record-board-card/states/recordBoardCardEditModePositionComponentState'; +import { recordBoardCardHoverPositionComponentState } from '@/object-record/record-board/record-board-card/states/recordBoardCardHoverPositionComponentState'; +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 { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; +import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; +import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { useContext } from 'react'; + +export const RecordBoardCardCellHoveredPortalContent = () => { + const { editModeContentOnly, isCentered } = useRecordInlineCellContext(); + + const { isRecordFieldReadOnly, recordId, fieldDefinition } = + useContext(FieldContext); + + const { openInlineCell } = useInlineCell( + getRecordFieldInputInstanceId({ + recordId, + fieldName: fieldDefinition.metadata.fieldName, + prefix: RECORD_BOARD_CARD_INPUT_ID_PREFIX, + }), + ); + + const shouldContainerBeClickable = + !isRecordFieldReadOnly && !editModeContentOnly; + + const [recordBoardCardHoverPosition, setRecordBoardCardHoverPosition] = + useRecoilComponentState(recordBoardCardHoverPositionComponentState); + + const setRecordBoardCardEditModePosition = useSetRecoilComponentState( + recordBoardCardEditModePositionComponentState, + ); + const { openFieldInput } = useOpenFieldInputEditMode(); + + const handleClick = () => { + if (shouldContainerBeClickable) { + openInlineCell(); + setRecordBoardCardEditModePosition(recordBoardCardHoverPosition); + + openFieldInput({ + fieldDefinition, + recordId, + prefix: RECORD_BOARD_CARD_INPUT_ID_PREFIX, + }); + } + }; + + const handleMouseLeave = () => { + setRecordBoardCardHoverPosition(null); + }; + + return ( + + + {editModeContentOnly ? : } + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardInputContextProvider.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardInputContextProvider.tsx new file mode 100644 index 0000000000..56a716af8f --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardInputContextProvider.tsx @@ -0,0 +1,126 @@ +import { usePersistFieldFromFieldInputContext } from '@/object-record/record-field/ui/hooks/usePersistFieldFromFieldInputContext'; +import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext'; + +import { recordBoardCardEditModePositionComponentState } from '@/object-record/record-board/record-board-card/states/recordBoardCardEditModePositionComponentState'; +import { + FieldInputEventContext, + type FieldInputClickOutsideEvent, + type FieldInputEvent, +} from '@/object-record/record-field/ui/contexts/FieldInputEventContext'; +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'; + +type RecordBoardCardInputContextProviderProps = { + children: React.ReactNode; +}; + +export const RecordBoardCardInputContextProvider = ({ + children, +}: RecordBoardCardInputContextProviderProps) => { + const { closeInlineCell } = useInlineCell(); + const setRecordBoardCardEditModePosition = useSetRecoilComponentState( + recordBoardCardEditModePositionComponentState, + ); + + const closeInlineCellAndResetEditModePosition = useCallback(() => { + setRecordBoardCardEditModePosition(null); + closeInlineCell(); + }, [closeInlineCell, setRecordBoardCardEditModePosition]); + + const instanceId = useAvailableComponentInstanceId( + RecordFieldComponentInstanceContext, + ); + + const { persistFieldFromFieldInputContext } = + usePersistFieldFromFieldInputContext(); + + const handleEnter: FieldInputEvent = ({ newValue, skipPersist }) => { + if (skipPersist !== true) { + persistFieldFromFieldInputContext(newValue); + } + + closeInlineCellAndResetEditModePosition(); + }; + + const handleSubmit: FieldInputEvent = ({ newValue, skipPersist }) => { + if (skipPersist !== true) { + persistFieldFromFieldInputContext(newValue); + } + + closeInlineCellAndResetEditModePosition(); + }; + + const handleCancel = () => { + closeInlineCellAndResetEditModePosition(); + }; + + const handleClickOutside: FieldInputClickOutsideEvent = useRecoilCallback( + ({ snapshot }) => + ({ newValue, event, skipPersist }) => { + const currentFocusId = snapshot + .getLoadable(currentFocusIdSelector) + .getValue(); + + if (currentFocusId !== instanceId) { + return; + } + event?.preventDefault(); + event?.stopImmediatePropagation(); + + if (skipPersist !== true) { + persistFieldFromFieldInputContext(newValue); + } + + closeInlineCellAndResetEditModePosition(); + }, + [ + closeInlineCellAndResetEditModePosition, + instanceId, + persistFieldFromFieldInputContext, + ], + ); + + const handleEscape: FieldInputEvent = ({ newValue, skipPersist }) => { + if (skipPersist !== true) { + persistFieldFromFieldInputContext(newValue); + } + + closeInlineCellAndResetEditModePosition(); + }; + + const handleTab: FieldInputEvent = ({ newValue, skipPersist }) => { + if (skipPersist !== true) { + persistFieldFromFieldInputContext(newValue); + } + + closeInlineCellAndResetEditModePosition(); + }; + + const handleShiftTab: FieldInputEvent = ({ newValue, skipPersist }) => { + if (skipPersist !== true) { + persistFieldFromFieldInputContext(newValue); + } + + closeInlineCellAndResetEditModePosition(); + }; + + return ( + + {children} + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx index 31815e1186..80e5027f06 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx @@ -11,9 +11,12 @@ import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-re import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard'; +import { RecordBoardCardCellEditModePortal } from '@/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellEditModePortal'; +import { RecordBoardCardCellHoveredPortal } from '@/object-record/record-board/record-board-card/anchored-portal/components/RecordBoardCardCellHoveredPortal'; import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody'; import { RecordBoardCardHeader } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeader'; import { RECORD_BOARD_CARD_CLICK_OUTSIDE_ID } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardClickOutsideId'; +import { RecordBoardCardComponentInstanceContext } from '@/object-record/record-board/record-board-card/states/contexts/RecordBoardCardComponentInstanceContext'; import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView'; import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown'; @@ -226,44 +229,58 @@ export const RecordBoardCard = () => { ); return ( - - - - {isPrimaryMultiDrag && - Array.from({ - length: Math.min(5, multiDragState.originalSelection.length - 1), - }).map((_, index) => ( - - ))} + + + + {isPrimaryMultiDrag && + Array.from({ + length: Math.min( + 5, + multiDragState.originalSelection.length - 1, + ), + }).map((_, index) => ( + + ))} - - - - - - - - - + + + + + + + + + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardBody.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardBody.tsx index 9895b61d08..0f7f564133 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardBody.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardBody.tsx @@ -3,6 +3,7 @@ import { RecordBoardCardBodyContainer } from '@/object-record/record-board/recor import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer'; import { RECORD_BOARD_CARD_INPUT_ID_PREFIX } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardInputIdPrefix'; import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext'; +import { recordBoardCardHoverPositionComponentState } from '@/object-record/record-board/record-board-card/states/recordBoardCardHoverPositionComponentState'; import { type RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition'; import { FieldContext, @@ -15,6 +16,7 @@ import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldM import { getFieldButtonIcon } from '@/object-record/record-field/ui/utils/getFieldButtonIcon'; import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell'; import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; +import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { useContext } from 'react'; export const RecordBoardCardBody = ({ @@ -53,9 +55,17 @@ export const RecordBoardCardBody = ({ }), ); + const setRecordBoardCardHoverPosition = useSetRecoilComponentState( + recordBoardCardHoverPositionComponentState, + ); + + const handleMouseEnter = (index: number) => { + setRecordBoardCardHoverPosition(index); + }; + return ( - {fieldDefinitionsWithReadOnly.map((fieldDefinition) => ( + {fieldDefinitionsWithReadOnly.map((fieldDefinition, index) => ( handleMouseEnter(index), }} > { + const { objectMetadataItem } = useContext(RecordBoardContext); + + const hoverPosition = useRecoilComponentValue( + recordBoardCardHoverPositionComponentState, + ); + + const editModePosition = useRecoilComponentValue( + recordBoardCardEditModePositionComponentState, + ); + + const visibleFieldDefinitions = useRecoilComponentValue( + recordBoardVisibleFieldDefinitionsComponentSelector, + ); + + const visibleFieldDefinitionsFiltered = visibleFieldDefinitions.filter( + (boardField) => !boardField.isLabelIdentifier, + ); + + const hoveredFieldDefinition = isDefined(hoverPosition) + ? visibleFieldDefinitionsFiltered.at(hoverPosition) + : undefined; + + const editedFieldDefinition = isDefined(editModePosition) + ? visibleFieldDefinitionsFiltered.at(editModePosition) + : undefined; + + const hoveredFieldMetadataItem = isDefined(hoveredFieldDefinition) + ? objectMetadataItem.fields.find( + (fieldMetadataItem) => + fieldMetadataItem.id === hoveredFieldDefinition.fieldMetadataId, + ) + : undefined; + + const editedFieldMetadataItem = isDefined(editedFieldDefinition) + ? objectMetadataItem.fields.find( + (fieldMetadataItem) => + fieldMetadataItem.id === editedFieldDefinition.fieldMetadataId, + ) + : undefined; + + return { + hoveredFieldMetadataItem, + editedFieldMetadataItem, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/states/contexts/RecordBoardCardComponentInstanceContext.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/states/contexts/RecordBoardCardComponentInstanceContext.ts new file mode 100644 index 0000000000..b78b7b5a42 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/states/contexts/RecordBoardCardComponentInstanceContext.ts @@ -0,0 +1,4 @@ +import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext'; + +export const RecordBoardCardComponentInstanceContext = + createComponentInstanceContext(); diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/states/recordBoardCardEditModePositionComponentState.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/states/recordBoardCardEditModePositionComponentState.ts new file mode 100644 index 0000000000..0b13a60e60 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/states/recordBoardCardEditModePositionComponentState.ts @@ -0,0 +1,9 @@ +import { RecordBoardCardComponentInstanceContext } from '@/object-record/record-board/record-board-card/states/contexts/RecordBoardCardComponentInstanceContext'; +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; + +export const recordBoardCardEditModePositionComponentState = + createComponentState({ + key: 'recordBoardCardEditModePositionComponentState', + defaultValue: null, + componentInstanceContext: RecordBoardCardComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/states/recordBoardCardHoverPositionComponentState.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/states/recordBoardCardHoverPositionComponentState.ts new file mode 100644 index 0000000000..aff482cc5e --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/states/recordBoardCardHoverPositionComponentState.ts @@ -0,0 +1,10 @@ +import { RecordBoardCardComponentInstanceContext } from '@/object-record/record-board/record-board-card/states/contexts/RecordBoardCardComponentInstanceContext'; +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; + +export const recordBoardCardHoverPositionComponentState = createComponentState< + number | null +>({ + key: 'recordBoardCardHoverPositionComponentState', + defaultValue: null, + componentInstanceContext: RecordBoardCardComponentInstanceContext, +}); 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 index c27a07aec3..e703b11874 100644 --- 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 @@ -1,12 +1,13 @@ 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 { RecordFieldListComponentInstanceContext } from '@/object-record/record-field-list/states/contexts/RecordFieldListComponentInstanceContext'; import { recordFieldListCellEditModePositionComponentState } from '@/object-record/record-field-list/states/recordFieldListCellEditModePositionComponentState'; +import { FieldInput } from '@/object-record/record-field/ui/components/FieldInput'; import { RecordInlineCellAnchoredPortal } from '@/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal'; +import { RecordInlineCellEditMode } from '@/object-record/record-inline-cell/components/RecordInlineCellEditMode'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { isDefined } from 'twenty-shared/utils'; @@ -41,10 +42,12 @@ export const RecordFieldListCellEditModePortal = ({ fieldMetadataItem={editedFieldMetadataItem} objectMetadataItem={objectMetadataItem} recordId={recordId} - anchorIdPrefix={instanceId} + instanceIdPrefix={instanceId} > - + + + ); 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 deleted file mode 100644 index 9cda5b1757..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortalContent.tsx +++ /dev/null @@ -1,10 +0,0 @@ -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 6753140d38..24d35793a2 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 @@ -41,7 +41,7 @@ export const RecordFieldListCellHoveredPortal = ({ fieldMetadataItem={hoveredFieldMetadataItem} objectMetadataItem={objectMetadataItem} recordId={recordId} - anchorIdPrefix={instanceId} + instanceIdPrefix={instanceId} > diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/components/RecordFieldList.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/components/RecordFieldList.tsx index 9701d42810..f1d595d831 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/components/RecordFieldList.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field-list/components/RecordFieldList.tsx @@ -111,7 +111,11 @@ export const RecordFieldList = ({ useUpdateRecord: useUpdateOneObjectRecordMutation, isDisplayModeFixHeight: true, onMouseEnter: () => handleMouseEnter(index), - anchorId: `${instanceId}-${index}`, + anchorId: `${getRecordFieldInputInstanceId({ + recordId: objectRecordId, + fieldName: fieldMetadataItem.name, + prefix: instanceId, + })}`, isRecordFieldReadOnly: isRecordFieldReadOnly({ isRecordReadOnly, objectPermissions: @@ -178,9 +182,11 @@ export const RecordFieldList = ({ handleMouseEnter( index + (inlineRelationFieldMetadataItems?.length ?? 0), ), - anchorId: `${instanceId}-${ - index + (inlineRelationFieldMetadataItems?.length ?? 0) - }`, + anchorId: `${getRecordFieldInputInstanceId({ + recordId: objectRecordId, + fieldName: fieldMetadataItem.name, + prefix: instanceId, + })}`, }} > { onCloseEditMode(); - setIsInlineCellInEditMode(false); goBackToPreviousDropdownFocusId(); - }, [ - onCloseEditMode, - setIsInlineCellInEditMode, - goBackToPreviousDropdownFocusId, - ]); + }, [onCloseEditMode, goBackToPreviousDropdownFocusId]); const { persistFieldFromFieldInputContext } = usePersistFieldFromFieldInputContext(); 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 index ec91c71d50..4bda96376b 100644 --- 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 @@ -11,6 +11,7 @@ import { FieldFocusContextProvider } from '@/object-record/record-field/ui/conte 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 { RecordInlineCellCloseOnCommandMenuOpeningEffect } from '@/object-record/record-inline-cell/components/RecordInlineCellCloseOnCommandMenuOpeningEffect'; import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; import { createPortal } from 'react-dom'; import { isDefined } from 'twenty-shared/utils'; @@ -23,20 +24,25 @@ type RecordInlineCellAnchoredPortalProps = { >; objectMetadataItem: ObjectMetadataItem; recordId: string; - anchorIdPrefix: string; + instanceIdPrefix: string; children: React.ReactNode; }; export const RecordInlineCellAnchoredPortal = ({ - position, fieldMetadataItem, objectMetadataItem, recordId, - anchorIdPrefix, + instanceIdPrefix, children, }: RecordInlineCellAnchoredPortalProps) => { + const fieldInstanceId = getRecordFieldInputInstanceId({ + recordId, + fieldName: fieldMetadataItem.name, + prefix: instanceIdPrefix, + }); + const anchorElement = document.body.querySelector( - `#${anchorIdPrefix}-${position}`, + `#${fieldInstanceId}`, ); const isRecordFieldReadOnly = useIsRecordFieldReadOnly({ @@ -88,16 +94,13 @@ export const RecordInlineCellAnchoredPortal = ({ {createPortal( {children} + , anchorElement, )} 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 44d3198d1a..1e38d25140 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 @@ -9,8 +9,6 @@ import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFi import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldText } from '@/object-record/record-field/ui/types/guards/isFieldText'; -import { RecordInlineCellCloseOnCommandMenuOpeningEffect } from '@/object-record/record-inline-cell/components/RecordInlineCellCloseOnCommandMenuOpeningEffect'; -import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell'; import { AppTooltip, OverflowingTextWithTooltip, @@ -88,23 +86,12 @@ export const StyledSkeletonDiv = styled.div` `; export const RecordInlineCellContainer = () => { - const { - readonly, - IconLabel, - label, - labelWidth, - showLabel, - editModeContentOnly, - } = useRecordInlineCellContext(); - - const { isInlineCellInEditMode, openInlineCell } = useInlineCell(); + const { readonly, IconLabel, label, labelWidth, showLabel } = + useRecordInlineCellContext(); const { recordId, fieldDefinition, onMouseEnter, onMouseLeave, anchorId } = useContext(FieldContext); - const shouldContainerBeClickable = - !readonly && !editModeContentOnly && !isInlineCellInEditMode; - if (isFieldText(fieldDefinition)) { assertFieldMetadata(FieldMetadataType.TEXT, isFieldText, fieldDefinition); } @@ -136,7 +123,6 @@ export const RecordInlineCellContainer = () => { readonly={readonly ?? false} onMouseEnter={handleContainerMouseEnter} onMouseLeave={handleContainerMouseLeave} - onClick={shouldContainerBeClickable ? openInlineCell : undefined} > {(IconLabel || label) && ( @@ -164,9 +150,7 @@ export const RecordInlineCellContainer = () => { )} )} - {isInlineCellInEditMode && ( - - )} + 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 ed2e4f610e..596324d6f2 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 @@ -1,12 +1,10 @@ import { useContext } from 'react'; -import { useRecoilState } from 'recoil'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; 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'; @@ -22,10 +20,6 @@ export const useInlineCell = ( recordFieldComponentInstanceIdFromProps, ); - const [isInlineCellInEditMode, setIsInlineCellInEditMode] = useRecoilState( - isInlineCellInEditModeFamilyState(recordFieldComponentInstanceId), - ); - const { onOpenEditMode, onCloseEditMode } = useRecordInlineCellContext(); const { setActiveDropdownFocusIdAndMemorizePrevious } = @@ -37,14 +31,12 @@ export const useInlineCell = ( const closeInlineCell = () => { onCloseEditMode?.(); - setIsInlineCellInEditMode(false); goBackToPreviousDropdownFocusId(); }; const openInlineCell = () => { onOpenEditMode?.(); - setIsInlineCellInEditMode(true); initFieldInputDraftValue({ recordId, fieldDefinition, @@ -61,7 +53,6 @@ export const useInlineCell = ( }; return { - isInlineCellInEditMode, closeInlineCell, openInlineCell, }; diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/states/isInlineCellInEditModeFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-inline-cell/states/isInlineCellInEditModeFamilyState.ts deleted file mode 100644 index d605eda213..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/states/isInlineCellInEditModeFamilyState.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState'; - -export const isInlineCellInEditModeFamilyState = createFamilyState< - boolean, - string ->({ - key: 'isInlineCellInEditModeScopedState', - defaultValue: false, -}); diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts index 9247139af2..0e9785e3d7 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts @@ -8,6 +8,7 @@ import { useRecordTitleCell } from '@/object-record/record-title-cell/hooks/useR import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { canOpenObjectInSidePanel } from '@/object-record/utils/canOpenObjectInSidePanel'; +import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; import { AppPath } from '@/types/AppPath'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; import { useRecoilCallback } from 'recoil'; @@ -15,11 +16,13 @@ import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; import { useNavigateApp } from '~/hooks/useNavigateApp'; +type UseCreateNewIndexRecordProps = { + objectMetadataItem: ObjectMetadataItem; +}; + export const useCreateNewIndexRecord = ({ objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { +}: UseCreateNewIndexRecordProps) => { const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); const { createOneRecord } = useCreateOneRecord({ @@ -67,7 +70,11 @@ export const useCreateNewIndexRecord = ({ openRecordTitleCell({ recordId, fieldName: labelIdentifierFieldMetadataItem.name, - containerType: RecordTitleCellContainerType.ShowPage, + instanceId: getRecordFieldInputInstanceId({ + recordId, + fieldName: labelIdentifierFieldMetadataItem.name, + prefix: RecordTitleCellContainerType.ShowPage, + }), }); } } else { diff --git a/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCell.tsx b/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCell.tsx index 0c14ee1602..d8be1cd0f5 100644 --- a/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCell.tsx @@ -19,6 +19,7 @@ import { import { RecordTitleCellFieldDisplay } from '@/object-record/record-title-cell/components/RecordTitleCellFieldDisplay'; import { RecordTitleCellFieldInput } from '@/object-record/record-title-cell/components/RecordTitleCellFieldInput'; import { useRecordTitleCell } from '@/object-record/record-title-cell/hooks/useRecordTitleCell'; +import { RecordTitleCellComponentInstanceContext } from '@/object-record/record-title-cell/states/contexts/RecordTitleCellComponentInstanceContext'; import { type RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType'; import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; import { useRecoilCallback } from 'recoil'; @@ -42,12 +43,8 @@ export const RecordTitleCell = ({ const { closeRecordTitleCell } = useRecordTitleCell(); const closeCell = useCallback(() => { - closeRecordTitleCell({ - recordId, - fieldName: fieldDefinition.metadata.fieldName, - containerType, - }); - }, [closeRecordTitleCell, containerType, fieldDefinition, recordId]); + closeRecordTitleCell(); + }, [closeRecordTitleCell]); const { persistFieldFromFieldInputContext } = usePersistFieldFromFieldInputContext(); @@ -132,11 +129,21 @@ export const RecordTitleCell = ({ }), }} > - - - - - + + + + + + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellContainer.tsx b/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellContainer.tsx index c41d0ca76e..05d438c86f 100644 --- a/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellContainer.tsx @@ -1,5 +1,6 @@ -import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell'; import { RecordTitleCellContext } from '@/object-record/record-title-cell/components/RecordTitleCellContext'; +import { isTitleCellInEditModeComponentState } from '@/object-record/record-title-cell/states/isTitleCellInEditModeComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useContext } from 'react'; import { isDefined } from 'twenty-shared/utils'; @@ -8,11 +9,13 @@ export const RecordTitleCellContainer = () => { RecordTitleCellContext, ); - const { isInlineCellInEditMode } = useInlineCell(); + const isTitleCellInEditMode = useRecoilComponentValue( + isTitleCellInEditModeComponentState, + ); if (isDefined(isReadOnly) && isReadOnly) { return <>{displayModeContent}; } - return <>{isInlineCellInEditMode ? editModeContent : displayModeContent}; + return <>{isTitleCellInEditMode ? editModeContent : displayModeContent}; }; diff --git a/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellTextFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellTextFieldDisplay.tsx index 9d983b524a..c90c1e49e8 100644 --- a/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellTextFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellTextFieldDisplay.tsx @@ -2,6 +2,7 @@ import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldCont import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { useRecordTitleCell } from '@/object-record/record-title-cell/hooks/useRecordTitleCell'; import { type RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType'; +import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; import { withTheme, type Theme } from '@emotion/react'; import styled from '@emotion/styled'; import { useContext } from 'react'; @@ -50,7 +51,11 @@ export const RecordTitleCellSingleTextDisplayMode = ({ openRecordTitleCell({ recordId, fieldName: fieldDefinition.metadata.fieldName, - containerType, + instanceId: getRecordFieldInputInstanceId({ + recordId, + fieldName: fieldDefinition.metadata.fieldName, + prefix: containerType, + }), }); }} > diff --git a/packages/twenty-front/src/modules/object-record/record-title-cell/hooks/useRecordTitleCell.tsx b/packages/twenty-front/src/modules/object-record/record-title-cell/hooks/useRecordTitleCell.tsx index 72bbcbded4..95367ecb2e 100644 --- a/packages/twenty-front/src/modules/object-record/record-title-cell/hooks/useRecordTitleCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-title-cell/hooks/useRecordTitleCell.tsx @@ -1,20 +1,30 @@ import { useContextStoreObjectMetadataItem } from '@/context-store/hooks/useContextStoreObjectMetadataItem'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; import { useInitDraftValue } from '@/object-record/record-field/ui/hooks/useInitDraftValue'; -import { isInlineCellInEditModeFamilyState } from '@/object-record/record-inline-cell/states/isInlineCellInEditModeFamilyState'; -import { type RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType'; -import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; +import { RecordTitleCellComponentInstanceContext } from '@/object-record/record-title-cell/states/contexts/RecordTitleCellComponentInstanceContext'; +import { isTitleCellInEditModeComponentState } from '@/object-record/record-title-cell/states/isTitleCellInEditModeComponentState'; import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; +import { useAvailableComponentInstanceId } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceId'; import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; +type OpenTitleCellFunctionParams = { + recordId: string; + fieldName: string; + instanceId?: string; +}; + export const useRecordTitleCell = () => { const { goBackToPreviousDropdownFocusId } = useGoBackToPreviousDropdownFocusId(); + const instanceId = useAvailableComponentInstanceId( + RecordTitleCellComponentInstanceContext, + ); + const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); const { removeFocusItemFromFocusStackById } = useRemoveFocusItemFromFocusStackById(); @@ -23,37 +33,32 @@ export const useRecordTitleCell = () => { const closeRecordTitleCell = useRecoilCallback( ({ set }) => - ({ - recordId, - fieldName, - containerType, - }: { - recordId: string; - fieldName: string; - containerType: RecordTitleCellContainerType; - }) => { + (instanceIdFromProps?: string) => { + const computedInstanceId = instanceIdFromProps ?? instanceId; + + if (!isDefined(computedInstanceId)) { + throw new Error( + 'Instance ID is not defined in closeRecordTitleCell this should not happen.', + ); + } set( - isInlineCellInEditModeFamilyState( - getRecordFieldInputInstanceId({ - recordId, - fieldName, - prefix: containerType, - }), - ), + isTitleCellInEditModeComponentState.atomFamily({ + instanceId: computedInstanceId, + }), false, ); removeFocusItemFromFocusStackById({ - focusId: getRecordFieldInputInstanceId({ - recordId, - fieldName, - prefix: containerType, - }), + focusId: computedInstanceId, }); goBackToPreviousDropdownFocusId(); }, - [goBackToPreviousDropdownFocusId, removeFocusItemFromFocusStackById], + [ + goBackToPreviousDropdownFocusId, + instanceId, + removeFocusItemFromFocusStackById, + ], ); const initFieldInputDraftValue = useInitDraftValue(); @@ -63,31 +68,27 @@ export const useRecordTitleCell = () => { ({ recordId, fieldName, - containerType, - }: { - recordId: string; - fieldName: string; - containerType: RecordTitleCellContainerType; - }) => { + instanceId: instanceIdFromProps, + }: OpenTitleCellFunctionParams) => { if (!isDefined(objectMetadataItem)) { throw new Error( 'Cannot find object metadata item in openRecordTitleCell this should not happen.', ); } + const computedInstanceId = instanceIdFromProps ?? instanceId; + + if (!isDefined(computedInstanceId)) { + throw new Error( + 'Instance ID is not defined in openRecordTitleCell this should not happen.', + ); + } + pushFocusItemToFocusStack({ - focusId: getRecordFieldInputInstanceId({ - recordId, - fieldName, - prefix: containerType, - }), + focusId: computedInstanceId, component: { type: FocusComponentType.OPENED_FIELD_INPUT, - instanceId: getRecordFieldInputInstanceId({ - recordId, - fieldName, - prefix: containerType, - }), + instanceId: computedInstanceId, }, globalHotkeysConfig: { enableGlobalHotkeysConflictingWithKeyboard: false, @@ -95,12 +96,12 @@ export const useRecordTitleCell = () => { }, }); - const recordTitleCellId = getRecordFieldInputInstanceId({ - recordId, - fieldName, - prefix: containerType, - }); - set(isInlineCellInEditModeFamilyState(recordTitleCellId), true); + set( + isTitleCellInEditModeComponentState.atomFamily({ + instanceId: computedInstanceId, + }), + true, + ); const fieldDefinitions = objectMetadataItem.fields.map( (fieldMetadataItem, index) => @@ -124,10 +125,15 @@ export const useRecordTitleCell = () => { initFieldInputDraftValue({ recordId, fieldDefinition, - fieldComponentInstanceId: recordTitleCellId, + fieldComponentInstanceId: computedInstanceId, }); }, - [initFieldInputDraftValue, pushFocusItemToFocusStack, objectMetadataItem], + [ + objectMetadataItem, + instanceId, + pushFocusItemToFocusStack, + initFieldInputDraftValue, + ], ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-title-cell/states/contexts/RecordTitleCellComponentInstanceContext.ts b/packages/twenty-front/src/modules/object-record/record-title-cell/states/contexts/RecordTitleCellComponentInstanceContext.ts new file mode 100644 index 0000000000..5bab6c1ac0 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-title-cell/states/contexts/RecordTitleCellComponentInstanceContext.ts @@ -0,0 +1,4 @@ +import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext'; + +export const RecordTitleCellComponentInstanceContext = + createComponentInstanceContext(); diff --git a/packages/twenty-front/src/modules/object-record/record-title-cell/states/isTitleCellInEditModeComponentState.ts b/packages/twenty-front/src/modules/object-record/record-title-cell/states/isTitleCellInEditModeComponentState.ts new file mode 100644 index 0000000000..67422b1e94 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-title-cell/states/isTitleCellInEditModeComponentState.ts @@ -0,0 +1,9 @@ +import { RecordTitleCellComponentInstanceContext } from '@/object-record/record-title-cell/states/contexts/RecordTitleCellComponentInstanceContext'; +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; + +export const isTitleCellInEditModeComponentState = + createComponentState({ + key: 'isTitleCellInEditModeComponentState', + defaultValue: false, + componentInstanceContext: RecordTitleCellComponentInstanceContext, + });