From 637a7f0e6418e0e44cf0289cfbfdd493f3f11b8b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?=
<71827178+bosiraphael@users.noreply.github.com>
Date: Fri, 11 Apr 2025 11:07:36 +0200
Subject: [PATCH] Fix workflow dropdown hotkey scope (#11518)
The hokey scope was set on focus of the outermost container of the field
inputs. When clicking on a dropdown inside this container, the hokey
scope was set first by the dropdown and then by the focused container.
This led to the previous hotkey scope being overwritten.
The fix consists in moving the hokey scope setter on focus to the inner
container.
Before:
https://github.com/user-attachments/assets/12538c5b-43ab-4b76-a867-7eda6992b1ea
After:
https://github.com/user-attachments/assets/002fedba-010c-41e9-bec6-d5d80cb2dcc5
---
.../components/FormBooleanFieldInput.tsx | 6 +-
.../components/FormDateTimeFieldInput.tsx | 4 +-
.../components/FormFieldInputContainer.tsx | 23 +----
.../FormFieldInputInnerContainer.tsx | 90 +++++++++++++++++++
.../FormFieldInputInputContainer.tsx | 33 -------
.../components/FormMultiSelectFieldInput.tsx | 8 +-
.../components/FormNumberFieldInput.tsx | 6 +-
.../components/FormRawJsonFieldInput.tsx | 8 +-
.../components/FormSelectFieldInput.tsx | 6 +-
.../components/FormSingleRecordPicker.tsx | 5 +-
.../components/FormTextFieldInput.tsx | 6 +-
.../components/FormUuidFieldInput.tsx | 6 +-
.../WorkflowEditActionFormBuilder.tsx | 10 +--
.../components/WorkflowVariablesDropdown.tsx | 12 ++-
14 files changed, 133 insertions(+), 90 deletions(-)
create mode 100644 packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormFieldInputInnerContainer.tsx
delete mode 100644 packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormFieldInputInputContainer.tsx
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 ae7a78e8a..ff4502bee 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
@@ -1,5 +1,5 @@
import { FormFieldInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputContainer';
-import { FormFieldInputInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputInputContainer';
+import { FormFieldInputInnerContainer } from '@/object-record/record-field/form-types/components/FormFieldInputInnerContainer';
import { FormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/FormFieldInputRowContainer';
import { VariableChipStandalone } from '@/object-record/record-field/form-types/components/VariableChipStandalone';
import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent';
@@ -84,7 +84,7 @@ export const FormBooleanFieldInput = ({
{label ? {label} : null}
-
{draftValue.type === 'static' ? (
@@ -101,7 +101,7 @@ export const FormBooleanFieldInput = ({
onRemove={readonly ? undefined : handleUnlinkVariable}
/>
)}
-
+
{VariablePicker && !readonly ? (
{
- const {
- goBackToPreviousHotkeyScope,
- setHotkeyScopeAndMemorizePreviousScope,
- } = usePreviousHotkeyScope();
-
- const onFocus = () => {
- setHotkeyScopeAndMemorizePreviousScope(
- FormFieldInputHotKeyScope.FormFieldInput,
- );
- };
-
- const onBlur = () => {
- goBackToPreviousHotkeyScope();
- };
-
return (
-
+
{children}
);
diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormFieldInputInnerContainer.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormFieldInputInnerContainer.tsx
new file mode 100644
index 000000000..32e6e7a88
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormFieldInputInnerContainer.tsx
@@ -0,0 +1,90 @@
+import { FormFieldInputHotKeyScope } from '@/object-record/record-field/form-types/constants/FormFieldInputHotKeyScope';
+import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
+import { css } from '@emotion/react';
+import styled from '@emotion/styled';
+import { forwardRef, HTMLAttributes, Ref } from 'react';
+
+type FormFieldInputInnerContainerProps = {
+ hasRightElement: boolean;
+ multiline?: boolean;
+ readonly?: boolean;
+ preventSetHotkeyScope?: boolean;
+};
+
+const StyledFormFieldInputInnerContainer = styled.div`
+ background-color: ${({ theme }) => theme.background.transparent.lighter};
+ border: 1px solid ${({ theme }) => theme.border.color.medium};
+ border-top-left-radius: ${({ theme }) => theme.border.radius.sm};
+ border-bottom-left-radius: ${({ theme }) => theme.border.radius.sm};
+
+ ${({ multiline, hasRightElement, theme }) =>
+ multiline || !hasRightElement
+ ? css`
+ border-right: auto;
+ border-bottom-right-radius: ${theme.border.radius.sm};
+ border-top-right-radius: ${theme.border.radius.sm};
+ `
+ : css`
+ border-right: none;
+ border-bottom-right-radius: none;
+ border-top-right-radius: none;
+ `}
+
+ box-sizing: border-box;
+ display: flex;
+ overflow: ${({ multiline }) => (multiline ? 'auto' : 'hidden')};
+ width: 100%;
+`;
+
+export const FormFieldInputInnerContainer = forwardRef(
+ (
+ {
+ className,
+ children,
+ onFocus,
+ onBlur,
+ hasRightElement,
+ multiline,
+ readonly,
+ preventSetHotkeyScope = false,
+ }: HTMLAttributes & FormFieldInputInnerContainerProps,
+ ref: Ref,
+ ) => {
+ const {
+ goBackToPreviousHotkeyScope,
+ setHotkeyScopeAndMemorizePreviousScope,
+ } = usePreviousHotkeyScope();
+
+ const handleFocus = (e: React.FocusEvent) => {
+ onFocus?.(e);
+
+ if (!preventSetHotkeyScope) {
+ setHotkeyScopeAndMemorizePreviousScope(
+ FormFieldInputHotKeyScope.FormFieldInput,
+ );
+ }
+ };
+
+ const handleBlur = (e: React.FocusEvent) => {
+ onBlur?.(e);
+
+ if (!preventSetHotkeyScope) {
+ goBackToPreviousHotkeyScope();
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+ },
+);
diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormFieldInputInputContainer.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormFieldInputInputContainer.tsx
deleted file mode 100644
index 36dbe3d86..000000000
--- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormFieldInputInputContainer.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { css } from '@emotion/react';
-import styled from '@emotion/styled';
-
-const StyledFormFieldInputInputContainer = styled.div<{
- hasRightElement: boolean;
- multiline?: boolean;
- readonly?: boolean;
-}>`
- background-color: ${({ theme }) => theme.background.transparent.lighter};
- border: 1px solid ${({ theme }) => theme.border.color.medium};
- border-top-left-radius: ${({ theme }) => theme.border.radius.sm};
- border-bottom-left-radius: ${({ theme }) => theme.border.radius.sm};
-
- ${({ multiline, hasRightElement, theme }) =>
- multiline || !hasRightElement
- ? css`
- border-right: auto;
- border-bottom-right-radius: ${theme.border.radius.sm};
- border-top-right-radius: ${theme.border.radius.sm};
- `
- : css`
- border-right: none;
- border-bottom-right-radius: none;
- border-top-right-radius: none;
- `}
-
- box-sizing: border-box;
- display: flex;
- overflow: ${({ multiline }) => (multiline ? 'auto' : 'hidden')};
- width: 100%;
-`;
-
-export const FormFieldInputInputContainer = StyledFormFieldInputInputContainer;
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 97d47e514..7574e4ae6 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
@@ -1,7 +1,7 @@
import styled from '@emotion/styled';
import { FormFieldInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputContainer';
-import { FormFieldInputInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputInputContainer';
+import { FormFieldInputInnerContainer } from '@/object-record/record-field/form-types/components/FormFieldInputInnerContainer';
import { FormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/FormFieldInputRowContainer';
import { VariableChipStandalone } from '@/object-record/record-field/form-types/components/VariableChipStandalone';
import { FormMultiSelectFieldInputHotKeyScope } from '@/object-record/record-field/form-types/constants/FormMultiSelectFieldInputHotKeyScope';
@@ -17,9 +17,9 @@ import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariabl
import { useTheme } from '@emotion/react';
import { useId, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
+import { VisibilityHidden } from 'twenty-ui/accessibility';
import { IconChevronDown } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input';
-import { VisibilityHidden } from 'twenty-ui/accessibility';
type FormMultiSelectFieldInputProps = {
label?: string;
@@ -185,7 +185,7 @@ export const FormMultiSelectFieldInput = ({
{label ? {label} : null}
-
{draftValue.type === 'static' ? (
@@ -231,7 +231,7 @@ export const FormMultiSelectFieldInput = ({
onRemove={readonly ? undefined : handleUnlinkVariable}
/>
)}
-
+
{draftValue.type === 'static' &&
draftValue.editingMode === 'edit' && (
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 c03e35d2a..9c69d77e7 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
@@ -1,5 +1,5 @@
import { FormFieldInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputContainer';
-import { FormFieldInputInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputInputContainer';
+import { FormFieldInputInnerContainer } from '@/object-record/record-field/form-types/components/FormFieldInputInnerContainer';
import { FormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/FormFieldInputRowContainer';
import { VariableChipStandalone } from '@/object-record/record-field/form-types/components/VariableChipStandalone';
import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent';
@@ -108,7 +108,7 @@ export const FormNumberFieldInput = ({
{label ? {label} : null}
-
@@ -128,7 +128,7 @@ export const FormNumberFieldInput = ({
onRemove={readonly ? undefined : handleUnlinkVariable}
/>
)}
-
+
{VariablePicker && !readonly ? (
{label} : null}
-
-
+
{VariablePicker && !readonly && (
) : (
-
-
+
)}
{isDefined(VariablePicker) && !readonly && (
diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSingleRecordPicker.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSingleRecordPicker.tsx
index f112dc7d2..20a4f27bb 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSingleRecordPicker.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/FormSingleRecordPicker.tsx
@@ -1,6 +1,6 @@
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { FormFieldInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputContainer';
-import { FormFieldInputInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputInputContainer';
+import { FormFieldInputInnerContainer } from '@/object-record/record-field/form-types/components/FormFieldInputInnerContainer';
import { FormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/FormFieldInputRowContainer';
import { FormSingleRecordFieldChip } from '@/object-record/record-field/form-types/components/FormSingleRecordFieldChip';
import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent';
@@ -20,7 +20,7 @@ import { isDefined, isValidUuid } from 'twenty-shared/utils';
import { IconChevronDown, IconForbid } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
-const StyledFormSelectContainer = styled(FormFieldInputInputContainer)`
+const StyledFormSelectContainer = styled(FormFieldInputInnerContainer)`
justify-content: space-between;
align-items: center;
padding-right: ${({ theme }) => theme.spacing(1)};
@@ -132,6 +132,7 @@ export const FormSingleRecordPicker = ({
{label} : null}
-
-
+
{VariablePicker && !readonly ? (
{label} : null}
-
{draftValue.type === 'static' ? (
@@ -113,7 +113,7 @@ export const FormUuidFieldInput = ({
onRemove={readonly ? undefined : handleUnlinkVariable}
/>
)}
-
+
{VariablePicker && !readonly ? (
setHoveredField(null)}
>
- {
handleFieldClick(field.id);
@@ -220,7 +220,7 @@ export const WorkflowEditActionFormBuilder = ({
/>
)}
-
+
{!actionOptions.readonly &&
(isFieldSelected(field.id) || isFieldHovered(field.id)) && (
@@ -263,7 +263,7 @@ export const WorkflowEditActionFormBuilder = ({
- {
const { label, name } = getDefaultFormFieldSettings(
@@ -296,7 +296,7 @@ export const WorkflowEditActionFormBuilder = ({
{t`Add Field`}
-
+
diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdown.tsx b/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdown.tsx
index a0ccf87f3..957abebb3 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdown.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-variables/components/WorkflowVariablesDropdown.tsx
@@ -1,6 +1,8 @@
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { StyledDropdownButtonContainer } from '@/ui/layout/dropdown/components/StyledDropdownButtonContainer';
-import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
+import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
+import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
+import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { WorkflowVariablesDropdownFieldItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownFieldItems';
import { WorkflowVariablesDropdownObjectItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownObjectItems';
import { WorkflowVariablesDropdownWorkflowStepItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownWorkflowStepItems';
@@ -11,6 +13,7 @@ import { StepOutputSchema } from '@/workflow/workflow-variables/types/StepOutput
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useState } from 'react';
+import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconVariablePlus } from 'twenty-ui/display';
@@ -43,7 +46,10 @@ export const WorkflowVariablesDropdown = ({
const theme = useTheme();
const dropdownId = `${SEARCH_VARIABLES_DROPDOWN_ID}-${inputId}`;
- const { isDropdownOpen, closeDropdown } = useDropdown(dropdownId);
+ const isDropdownOpen = useRecoilValue(
+ extractComponentState(isDropdownOpenComponentState, dropdownId),
+ );
+ const { closeDropdown } = useDropdownV2();
const availableVariablesInWorkflowStep = useAvailableVariablesInWorkflowStep({
objectNameSingularToSelect,
});
@@ -68,7 +74,7 @@ export const WorkflowVariablesDropdown = ({
const handleSubItemSelect = (subItem: string) => {
onVariableSelect(subItem);
setSelectedStep(initialStep);
- closeDropdown();
+ closeDropdown(dropdownId);
};
const handleBack = () => {