Open showpage on workflow creation (#9714)

- Created an new component state
`isRecordEditableNameRenamingComponentState`
- Updated `useCreateNewTableRecord` to open the ShowPage on workflow
creation
- Refactored `RecordEditableName` and its components to remove the
useEffect (This was causing the recordName state to be updated after the
focus on `NavigationDrawerInput`, but we want the text so be selected
after the update).
- Introduced a new component `EditableBreadcrumbItem`
- Created an autosizing text input: This is done by a hack using a span
inside a div and the input position is set to absolute and takes the
size of the div. There are two problems that I didn't manage to fix:
If the text is too long, the title overflows, and the letter spacing is
different between the span and the input creating a small offset.


https://github.com/user-attachments/assets/4aa1e177-7458-4691-b0c8-96567b482206


New text input component:


https://github.com/user-attachments/assets/94565546-fe2b-457d-a1d8-907007e0e2ce
This commit is contained in:
Raphaël Bosi
2025-01-22 14:44:10 +01:00
committed by GitHub
parent 441b88b7e1
commit 8213995887
16 changed files with 429 additions and 179 deletions

View File

@ -1,50 +1,49 @@
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { NavigationDrawerInput } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerInput';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { EditableBreadcrumbItem } from '@/ui/navigation/bread-crumb/components/EditableBreadcrumbItem';
import styled from '@emotion/styled';
import { useEffect, useState } from 'react';
import { capitalize } from 'twenty-shared';
const StyledEditableTitleContainer = styled.div`
align-items: flex-start;
align-items: center;
display: flex;
flex-direction: row;
overflow-x: hidden;
`;
const StyledEditableTitlePrefix = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
line-height: 24px;
display: flex;
flex: 1 0 auto;
flex-direction: row;
padding: ${({ theme }) => theme.spacing(0.75)};
gap: ${({ theme }) => theme.spacing(1)};
padding: ${({ theme }) => theme.spacing(0.75)};
`;
export const RecordEditableName = ({
export const ObjectRecordShowPageBreadcrumb = ({
objectNameSingular,
objectRecordId,
objectLabelPlural,
labelIdentifierFieldMetadataItem,
}: {
objectNameSingular: string;
objectRecordId: string;
objectLabelPlural: string;
labelIdentifierFieldMetadataItem?: FieldMetadataItem;
}) => {
const [isRenaming, setIsRenaming] = useState(false);
const { record, loading } = useFindOneRecord({
objectNameSingular,
objectRecordId,
recordGqlFields: {
name: true,
[labelIdentifierFieldMetadataItem?.name ?? 'name']: true,
},
});
const [recordName, setRecordName] = useState(record?.name);
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular,
recordGqlFields: {
name: true,
[labelIdentifierFieldMetadataItem?.name ?? 'name']: true,
},
});
@ -55,18 +54,8 @@ export const RecordEditableName = ({
name: value,
},
});
setIsRenaming(false);
};
const handleCancel = () => {
setRecordName(record?.name);
setIsRenaming(false);
};
useEffect(() => {
setRecordName(record?.name);
}, [record?.name]);
if (loading) {
return null;
}
@ -77,24 +66,13 @@ export const RecordEditableName = ({
{capitalize(objectLabelPlural)}
<span>{' / '}</span>
</StyledEditableTitlePrefix>
{isRenaming ? (
<NavigationDrawerInput
value={recordName}
onChange={setRecordName}
onSubmit={handleSubmit}
onCancel={handleCancel}
onClickOutside={handleCancel}
hotkeyScope="favorites-folder-input"
/>
) : (
<NavigationDrawerItem
label={recordName}
onClick={() => setIsRenaming(true)}
rightOptions={undefined}
className="navigation-drawer-item"
active
/>
)}
<EditableBreadcrumbItem
defaultValue={record?.name ?? ''}
noValuePlaceholder={labelIdentifierFieldMetadataItem?.label ?? 'Name'}
placeholder={labelIdentifierFieldMetadataItem?.label ?? 'Name'}
onSubmit={handleSubmit}
hotkeyScope="editable-breadcrumb-item"
/>
</StyledEditableTitleContainer>
);
};

View File

@ -5,7 +5,10 @@ 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 { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
import { shouldRedirectToShowPageOnCreation } from '@/object-record/utils/shouldRedirectToShowPageOnCreation';
import { AppPath } from '@/types/AppPath';
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
@ -14,6 +17,7 @@ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRecoilCallback } from 'recoil';
import { v4 } from 'uuid';
import { FeatureFlagKey } from '~/generated/graphql';
import { useNavigateApp } from '~/hooks/useNavigateApp';
import { isDefined } from '~/utils/isDefined';
export const useCreateNewTableRecord = ({
@ -54,30 +58,69 @@ export const useCreateNewTableRecord = ({
shouldMatchRootQueryFilter: true,
});
const createNewTableRecord = async () => {
const recordId = v4();
const navigate = useNavigateApp();
if (isCommandMenuV2Enabled) {
await createOneRecord({ id: recordId });
const createNewTableRecord = useRecoilCallback(
({ set }) =>
async () => {
const recordId = v4();
openRecordInCommandMenu(recordId, objectMetadataItem.nameSingular);
return;
}
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',
});
setPendingRecordId(recordId);
setSelectedTableCellEditMode(-1, 0);
setHotkeyScope(DEFAULT_CELL_SCOPE.scope, DEFAULT_CELL_SCOPE.customScopes);
navigate(AppPath.RecordShowPage, {
objectNameSingular: objectMetadataItem.nameSingular,
objectRecordId: recordId,
});
if (isDefined(objectMetadataItem.labelIdentifierFieldMetadataId)) {
setActiveDropdownFocusIdAndMemorizePrevious(
getDropdownFocusIdForRecordField(
recordId,
objectMetadataItem.labelIdentifierFieldMetadataId,
'table-cell',
),
);
}
};
set(isUpdatingRecordEditableNameState, true);
return;
}
await createOneRecord({ id: recordId });
openRecordInCommandMenu(recordId, objectMetadataItem.nameSingular);
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',
),
);
}
},
[
createOneRecord,
isCommandMenuV2Enabled,
navigate,
objectMetadataItem.labelIdentifierFieldMetadataId,
objectMetadataItem.nameSingular,
openRecordInCommandMenu,
setActiveDropdownFocusIdAndMemorizePrevious,
setHotkeyScope,
setPendingRecordId,
setSelectedTableCellEditMode,
],
);
const createNewTableRecordInGroup = useRecoilCallback(
({ set }) =>

View File

@ -0,0 +1,6 @@
import { createState } from 'twenty-ui';
export const isUpdatingRecordEditableNameState = createState<boolean>({
key: 'isUpdatingRecordEditableNameState',
defaultValue: false,
});

View File

@ -0,0 +1,11 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
export const shouldRedirectToShowPageOnCreation = (
objectNameSingular: string,
) => {
if (objectNameSingular === CoreObjectNameSingular.Workflow) {
return true;
}
return false;
};