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
This commit is contained in:
-118
@@ -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<HTMLAnchorElement>(
|
||||
`#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 (
|
||||
<FieldContext.Provider
|
||||
key={recordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
recordId,
|
||||
maxWidth: 200,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: 0,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
isDisplayModeFixHeight: true,
|
||||
isRecordFieldReadOnly,
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{createPortal(
|
||||
<RecordFieldComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: getRecordFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
prefix: INPUT_ID_PREFIX,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RecordFieldListCellAnchorPortalContext>
|
||||
<RecordInlineCellHoveredPortal>
|
||||
{children}
|
||||
</RecordInlineCellHoveredPortal>
|
||||
</RecordFieldListCellAnchorPortalContext>
|
||||
</RecordFieldComponentInstanceContext.Provider>,
|
||||
anchorElement,
|
||||
)}
|
||||
</>
|
||||
</FieldContext.Provider>
|
||||
);
|
||||
};
|
||||
+45
@@ -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 (
|
||||
<RecordInlineCellAnchoredPortal
|
||||
position={editModePosition}
|
||||
fieldMetadataItem={editedFieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={recordId}
|
||||
anchorIdPrefix={'record-field-list-inline-cell'}
|
||||
>
|
||||
<RecordFieldListInputContextProvider>
|
||||
<RecordFieldListCellEditModePortalContent />
|
||||
</RecordFieldListInputContextProvider>
|
||||
</RecordInlineCellAnchoredPortal>
|
||||
);
|
||||
};
|
||||
+10
@@ -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 (
|
||||
<RecordInlineCellEditMode>
|
||||
<FieldInput />
|
||||
</RecordInlineCellEditMode>
|
||||
);
|
||||
};
|
||||
+12
-6
@@ -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 (
|
||||
<RecordFieldListCellAnchoredPortal
|
||||
<RecordInlineCellAnchoredPortal
|
||||
position={hoverPosition}
|
||||
fieldMetadataItem={hoveredFieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={recordId}
|
||||
anchorIdPrefix={'record-field-list-inline-cell'}
|
||||
>
|
||||
<RecordFieldListCellHoveredPortalContent
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
</RecordFieldListCellAnchoredPortal>
|
||||
<RecordFieldListInputContextProvider>
|
||||
<RecordFieldListCellHoveredPortalContent />
|
||||
</RecordFieldListInputContextProvider>
|
||||
</RecordInlineCellAnchoredPortal>
|
||||
);
|
||||
};
|
||||
|
||||
+27
-30
@@ -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 (
|
||||
<RecordInlineCellHoveredPortalContent
|
||||
readonly={isRecordFieldReadOnly}
|
||||
isCentered={isCentered}
|
||||
onClick={shouldContainerBeClickable ? openInlineCell : undefined}
|
||||
onClick={handleClick}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<RecordInlineCellDisplayMode>
|
||||
<RecordInlineCellDisplayMode isHovered={true}>
|
||||
{editModeContentOnly ? <FieldInput /> : <FieldDisplay />}
|
||||
</RecordInlineCellDisplayMode>
|
||||
</RecordInlineCellHoveredPortalContent>
|
||||
|
||||
+33
-17
@@ -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,
|
||||
}}
|
||||
>
|
||||
<FieldInput />
|
||||
{children}
|
||||
</FieldInputEventContext.Provider>
|
||||
);
|
||||
};
|
||||
+108
@@ -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<HTMLAnchorElement>(
|
||||
`#${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 (
|
||||
<FieldFocusContextProvider isFocused={true}>
|
||||
<FieldContext.Provider
|
||||
key={recordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
recordId,
|
||||
maxWidth: 200,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: 0,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
isDisplayModeFixHeight: true,
|
||||
isRecordFieldReadOnly,
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{createPortal(
|
||||
<RecordFieldComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: getRecordFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
prefix: 'inline-cell',
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RecordInlineCellAnchoredPortalContext>
|
||||
{children}
|
||||
</RecordInlineCellAnchoredPortalContext>
|
||||
</RecordFieldComponentInstanceContext.Provider>,
|
||||
anchorElement,
|
||||
)}
|
||||
</>
|
||||
</FieldContext.Provider>
|
||||
</FieldFocusContextProvider>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -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,
|
||||
+1
-1
@@ -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} {
|
||||
|
||||
+11
-10
@@ -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<unknown>) => {
|
||||
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 (
|
||||
<>
|
||||
<StyledRecordInlineCellNormalModeOuterContainer isHovered={isFocused}>
|
||||
<StyledRecordInlineCellNormalModeOuterContainer isHovered={isHovered}>
|
||||
<StyledRecordInlineCellNormalModeInnerContainer>
|
||||
{(isDisplayModeContentEmpty && !shouldDisplayEditModeOnFocus) ||
|
||||
!children ? (
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+13
-10
@@ -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 (
|
||||
<StyledInlineCellBaseContainer
|
||||
readonly={readonly}
|
||||
onClick={onClick}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<StyledRecordTableCellHoveredPortalContent
|
||||
isCentered={isCentered}
|
||||
<RecordInlineCellHoveredPortal>
|
||||
<StyledInlineCellBaseContainer
|
||||
readonly={readonly}
|
||||
onClick={onClick}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
{children}
|
||||
</StyledRecordTableCellHoveredPortalContent>
|
||||
</StyledInlineCellBaseContainer>
|
||||
<StyledRecordTableCellHoveredPortalContent
|
||||
isCentered={isCentered}
|
||||
readonly={readonly}
|
||||
>
|
||||
{children}
|
||||
</StyledRecordTableCellHoveredPortalContent>
|
||||
</StyledInlineCellBaseContainer>
|
||||
</RecordInlineCellHoveredPortal>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-17
@@ -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 <RecordInlineCellSkeletonLoader />;
|
||||
@@ -47,12 +37,9 @@ export const RecordInlineCellValue = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{!readonly && isInlineCellInEditMode && (
|
||||
<RecordInlineCellEditMode>{editModeContent}</RecordInlineCellEditMode>
|
||||
)}
|
||||
<StyledClickableContainer readonly={readonly} isCentered={isCentered}>
|
||||
<RecordInlineCellDisplayMode>
|
||||
{editModeContentOnly ? editModeContent : displayModeContent}
|
||||
<RecordInlineCellDisplayMode isHovered={false}>
|
||||
<FieldDisplay />
|
||||
</RecordInlineCellDisplayMode>
|
||||
</StyledClickableContainer>
|
||||
</>
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+9
-3
@@ -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 (
|
||||
<RecordFieldListComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: 'fields-card',
|
||||
instanceId: `fields-list-${objectRecordId}`,
|
||||
}}
|
||||
>
|
||||
<PropertyBox>
|
||||
@@ -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 = ({
|
||||
|
||||
<RecordFieldListCellHoveredPortal
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={objectRecordId}
|
||||
/>
|
||||
<RecordFieldListCellEditModePortal
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={objectRecordId}
|
||||
/>
|
||||
</RecordFieldListComponentInstanceContext.Provider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user