From c6e5bab4e93180638e71ab6997e713193fae3574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Mon, 7 Jul 2025 15:42:12 +0200 Subject: [PATCH] Replace hotkey scopes by focus stack (Part 4 - Inputs) (#12933) # Replace hotkey scopes by focus stack (Part 4 - Inputs) This PR is the 4th part of a refactoring aiming to deprecate the hotkey scopes api in favor of the new focus stack api which is more robust. Part 1: https://github.com/twentyhq/twenty/pull/12673 Part 2: https://github.com/twentyhq/twenty/pull/12798 Part 3: https://github.com/twentyhq/twenty/pull/12910 In this part, I refactored all inputs in the app so that each input has a unique id which can be used to track the focused element. --- .../modules/ui/input/components/TextInput.tsx | 6 +- .../components/CalendarEventDetails.tsx | 12 +- .../components/ActivityRichTextEditor.tsx | 13 +- .../files/components/AttachmentRow.tsx | 1 + .../internal/SignInUpEmailField.tsx | 5 +- .../internal/SignInUpPasswordField.tsx | 1 + .../ObjectFilterDropdownNumberInput.tsx | 1 + .../ObjectFilterDropdownTextInput.tsx | 1 + .../components/RecordBoardCardBody.tsx | 15 +- .../constants/RecordBoardCardInputIdPrefix.ts | 1 + .../components/FormBooleanFieldInput.tsx | 4 +- .../components/FormDateTimeFieldInput.tsx | 4 +- .../components/FormMultiSelectFieldInput.tsx | 4 +- .../components/FormNumberFieldInput.tsx | 8 +- .../components/FormRawJsonFieldInput.tsx | 4 +- .../components/FormSelectFieldInput.tsx | 12 +- .../components/FormSingleRecordPicker.tsx | 2 +- .../components/FormTextFieldInput.tsx | 4 +- .../components/FormUuidFieldInput.tsx | 8 +- .../types/VariablePickerComponent.ts | 2 +- .../hooks/useOpenFieldInputEditMode.ts | 24 +- .../input/components/AddressFieldInput.tsx | 7 + .../input/components/CurrencyFieldInput.tsx | 7 + .../input/components/DateFieldInput.tsx | 7 + .../input/components/DateTimeFieldInput.tsx | 7 + .../input/components/FullNameFieldInput.tsx | 7 + .../input/components/MultiItemBaseInput.tsx | 5 +- .../input/components/MultiItemFieldInput.tsx | 17 +- .../components/MultiSelectFieldInput.tsx | 15 +- .../input/components/NumberFieldInput.tsx | 7 + .../input/components/RawJsonFieldInput.tsx | 46 +-- .../components/RelationFromManyFieldInput.tsx | 22 +- .../components/RelationToOneFieldInput.tsx | 18 +- .../input/components/RichTextFieldInput.tsx | 8 +- .../input/components/SelectFieldInput.tsx | 30 +- .../input/components/TextFieldInput.tsx | 7 + .../__stories__/AddressFieldInput.stories.tsx | 50 +++- .../__stories__/ArrayFieldInput.stories.tsx | 33 ++- .../__stories__/BooleanFieldInput.stories.tsx | 13 +- .../DateTimeFieldInput.stories.tsx | 34 ++- .../__stories__/EmailsFieldInput.stories.tsx | 30 +- .../__stories__/LinksFieldInput.stories.tsx | 35 ++- .../__stories__/NumberFieldInput.stories.tsx | 37 ++- .../__stories__/PhonesFieldInput.stories.tsx | 30 +- .../__stories__/RatingFieldInput.stories.tsx | 33 ++- .../RelationFromManyFieldInput.stories.tsx | 24 +- .../RelationToOneFieldInput.stories.tsx | 22 +- .../RichTextFieldInput.stories.tsx | 26 +- .../__stories__/TextFieldInput.stories.tsx | 33 ++- .../useOpenRelationFromManyFieldInput.tsx | 7 +- .../hooks/useOpenRelationToOneFieldInput.tsx | 15 +- .../input/hooks/useRegisterInputEvents.ts | 56 ++-- .../utils/getFieldInputInstanceId.ts | 9 - .../components/RecordInlineCell.tsx | 28 +- .../components/RecordInlineCellContainer.tsx | 8 +- .../components/MultipleRecordPicker.tsx | 4 - .../record-show/components/FieldsCard.tsx | 29 +- .../RecordDetailRelationRecordsListItem.tsx | 14 +- .../constants/RecordTableCellInputIdPrefix.ts | 1 + .../hooks/useCreateNewIndexRecord.ts | 20 +- .../RecordTableCellFieldContextWrapper.tsx | 11 +- .../hooks/useOpenRecordTableCellV2.ts | 12 +- .../components/RecordTitleCell.tsx | 54 ++-- .../RecordTitleCellFieldDisplay.tsx | 11 +- .../components/RecordTitleCellFieldInput.tsx | 3 + .../RecordTitleCellTextFieldDisplay.tsx | 12 +- .../RecordTitleCellTextFieldInput.tsx | 3 + .../components/RecordTitleDoubleTextInput.tsx | 72 +++-- .../RecordTitleFullNameFieldDisplay.tsx | 33 ++- .../hooks/useRecordTitleCell.tsx | 66 +++-- .../utils/getRecordTitleCellId.ts | 9 - .../utils/getRecordFieldInputId.ts | 15 +- .../SettingsAccountsBlocklistInput.tsx | 1 + .../SetttingsAccountsImapConnectionForm.tsx | 4 + .../components/SettingsAdminGeneral.tsx | 1 + .../ConfigVariableDatabaseInput.tsx | 3 + .../components/ConfigVariableSearchInput.tsx | 1 + .../settings/components/SettingsCounter.tsx | 5 +- .../SettingsDataModelFieldDescriptionForm.tsx | 3 + .../SettingsDataModelFieldIconLabelForm.tsx | 7 +- .../SettingsObjectNewFieldSelector.tsx | 1 + .../SettingsDataModelFieldDateForm.tsx | 1 + .../SettingsDataModelFieldRelationForm.tsx | 1 + ...tingsDataModelFieldSelectFormOptionRow.tsx | 2 + .../SettingsDataModelObjectAboutForm.tsx | 8 + .../developers/components/ApiKeyInput.tsx | 2 +- .../developers/components/ApiKeyNameInput.tsx | 3 + .../SettingsDevelopersWebhookForm.tsx | 7 + ...tingsIntegrationDatabaseConnectionForm.tsx | 3 +- .../components/PlaygroundSetupForm.tsx | 1 + .../profile/components/EmailField.tsx | 1 + .../profile/components/NameFields.tsx | 7 +- .../components/SettingsRoleAssignment.tsx | 1 + ...RolePermissionsObjectLevelObjectPicker.tsx | 1 + .../components/SettingsRoleSettings.tsx | 5 + .../components/SettingsRoleLabelContainer.tsx | 1 + .../SettingsSSOIdentitiesProvidersForm.tsx | 5 +- .../components/SSO/SettingsSSOOIDCForm.tsx | 5 + .../components/SSO/SettingsSSOSAMLForm.tsx | 2 + .../SettingsServerlessFunctionNewForm.tsx | 5 + ...FunctionTabEnvironmentVariablesSection.tsx | 7 +- .../workspace/components/NameField.tsx | 1 + .../ValidationStep/components/columns.tsx | 1 + .../field/input/components/AddressInput.tsx | 124 ++++---- .../field/input/components/CurrencyInput.tsx | 3 + .../ui/field/input/components/DateInput.tsx | 3 + .../input/components/DoubleTextInput.tsx | 68 +++-- .../field/input/components/TextAreaInput.tsx | 5 +- .../ui/field/input/components/TextInput.tsx | 7 +- .../__stories__/MultiSelectInput.stories.tsx | 8 +- .../ui/input/components/AutosizeTextInput.tsx | 271 ------------------ .../ui/input/components/IconPicker.tsx | 66 +++-- .../modules/ui/input/components/TextArea.tsx | 35 ++- .../modules/ui/input/components/TextInput.tsx | 96 ++++--- .../ui/input/components/TextInputV2.tsx | 10 +- .../ui/input/components/TitleInput.tsx | 28 +- .../__stories__/AutosizeTextInput.stories.tsx | 49 ---- .../components/RelativeDatePickerHeader.tsx | 1 + .../ui/input/types/IconPickerHotkeyScope.ts | 3 - .../dropdown/components/DropdownMenuInput.tsx | 5 +- .../__stories__/Dropdown.stories.tsx | 6 +- .../modal/components/ConfirmationModal.tsx | 1 + .../components/SelectableListItem.tsx | 12 +- .../focus/types/FocusComponentType.ts | 3 +- .../hotkey/constants/DebugHotkeyScope.ts | 2 +- .../components/WorkflowStepHeader.tsx | 1 + .../WorkflowEditActionServerlessFunction.tsx | 3 + .../components/WorkflowVariablePicker.tsx | 4 +- .../components/WorkflowVariablesDropdown.tsx | 6 +- .../components/WorkspaceInviteLink.tsx | 7 +- .../components/WorkspaceInviteTeam.tsx | 1 + .../settings/SettingsWorkspaceMembers.tsx | 1 + .../data-model/SettingsObjectFieldTable.tsx | 3 +- .../data-model/SettingsObjectIndexTable.tsx | 3 +- .../settings/data-model/SettingsObjects.tsx | 1 + .../SettingsDevelopersApiKeyDetail.tsx | 1 + .../api-keys/SettingsDevelopersApiKeysNew.tsx | 1 + .../SettingsSecurityApprovedAccessDomain.tsx | 2 + .../components/AdvancedSettingsToggle.tsx | 6 +- .../menu-item/components/MenuItemToggle.tsx | 6 +- 140 files changed, 1178 insertions(+), 1004 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-board/record-board-card/constants/RecordBoardCardInputIdPrefix.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-field/utils/getFieldInputInstanceId.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/constants/RecordTableCellInputIdPrefix.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-title-cell/utils/getRecordTitleCellId.ts delete mode 100644 packages/twenty-front/src/modules/ui/input/components/AutosizeTextInput.tsx delete mode 100644 packages/twenty-front/src/modules/ui/input/components/__stories__/AutosizeTextInput.stories.tsx delete mode 100644 packages/twenty-front/src/modules/ui/input/types/IconPickerHotkeyScope.ts diff --git a/packages/twenty-chrome-extension/src/options/modules/ui/input/components/TextInput.tsx b/packages/twenty-chrome-extension/src/options/modules/ui/input/components/TextInput.tsx index cb90f5bcd..c6dc189a1 100644 --- a/packages/twenty-chrome-extension/src/options/modules/ui/input/components/TextInput.tsx +++ b/packages/twenty-chrome-extension/src/options/modules/ui/input/components/TextInput.tsx @@ -64,15 +64,15 @@ const TextInput: React.FC = ({ placeholder, icon, }) => { - const inputId = useId(); + const instanceId = useId(); return ( - {label && {label}} + {label && {label}} {icon && {icon}} onChange(e.target.value)} diff --git a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx index 19d2e151f..f21b96177 100644 --- a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx +++ b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx @@ -10,7 +10,7 @@ import { FieldContext } from '@/object-record/record-field/contexts/FieldContext import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext'; import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell'; import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox'; -import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId'; +import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; import { Chip, ChipAccent, ChipSize, ChipVariant } from 'twenty-ui/components'; import { IconCalendarEvent } from 'twenty-ui/display'; import { mapArrayToObject } from '~/utils/array/mapArrayToObject'; @@ -20,6 +20,8 @@ type CalendarEventDetailsProps = { calendarEvent: CalendarEvent; }; +const INPUT_ID_PREFIX = 'calendar-event-details'; + const StyledContainer = styled.div` background: ${({ theme }) => theme.background.secondary}; align-items: flex-start; @@ -111,10 +113,14 @@ export const CalendarEventDetails = ({ > - + diff --git a/packages/twenty-front/src/modules/activities/components/ActivityRichTextEditor.tsx b/packages/twenty-front/src/modules/activities/components/ActivityRichTextEditor.tsx index 9c4d7f3ab..03b656898 100644 --- a/packages/twenty-front/src/modules/activities/components/ActivityRichTextEditor.tsx +++ b/packages/twenty-front/src/modules/activities/components/ActivityRichTextEditor.tsx @@ -34,8 +34,7 @@ import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecor import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly'; import { isInlineCellInEditModeScopedState } from '@/object-record/record-inline-cell/states/isInlineCellInEditModeScopedState'; import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData'; -import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType'; -import { getRecordTitleCellId } from '@/object-record/record-title-cell/utils/getRecordTitleCellId'; +import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; import { BlockEditor } from '@/ui/input/editor/components/BlockEditor'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; @@ -364,11 +363,11 @@ export const ActivityRichTextEditor = ({ objectRecordId: activityId, }); - const recordTitleCellId = getRecordTitleCellId( - activityId, - labelIdentifierFieldMetadataItem?.id, - RecordTitleCellContainerType.ShowPage, - ); + const recordTitleCellId = getRecordFieldInputInstanceId({ + recordId: activityId, + fieldName: labelIdentifierFieldMetadataItem?.id, + prefix: 'activity-rich-text-editor', + }); const handleBlockEditorFocus = useRecoilCallback( ({ snapshot }) => diff --git a/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx b/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx index e9de906b3..5f126b43a 100644 --- a/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx +++ b/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx @@ -165,6 +165,7 @@ export const AttachmentRow = ({ {isEditing ? ( ( ( { return ( { return ( - + diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/constants/RecordBoardCardInputIdPrefix.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/constants/RecordBoardCardInputIdPrefix.ts new file mode 100644 index 000000000..48313262b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/constants/RecordBoardCardInputIdPrefix.ts @@ -0,0 +1 @@ +export const RECORD_BOARD_CARD_INPUT_ID_PREFIX = 'record-board-card'; diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormBooleanFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormBooleanFieldInput.tsx index ff4502bee..744049857 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormBooleanFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormBooleanFieldInput.tsx @@ -29,7 +29,7 @@ export const FormBooleanFieldInput = ({ readonly, VariablePicker, }: FormBooleanFieldInputProps) => { - const inputId = useId(); + const instanceId = useId(); const [draftValue, setDraftValue] = useState< | { @@ -105,7 +105,7 @@ export const FormBooleanFieldInput = ({ {VariablePicker && !readonly ? ( ) : null} diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormDateTimeFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormDateTimeFieldInput.tsx index 552272b24..454d3b6d7 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormDateTimeFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormDateTimeFieldInput.tsx @@ -89,7 +89,7 @@ export const FormDateTimeFieldInput = ({ isDateTimeInput: !dateOnly, }); - const inputId = useId(); + const instanceId = useId(); const [draftValue, setDraftValue] = useState( isStandaloneVariableString(defaultValue) @@ -340,7 +340,7 @@ export const FormDateTimeFieldInput = ({ {VariablePicker && !readonly ? ( ) : null} diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormMultiSelectFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormMultiSelectFieldInput.tsx index e1f5b2283..517a69c01 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormMultiSelectFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormMultiSelectFieldInput.tsx @@ -82,7 +82,7 @@ export const FormMultiSelectFieldInput = ({ placeholder, testId, }: FormMultiSelectFieldInputProps) => { - const inputId = useId(); + const instanceId = useId(); const theme = useTheme(); const hotkeyScope = @@ -268,7 +268,7 @@ export const FormMultiSelectFieldInput = ({ {VariablePicker && !readonly && ( )} diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormNumberFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormNumberFieldInput.tsx index e1d37c79d..26d9ad034 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormNumberFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormNumberFieldInput.tsx @@ -45,7 +45,7 @@ export const FormNumberFieldInput = ({ readonly, error: errorFromProps, }: FormNumberFieldInputProps) => { - const inputId = useId(); + const instanceId = useId(); const [errorMessage, setErrorMessage] = useState( undefined, ); @@ -117,7 +117,7 @@ export const FormNumberFieldInput = ({ return ( - {label ? {label} : null} + {label ? {label} : null} {draftValue.type === 'static' ? ( ) : null} diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormRawJsonFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormRawJsonFieldInput.tsx index 3f6bb3434..1f53da157 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormRawJsonFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormRawJsonFieldInput.tsx @@ -31,7 +31,7 @@ export const FormRawJsonFieldInput = ({ readonly, VariablePicker, }: FormRawJsonFieldInputProps) => { - const inputId = useId(); + const instanceId = useId(); const editor = useTextVariableEditor({ placeholder: placeholder ?? 'Enter a JSON object', @@ -80,7 +80,7 @@ export const FormRawJsonFieldInput = ({ {VariablePicker && !readonly && ( diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSelectFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSelectFieldInput.tsx index 4c972a3f3..d2a27d696 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSelectFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSelectFieldInput.tsx @@ -6,16 +6,16 @@ import { VariablePickerComponent } from '@/object-record/record-field/form-types import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope'; import { InputLabel } from '@/ui/input/components/InputLabel'; import { Select } from '@/ui/input/components/Select'; +import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString'; +import { useTheme } from '@emotion/react'; import { useId, useState } from 'react'; import { Key } from 'ts-key-enum'; import { isDefined } from 'twenty-shared/utils'; -import { SelectOption } from 'twenty-ui/input'; -import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; -import { useTheme } from '@emotion/react'; import { IconCircleOff } from 'twenty-ui/display'; +import { SelectOption } from 'twenty-ui/input'; type FormSelectFieldInputProps = { label?: string; @@ -36,7 +36,7 @@ export const FormSelectFieldInput = ({ }: FormSelectFieldInputProps) => { const theme = useTheme(); - const inputId = useId(); + const instanceId = useId(); const hotkeyScope = InlineCellHotkeyScope.InlineCell; @@ -135,7 +135,7 @@ export const FormSelectFieldInput = ({ {draftValue.type === 'static' ? (