384 update the input of the record show page inside the command menu (#10213)
Created a new component `RecordTitleCell` with an API close to `RecordInlineCell`. This new component is an autogrowing input. It consumes the `FieldContext`. It uses some hooks and states from `RecordInlineCell` because I didn't want to duplicate all the logic, but this logic could be duplicated. Two issues that I didn't solve in this PR: - There is a flashing glitch inside the input when typing - The input of a workflow isn't focused when creating a new one. This is because of an issue with the `useHotkeyScopeOnMount` hook which is deprecated but still used in some components. Upon redirection on the workflow showpage, the hokey scope of the input is overridden by the hokey scopes of the components which use `useHotkeyScopeOnMount`. I decided not to open the input for now. ## Command menu record show page ### Single input https://github.com/user-attachments/assets/50dc235c-8f34-4445-8b04-586125606bd5 ### Double input https://github.com/user-attachments/assets/bdcfd6eb-d25e-4006-a87f-6e615e8a6e7e ## Workflow breadcrumb https://github.com/user-attachments/assets/ded38dd6-5794-4779-a4ae-b3948567595a ## Record show page ### Single input https://github.com/user-attachments/assets/8ad7a606-556a-416b-8788-93415f7989e1 ### Double input https://github.com/user-attachments/assets/55aae40b-36ae-40f1-8171-06f1a5db3532
This commit is contained in:
@ -1,26 +1,35 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { EditableBreadcrumbItem } from '@/ui/navigation/bread-crumb/components/EditableBreadcrumbItem';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
|
||||
import { RecordTitleCell } from '@/object-record/record-title-cell/components/RecordTitleCell';
|
||||
import styled from '@emotion/styled';
|
||||
import { capitalize } from 'twenty-shared';
|
||||
import { FieldMetadataType, capitalize } from 'twenty-shared';
|
||||
|
||||
const StyledEditableTitleContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledEditableTitlePrefix = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
flex-direction: row;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
padding: ${({ theme }) => theme.spacing(0.75)};
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
padding-right: ${({ theme }) => theme.spacing(1)};
|
||||
width: fit-content;
|
||||
`;
|
||||
|
||||
export const ObjectRecordShowPageBreadcrumb = ({
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
@ -40,22 +49,12 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { updateOneRecord } = useUpdateOneRecord({
|
||||
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
||||
objectNameSingular,
|
||||
recordGqlFields: {
|
||||
[labelIdentifierFieldMetadataItem?.name ?? 'name']: true,
|
||||
},
|
||||
objectRecordId,
|
||||
recordFromStore: record ?? null,
|
||||
});
|
||||
|
||||
const handleSubmit = (value: string) => {
|
||||
updateOneRecord({
|
||||
idToUpdate: objectRecordId,
|
||||
updateOneRecordInput: {
|
||||
name: value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
@ -66,13 +65,35 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
||||
{capitalize(objectLabelPlural)}
|
||||
<span>{' / '}</span>
|
||||
</StyledEditableTitlePrefix>
|
||||
<EditableBreadcrumbItem
|
||||
defaultValue={record?.name ?? ''}
|
||||
noValuePlaceholder={labelIdentifierFieldMetadataItem?.label ?? 'Name'}
|
||||
placeholder={labelIdentifierFieldMetadataItem?.label ?? 'Name'}
|
||||
onSubmit={handleSubmit}
|
||||
hotkeyScope="editable-breadcrumb-item"
|
||||
/>
|
||||
<StyledTitle>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
recordId: objectRecordId,
|
||||
recoilScopeId:
|
||||
objectRecordId + labelIdentifierFieldMetadataItem?.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
type:
|
||||
labelIdentifierFieldMetadataItem?.type ||
|
||||
FieldMetadataType.TEXT,
|
||||
iconName: '',
|
||||
fieldMetadataId: labelIdentifierFieldMetadataItem?.id ?? '',
|
||||
label: labelIdentifierFieldMetadataItem?.label || '',
|
||||
metadata: {
|
||||
fieldName: labelIdentifierFieldMetadataItem?.name || '',
|
||||
objectMetadataNameSingular: objectNameSingular,
|
||||
},
|
||||
defaultValue: labelIdentifierFieldMetadataItem?.defaultValue,
|
||||
},
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
isCentered: false,
|
||||
isDisplayModeFixHeight: true,
|
||||
}}
|
||||
>
|
||||
<RecordTitleCell sizeVariant="sm" />
|
||||
</FieldContext.Provider>
|
||||
</StyledTitle>
|
||||
</StyledEditableTitleContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,11 +7,13 @@ import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/
|
||||
import { RightDrawerTitleRecordInlineCell } from '@/object-record/record-right-drawer/components/RightDrawerTitleRecordInlineCell';
|
||||
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
|
||||
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
|
||||
import { RecordTitleCell } from '@/object-record/record-title-cell/components/RecordTitleCell';
|
||||
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
||||
import { ShowPageSummaryCardSkeletonLoader } from '@/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { FeatureFlagKey, FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
type SummaryCardProps = {
|
||||
objectNameSingular: string;
|
||||
@ -53,6 +55,10 @@ export const SummaryCard = ({
|
||||
isRecordDeleted: recordFromStore?.isDeleted,
|
||||
});
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
if (isNewRightDrawerItemLoading || !isDefined(recordFromStore)) {
|
||||
return <ShowPageSummaryCardSkeletonLoader />;
|
||||
}
|
||||
@ -93,7 +99,9 @@ export const SummaryCard = ({
|
||||
isDisplayModeFixHeight: true,
|
||||
}}
|
||||
>
|
||||
{isInRightDrawer ? (
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<RecordTitleCell sizeVariant="md" />
|
||||
) : isInRightDrawer ? (
|
||||
<RightDrawerTitleRecordInlineCell />
|
||||
) : (
|
||||
<RecordInlineCell readonly={isReadOnly} />
|
||||
|
||||
@ -5,7 +5,7 @@ import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-ce
|
||||
import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode';
|
||||
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
|
||||
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
||||
import { isUpdatingRecordEditableNameState } from '@/object-record/states/isUpdatingRecordEditableName';
|
||||
import { useRecordTitleCell } from '@/object-record/record-title-cell/hooks/useRecordTitleCell';
|
||||
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
|
||||
import { shouldRedirectToShowPageOnCreation } from '@/object-record/utils/shouldRedirectToShowPageOnCreation';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
@ -60,67 +60,59 @@ export const useCreateNewTableRecord = ({
|
||||
|
||||
const navigate = useNavigateApp();
|
||||
|
||||
const createNewTableRecord = useRecoilCallback(
|
||||
({ set }) =>
|
||||
async () => {
|
||||
const recordId = v4();
|
||||
const { openRecordTitleCell } = useRecordTitleCell();
|
||||
|
||||
if (isCommandMenuV2Enabled) {
|
||||
// TODO: Generalize this behaviour, there will be a view setting to specify
|
||||
// if the new record should be displayed in the side panel or on the record page
|
||||
if (
|
||||
shouldRedirectToShowPageOnCreation(objectMetadataItem.nameSingular)
|
||||
) {
|
||||
await createOneRecord({
|
||||
id: recordId,
|
||||
name: 'Untitled',
|
||||
});
|
||||
const createNewTableRecord = async () => {
|
||||
const recordId = v4();
|
||||
|
||||
navigate(AppPath.RecordShowPage, {
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
objectRecordId: recordId,
|
||||
});
|
||||
if (isCommandMenuV2Enabled) {
|
||||
// TODO: Generalize this behaviour, there will be a view setting to specify
|
||||
// if the new record should be displayed in the side panel or on the record page
|
||||
if (shouldRedirectToShowPageOnCreation(objectMetadataItem.nameSingular)) {
|
||||
await createOneRecord({
|
||||
id: recordId,
|
||||
name: 'Untitled',
|
||||
});
|
||||
|
||||
set(isUpdatingRecordEditableNameState, true);
|
||||
return;
|
||||
}
|
||||
navigate(AppPath.RecordShowPage, {
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
objectRecordId: recordId,
|
||||
});
|
||||
|
||||
await createOneRecord({ id: recordId });
|
||||
openRecordInCommandMenu(recordId, objectMetadataItem.nameSingular);
|
||||
// TODO: we should open the record title cell here but because
|
||||
// we are redirecting to the record show page, the hotkey scope will
|
||||
// be overridden by the hotkey scope on mount. We need to deprecate
|
||||
// the useHotkeyScopeOnMount hook.
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setPendingRecordId(recordId);
|
||||
setSelectedTableCellEditMode(-1, 0);
|
||||
setHotkeyScope(
|
||||
DEFAULT_CELL_SCOPE.scope,
|
||||
DEFAULT_CELL_SCOPE.customScopes,
|
||||
);
|
||||
await createOneRecord({ id: recordId });
|
||||
|
||||
if (isDefined(objectMetadataItem.labelIdentifierFieldMetadataId)) {
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(
|
||||
getDropdownFocusIdForRecordField(
|
||||
recordId,
|
||||
objectMetadataItem.labelIdentifierFieldMetadataId,
|
||||
'table-cell',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
[
|
||||
createOneRecord,
|
||||
isCommandMenuV2Enabled,
|
||||
navigate,
|
||||
objectMetadataItem.labelIdentifierFieldMetadataId,
|
||||
objectMetadataItem.nameSingular,
|
||||
openRecordInCommandMenu,
|
||||
setActiveDropdownFocusIdAndMemorizePrevious,
|
||||
setHotkeyScope,
|
||||
setPendingRecordId,
|
||||
setSelectedTableCellEditMode,
|
||||
],
|
||||
);
|
||||
openRecordInCommandMenu(recordId, objectMetadataItem.nameSingular);
|
||||
|
||||
openRecordTitleCell({
|
||||
recordId,
|
||||
fieldMetadataId: objectMetadataItem.labelIdentifierFieldMetadataId,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setPendingRecordId(recordId);
|
||||
setSelectedTableCellEditMode(-1, 0);
|
||||
setHotkeyScope(DEFAULT_CELL_SCOPE.scope, DEFAULT_CELL_SCOPE.customScopes);
|
||||
|
||||
if (isDefined(objectMetadataItem.labelIdentifierFieldMetadataId)) {
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(
|
||||
getDropdownFocusIdForRecordField(
|
||||
recordId,
|
||||
objectMetadataItem.labelIdentifierFieldMetadataId,
|
||||
'table-cell',
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const createNewTableRecordInGroup = useRecoilCallback(
|
||||
({ set }) =>
|
||||
|
||||
@ -0,0 +1,109 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider';
|
||||
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||
|
||||
import { useInlineCell } from '../../record-inline-cell/hooks/useInlineCell';
|
||||
|
||||
import { FieldInputClickOutsideEvent } from '@/object-record/record-field/meta-types/input/components/DateTimeFieldInput';
|
||||
import { RecordTitleCellContainer } from '@/object-record/record-title-cell/components/RecordTitleCellContainer';
|
||||
import {
|
||||
RecordTitleCellContext,
|
||||
RecordTitleCellContextProps,
|
||||
} from '@/object-record/record-title-cell/components/RecordTitleCellContext';
|
||||
import { RecordTitleCellFieldDisplay } from '@/object-record/record-title-cell/components/RecordTitleCellFieldDisplay';
|
||||
import { RecordTitleCellFieldInput } from '@/object-record/record-title-cell/components/RecordTitleCellFieldInput';
|
||||
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
|
||||
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
type RecordTitleCellProps = {
|
||||
loading?: boolean;
|
||||
sizeVariant?: 'sm' | 'md';
|
||||
};
|
||||
|
||||
export const RecordTitleCell = ({
|
||||
loading,
|
||||
sizeVariant,
|
||||
}: RecordTitleCellProps) => {
|
||||
const { fieldDefinition, recordId } = useContext(FieldContext);
|
||||
|
||||
const isFieldInputOnly = useIsFieldInputOnly();
|
||||
|
||||
const { closeInlineCell } = useInlineCell();
|
||||
|
||||
const handleEnter: FieldInputEvent = (persistField) => {
|
||||
persistField();
|
||||
closeInlineCell();
|
||||
};
|
||||
|
||||
const handleEscape = () => {
|
||||
closeInlineCell();
|
||||
};
|
||||
|
||||
const handleTab: FieldInputEvent = (persistField) => {
|
||||
persistField();
|
||||
closeInlineCell();
|
||||
};
|
||||
|
||||
const handleShiftTab: FieldInputEvent = (persistField) => {
|
||||
persistField();
|
||||
closeInlineCell();
|
||||
};
|
||||
|
||||
const handleClickOutside: FieldInputClickOutsideEvent = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(persistField, event) => {
|
||||
const recordFieldDropdownId = getDropdownFocusIdForRecordField(
|
||||
recordId,
|
||||
fieldDefinition.fieldMetadataId,
|
||||
'inline-cell',
|
||||
);
|
||||
|
||||
const activeDropdownFocusId = snapshot
|
||||
.getLoadable(activeDropdownFocusIdState)
|
||||
.getValue();
|
||||
|
||||
if (recordFieldDropdownId !== activeDropdownFocusId) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
persistField();
|
||||
closeInlineCell();
|
||||
},
|
||||
[closeInlineCell, fieldDefinition.fieldMetadataId, recordId],
|
||||
);
|
||||
|
||||
const recordTitleCellContextValue: RecordTitleCellContextProps = {
|
||||
editModeContent: (
|
||||
<RecordTitleCellFieldInput
|
||||
recordFieldInputId={getRecordFieldInputId(
|
||||
recordId,
|
||||
fieldDefinition?.metadata?.fieldName,
|
||||
)}
|
||||
onEnter={handleEnter}
|
||||
onEscape={handleEscape}
|
||||
onTab={handleTab}
|
||||
onShiftTab={handleShiftTab}
|
||||
onClickOutside={handleClickOutside}
|
||||
sizeVariant={sizeVariant}
|
||||
/>
|
||||
),
|
||||
displayModeContent: <RecordTitleCellFieldDisplay />,
|
||||
editModeContentOnly: isFieldInputOnly,
|
||||
loading: loading,
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldFocusContextProvider>
|
||||
<RecordTitleCellContext.Provider value={recordTitleCellContextValue}>
|
||||
<RecordTitleCellContainer />
|
||||
</RecordTitleCellContext.Provider>
|
||||
</FieldFocusContextProvider>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
||||
import { RecordTitleCellContext } from '@/object-record/record-title-cell/components/RecordTitleCellContext';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const RecordTitleCellContainer = () => {
|
||||
const { displayModeContent, editModeContent } = useContext(
|
||||
RecordTitleCellContext,
|
||||
);
|
||||
|
||||
const { isInlineCellInEditMode } = useInlineCell();
|
||||
|
||||
return <>{isInlineCellInEditMode ? editModeContent : displayModeContent}</>;
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
import { createContext, ReactElement } from 'react';
|
||||
|
||||
export type RecordTitleCellContextProps = {
|
||||
editModeContent?: ReactElement;
|
||||
editModeContentOnly?: boolean;
|
||||
displayModeContent?: ReactElement;
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
const defaultRecordTitleCellContextProp: RecordTitleCellContextProps = {
|
||||
editModeContent: undefined,
|
||||
editModeContentOnly: false,
|
||||
displayModeContent: undefined,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
export const RecordTitleCellContext =
|
||||
createContext<RecordTitleCellContextProps>(defaultRecordTitleCellContextProp);
|
||||
@ -0,0 +1,24 @@
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
||||
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
||||
import { RecordTitleCellSingleTextDisplayMode } from '@/object-record/record-title-cell/components/RecordTitleCellTextFieldDisplay';
|
||||
import { RecordTitleFullNameFieldDisplay } from '@/object-record/record-title-cell/components/RecordTitleFullNameFieldDisplay';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const RecordTitleCellFieldDisplay = () => {
|
||||
const { fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
if (!isFieldText(fieldDefinition) && !isFieldFullName(fieldDefinition)) {
|
||||
throw new Error('Field definition is not a text or full name field');
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isFieldText(fieldDefinition) ? (
|
||||
<RecordTitleCellSingleTextDisplayMode />
|
||||
) : isFieldFullName(fieldDefinition) ? (
|
||||
<RecordTitleFullNameFieldDisplay />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,66 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope';
|
||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
||||
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
||||
import { RecordTitleCellTextFieldInput } from '@/object-record/record-title-cell/components/RecordTitleCellTextFieldInput';
|
||||
import { RecordTitleFullNameFieldInput } from '@/object-record/record-title-cell/components/RecordTitleFullNameFieldInput';
|
||||
|
||||
type RecordTitleCellFieldInputProps = {
|
||||
recordFieldInputId: string;
|
||||
onClickOutside?: (
|
||||
persist: () => void,
|
||||
event: MouseEvent | TouchEvent,
|
||||
) => void;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
onTab?: FieldInputEvent;
|
||||
onShiftTab?: FieldInputEvent;
|
||||
sizeVariant?: 'sm' | 'md';
|
||||
};
|
||||
|
||||
export const RecordTitleCellFieldInput = ({
|
||||
sizeVariant,
|
||||
recordFieldInputId,
|
||||
onEnter,
|
||||
onEscape,
|
||||
onShiftTab,
|
||||
onTab,
|
||||
onClickOutside,
|
||||
}: RecordTitleCellFieldInputProps) => {
|
||||
const { fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
if (!isFieldText(fieldDefinition) && !isFieldFullName(fieldDefinition)) {
|
||||
throw new Error('Field definition is not a text or full name field');
|
||||
}
|
||||
|
||||
return (
|
||||
<RecordFieldInputScope
|
||||
recordFieldInputScopeId={getScopeIdFromComponentId(recordFieldInputId)}
|
||||
>
|
||||
{isFieldText(fieldDefinition) ? (
|
||||
<RecordTitleCellTextFieldInput
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
onClickOutside={onClickOutside}
|
||||
onTab={onTab}
|
||||
onShiftTab={onShiftTab}
|
||||
sizeVariant={sizeVariant}
|
||||
/>
|
||||
) : isFieldFullName(fieldDefinition) ? (
|
||||
<RecordTitleFullNameFieldInput
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
onClickOutside={onClickOutside}
|
||||
onTab={onTab}
|
||||
onShiftTab={onShiftTab}
|
||||
sizeVariant={sizeVariant}
|
||||
/>
|
||||
) : null}
|
||||
</RecordFieldInputScope>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,40 @@
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
||||
import { useRecordValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
import styled from '@emotion/styled';
|
||||
import { useContext } from 'react';
|
||||
import { OverflowingTextWithTooltip } from 'twenty-ui';
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
align-items: center;
|
||||
background: inherit;
|
||||
border: none;
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
:hover {
|
||||
background: ${({ theme }) => theme.background.transparent.light};
|
||||
}
|
||||
`;
|
||||
|
||||
export const RecordTitleCellSingleTextDisplayMode = () => {
|
||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const recordValue = useRecordValue(recordId);
|
||||
|
||||
const { openInlineCell } = useInlineCell();
|
||||
|
||||
return (
|
||||
<StyledDiv onClick={() => openInlineCell()}>
|
||||
<OverflowingTextWithTooltip
|
||||
text={
|
||||
recordValue?.[fieldDefinition.metadata.fieldName] ||
|
||||
fieldDefinition.label
|
||||
}
|
||||
/>
|
||||
</StyledDiv>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,78 @@
|
||||
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
||||
import { useTextField } from '@/object-record/record-field/meta-types/hooks/useTextField';
|
||||
import { FieldInputClickOutsideEvent } from '@/object-record/record-field/meta-types/input/components/DateTimeFieldInput';
|
||||
import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents';
|
||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { useRef } from 'react';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { turnIntoUndefinedIfWhitespacesOnly } from '~/utils/string/turnIntoUndefinedIfWhitespacesOnly';
|
||||
|
||||
type RecordTitleCellTextFieldInputProps = {
|
||||
onClickOutside?: FieldInputClickOutsideEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
onTab?: FieldInputEvent;
|
||||
onShiftTab?: FieldInputEvent;
|
||||
sizeVariant?: 'sm' | 'md';
|
||||
};
|
||||
|
||||
export const RecordTitleCellTextFieldInput = ({
|
||||
sizeVariant,
|
||||
onEnter,
|
||||
onEscape,
|
||||
onClickOutside,
|
||||
onTab,
|
||||
onShiftTab,
|
||||
}: RecordTitleCellTextFieldInputProps) => {
|
||||
const { fieldDefinition, draftValue, hotkeyScope, setDraftValue } =
|
||||
useTextField();
|
||||
|
||||
const wrapperRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleChange = (newText: string) => {
|
||||
setDraftValue(turnIntoUndefinedIfWhitespacesOnly(newText));
|
||||
};
|
||||
|
||||
const persistField = usePersistField();
|
||||
|
||||
useRegisterInputEvents<string>({
|
||||
inputRef: wrapperRef,
|
||||
inputValue: draftValue ?? '',
|
||||
onEnter: (inputValue) => {
|
||||
onEnter?.(() => persistField(inputValue));
|
||||
},
|
||||
onEscape: (inputValue) => {
|
||||
onEscape?.(() => persistField(inputValue));
|
||||
},
|
||||
onClickOutside: (event, inputValue) => {
|
||||
onClickOutside?.(() => persistField(inputValue), event);
|
||||
},
|
||||
onTab: (inputValue) => {
|
||||
onTab?.(() => persistField(inputValue));
|
||||
},
|
||||
onShiftTab: (inputValue) => {
|
||||
onShiftTab?.(() => persistField(inputValue));
|
||||
},
|
||||
hotkeyScope,
|
||||
});
|
||||
|
||||
const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (isDefined(draftValue)) {
|
||||
event.target.select();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TextInputV2
|
||||
autoGrow
|
||||
sizeVariant={sizeVariant}
|
||||
inheritFontStyles
|
||||
value={draftValue ?? ''}
|
||||
onChange={handleChange}
|
||||
placeholder={fieldDefinition.label}
|
||||
onFocus={handleFocus}
|
||||
autoFocus
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,235 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { ClipboardEvent, useEffect, useRef, useState } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { FieldDoubleText } from '@/object-record/record-field/types/FieldDoubleText';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { splitFullName } from '~/utils/format/spiltFullName';
|
||||
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
justify-content: inherit;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledTextInputWrapper = styled.div`
|
||||
max-width: 50%;
|
||||
`;
|
||||
|
||||
type RecordTitleDoubleTextInputProps = {
|
||||
firstValue: string;
|
||||
secondValue: string;
|
||||
firstValuePlaceholder: string;
|
||||
secondValuePlaceholder: string;
|
||||
hotkeyScope: string;
|
||||
onEnter: (newDoubleTextValue: FieldDoubleText) => void;
|
||||
onEscape: (newDoubleTextValue: FieldDoubleText) => void;
|
||||
onTab?: (newDoubleTextValue: FieldDoubleText) => void;
|
||||
onShiftTab?: (newDoubleTextValue: FieldDoubleText) => void;
|
||||
onClickOutside: (
|
||||
event: MouseEvent | TouchEvent,
|
||||
newDoubleTextValue: FieldDoubleText,
|
||||
) => void;
|
||||
onChange?: (newDoubleTextValue: FieldDoubleText) => void;
|
||||
onPaste?: (newDoubleTextValue: FieldDoubleText) => void;
|
||||
sizeVariant?: 'sm' | 'md';
|
||||
};
|
||||
|
||||
export const RecordTitleDoubleTextInput = ({
|
||||
firstValue,
|
||||
secondValue,
|
||||
firstValuePlaceholder,
|
||||
secondValuePlaceholder,
|
||||
hotkeyScope,
|
||||
onClickOutside,
|
||||
onEnter,
|
||||
onEscape,
|
||||
onShiftTab,
|
||||
onTab,
|
||||
onChange,
|
||||
onPaste,
|
||||
sizeVariant,
|
||||
}: RecordTitleDoubleTextInputProps) => {
|
||||
const [firstInternalValue, setFirstInternalValue] = useState(firstValue);
|
||||
const [secondInternalValue, setSecondInternalValue] = useState(secondValue);
|
||||
|
||||
const firstValueInputRef = useRef<HTMLInputElement>(null);
|
||||
const secondValueInputRef = useRef<HTMLInputElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setFirstInternalValue(firstValue);
|
||||
setSecondInternalValue(secondValue);
|
||||
}, [firstValue, secondValue]);
|
||||
|
||||
const handleChange = (
|
||||
newFirstValue: string,
|
||||
newSecondValue: string,
|
||||
): void => {
|
||||
setFirstInternalValue(newFirstValue);
|
||||
setSecondInternalValue(newSecondValue);
|
||||
|
||||
onChange?.({
|
||||
firstValue: newFirstValue,
|
||||
secondValue: newSecondValue,
|
||||
});
|
||||
};
|
||||
|
||||
const [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left');
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
onEnter({
|
||||
firstValue: firstInternalValue,
|
||||
secondValue: secondInternalValue,
|
||||
});
|
||||
},
|
||||
hotkeyScope,
|
||||
[onEnter, firstInternalValue, secondInternalValue],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
onEscape({
|
||||
firstValue: firstInternalValue,
|
||||
secondValue: secondInternalValue,
|
||||
});
|
||||
},
|
||||
hotkeyScope,
|
||||
[onEscape, firstInternalValue, secondInternalValue],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
'tab',
|
||||
() => {
|
||||
if (focusPosition === 'left') {
|
||||
setFocusPosition('right');
|
||||
secondValueInputRef.current?.focus();
|
||||
} else {
|
||||
onTab?.({
|
||||
firstValue: firstInternalValue,
|
||||
secondValue: secondInternalValue,
|
||||
});
|
||||
}
|
||||
},
|
||||
hotkeyScope,
|
||||
[onTab, firstInternalValue, secondInternalValue, focusPosition],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
'shift+tab',
|
||||
() => {
|
||||
if (focusPosition === 'right') {
|
||||
setFocusPosition('left');
|
||||
firstValueInputRef.current?.focus();
|
||||
} else {
|
||||
onShiftTab?.({
|
||||
firstValue: firstInternalValue,
|
||||
secondValue: secondInternalValue,
|
||||
});
|
||||
}
|
||||
},
|
||||
hotkeyScope,
|
||||
[onShiftTab, firstInternalValue, secondInternalValue, focusPosition],
|
||||
);
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [containerRef],
|
||||
callback: (event) => {
|
||||
onClickOutside?.(event, {
|
||||
firstValue: firstInternalValue,
|
||||
secondValue: secondInternalValue,
|
||||
});
|
||||
},
|
||||
enabled: isDefined(onClickOutside),
|
||||
listenerId: 'record-title-double-text-input',
|
||||
});
|
||||
|
||||
const handleOnPaste = (event: ClipboardEvent<HTMLInputElement>) => {
|
||||
if (firstInternalValue.length > 0 || secondInternalValue.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const name = event.clipboardData.getData('Text');
|
||||
|
||||
const splittedName = splitFullName(name);
|
||||
|
||||
onPaste?.({
|
||||
firstValue: splittedName[0],
|
||||
secondValue: splittedName[1],
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickToPreventParentClickEvents = (
|
||||
event: React.MouseEvent<HTMLInputElement>,
|
||||
) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledContainer ref={containerRef}>
|
||||
<StyledTextInputWrapper>
|
||||
<TextInputV2
|
||||
autoGrow
|
||||
sizeVariant={sizeVariant}
|
||||
autoComplete="off"
|
||||
inheritFontStyles
|
||||
autoFocus
|
||||
onFocus={(event: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (isDefined(firstInternalValue)) {
|
||||
event.target.select();
|
||||
}
|
||||
setFocusPosition('left');
|
||||
}}
|
||||
ref={firstValueInputRef}
|
||||
placeholder={firstValuePlaceholder}
|
||||
value={firstInternalValue}
|
||||
onChange={(text: string) => {
|
||||
handleChange(
|
||||
turnIntoEmptyStringIfWhitespacesOnly(text),
|
||||
secondInternalValue,
|
||||
);
|
||||
}}
|
||||
onPaste={(event: ClipboardEvent<HTMLInputElement>) =>
|
||||
handleOnPaste(event)
|
||||
}
|
||||
onClick={handleClickToPreventParentClickEvents}
|
||||
/>
|
||||
</StyledTextInputWrapper>
|
||||
<StyledTextInputWrapper>
|
||||
<TextInputV2
|
||||
autoGrow
|
||||
sizeVariant={sizeVariant}
|
||||
autoComplete="off"
|
||||
inheritFontStyles
|
||||
onFocus={(event: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (isDefined(secondInternalValue)) {
|
||||
event.target.select();
|
||||
}
|
||||
setFocusPosition('right');
|
||||
}}
|
||||
ref={secondValueInputRef}
|
||||
placeholder={secondValuePlaceholder}
|
||||
value={secondInternalValue}
|
||||
onChange={(text: string) => {
|
||||
handleChange(
|
||||
firstInternalValue,
|
||||
turnIntoEmptyStringIfWhitespacesOnly(text),
|
||||
);
|
||||
}}
|
||||
onClick={handleClickToPreventParentClickEvents}
|
||||
/>
|
||||
</StyledTextInputWrapper>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,43 @@
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useFullNameFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useFullNameFieldDisplay';
|
||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useContext } from 'react';
|
||||
import { OverflowingTextWithTooltip } from 'twenty-ui';
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
align-items: center;
|
||||
background: inherit;
|
||||
border: none;
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
:hover {
|
||||
background: ${({ theme }) => theme.background.transparent.light};
|
||||
}
|
||||
`;
|
||||
|
||||
export const RecordTitleFullNameFieldDisplay = () => {
|
||||
const { fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const { openInlineCell } = useInlineCell();
|
||||
|
||||
const { fieldValue } = useFullNameFieldDisplay();
|
||||
|
||||
const content = [fieldValue?.firstName, fieldValue?.lastName]
|
||||
.filter(isNonEmptyString)
|
||||
.join(' ')
|
||||
.trim();
|
||||
|
||||
return (
|
||||
<StyledDiv onClick={() => openInlineCell()}>
|
||||
<OverflowingTextWithTooltip
|
||||
text={isNonEmptyString(content) ? content : fieldDefinition.label}
|
||||
/>
|
||||
</StyledDiv>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,102 @@
|
||||
import { useFullNameField } from '@/object-record/record-field/meta-types/hooks/useFullNameField';
|
||||
import {
|
||||
FieldInputClickOutsideEvent,
|
||||
FieldInputEvent,
|
||||
} from '@/object-record/record-field/meta-types/input/components/DateTimeFieldInput';
|
||||
import { FIRST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS } from '@/object-record/record-field/meta-types/input/constants/FirstNamePlaceholder';
|
||||
import { LAST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS } from '@/object-record/record-field/meta-types/input/constants/LastNamePlaceholder';
|
||||
import { isDoubleTextFieldEmpty } from '@/object-record/record-field/meta-types/input/utils/isDoubleTextFieldEmpty';
|
||||
import { FieldDoubleText } from '@/object-record/record-field/types/FieldDoubleText';
|
||||
import { RecordTitleDoubleTextInput } from './RecordTitleDoubleTextInput';
|
||||
|
||||
type RecordTitleFullNameFieldInputProps = {
|
||||
onClickOutside?: FieldInputClickOutsideEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
onTab?: FieldInputEvent;
|
||||
onShiftTab?: FieldInputEvent;
|
||||
sizeVariant?: 'sm' | 'md';
|
||||
};
|
||||
|
||||
export const RecordTitleFullNameFieldInput = ({
|
||||
onEnter,
|
||||
onEscape,
|
||||
onClickOutside,
|
||||
onTab,
|
||||
onShiftTab,
|
||||
sizeVariant,
|
||||
}: RecordTitleFullNameFieldInputProps) => {
|
||||
const { hotkeyScope, draftValue, setDraftValue, persistFullNameField } =
|
||||
useFullNameField();
|
||||
|
||||
const convertToFullName = (newDoubleText: FieldDoubleText) => {
|
||||
return {
|
||||
firstName: newDoubleText.firstValue.trim(),
|
||||
lastName: newDoubleText.secondValue.trim(),
|
||||
};
|
||||
};
|
||||
|
||||
const getRequiredDraftValueFromDoubleText = (
|
||||
newDoubleText: FieldDoubleText,
|
||||
) => {
|
||||
return isDoubleTextFieldEmpty(newDoubleText)
|
||||
? undefined
|
||||
: convertToFullName(newDoubleText);
|
||||
};
|
||||
|
||||
const handleEnter = (newDoubleText: FieldDoubleText) => {
|
||||
onEnter?.(() => persistFullNameField(convertToFullName(newDoubleText)));
|
||||
};
|
||||
|
||||
const handleEscape = (newDoubleText: FieldDoubleText) => {
|
||||
onEscape?.(() => persistFullNameField(convertToFullName(newDoubleText)));
|
||||
};
|
||||
|
||||
const handleClickOutside = (
|
||||
event: MouseEvent | TouchEvent,
|
||||
newDoubleText: FieldDoubleText,
|
||||
) => {
|
||||
onClickOutside?.(
|
||||
() => persistFullNameField(convertToFullName(newDoubleText)),
|
||||
event,
|
||||
);
|
||||
};
|
||||
|
||||
const handleTab = (newDoubleText: FieldDoubleText) => {
|
||||
onTab?.(() => persistFullNameField(convertToFullName(newDoubleText)));
|
||||
};
|
||||
|
||||
const handleShiftTab = (newDoubleText: FieldDoubleText) => {
|
||||
onShiftTab?.(() => persistFullNameField(convertToFullName(newDoubleText)));
|
||||
};
|
||||
|
||||
const handleChange = (newDoubleText: FieldDoubleText) => {
|
||||
setDraftValue(getRequiredDraftValueFromDoubleText(newDoubleText));
|
||||
};
|
||||
|
||||
const handlePaste = (newDoubleText: FieldDoubleText) => {
|
||||
setDraftValue(getRequiredDraftValueFromDoubleText(newDoubleText));
|
||||
};
|
||||
|
||||
return (
|
||||
<RecordTitleDoubleTextInput
|
||||
firstValue={draftValue?.firstName ?? ''}
|
||||
secondValue={draftValue?.lastName ?? ''}
|
||||
firstValuePlaceholder={
|
||||
FIRST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS
|
||||
}
|
||||
secondValuePlaceholder={
|
||||
LAST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS
|
||||
}
|
||||
onClickOutside={handleClickOutside}
|
||||
onEnter={handleEnter}
|
||||
onEscape={handleEscape}
|
||||
onShiftTab={handleShiftTab}
|
||||
onTab={handleTab}
|
||||
onPaste={handlePaste}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onChange={handleChange}
|
||||
sizeVariant={sizeVariant}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,73 @@
|
||||
import { isInlineCellInEditModeScopedState } from '@/object-record/record-inline-cell/states/isInlineCellInEditModeScopedState';
|
||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const useRecordTitleCell = () => {
|
||||
const { goBackToPreviousDropdownFocusId } =
|
||||
useGoBackToPreviousDropdownFocusId();
|
||||
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const closeRecordTitleCell = useRecoilCallback(
|
||||
({ set }) =>
|
||||
({
|
||||
recordId,
|
||||
fieldMetadataId,
|
||||
}: {
|
||||
recordId: string;
|
||||
fieldMetadataId: string;
|
||||
}) => {
|
||||
set(
|
||||
isInlineCellInEditModeScopedState(recordId + fieldMetadataId),
|
||||
false,
|
||||
);
|
||||
|
||||
goBackToPreviousHotkeyScope();
|
||||
|
||||
goBackToPreviousDropdownFocusId();
|
||||
},
|
||||
[goBackToPreviousDropdownFocusId, goBackToPreviousHotkeyScope],
|
||||
);
|
||||
|
||||
const openRecordTitleCell = useRecoilCallback(
|
||||
({ set }) =>
|
||||
({
|
||||
recordId,
|
||||
fieldMetadataId,
|
||||
customEditHotkeyScopeForField,
|
||||
}: {
|
||||
recordId: string;
|
||||
fieldMetadataId: string;
|
||||
customEditHotkeyScopeForField?: HotkeyScope;
|
||||
}) => {
|
||||
set(
|
||||
isInlineCellInEditModeScopedState(recordId + fieldMetadataId),
|
||||
true,
|
||||
);
|
||||
|
||||
if (isDefined(customEditHotkeyScopeForField)) {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
customEditHotkeyScopeForField.scope,
|
||||
customEditHotkeyScopeForField.customScopes,
|
||||
);
|
||||
} else {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
InlineCellHotkeyScope.InlineCell,
|
||||
);
|
||||
}
|
||||
},
|
||||
[setHotkeyScopeAndMemorizePreviousScope],
|
||||
);
|
||||
|
||||
return {
|
||||
closeRecordTitleCell,
|
||||
openRecordTitleCell,
|
||||
};
|
||||
};
|
||||
@ -1,6 +0,0 @@
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
export const isUpdatingRecordEditableNameState = createState<boolean>({
|
||||
key: 'isUpdatingRecordEditableNameState',
|
||||
defaultValue: false,
|
||||
});
|
||||
Reference in New Issue
Block a user