Create form field number (#8634)
- Refactor VariableTagInput to have a reusable low-level TipTap editor - Create three primitive form fields: - Text - Number - Boolean ## Notes - We should automatically recognize the placeholder to use for every FormFieldInput, as it's done for FieldInputs. ## Design decisions Our main challenge was for variables and inputs to be able to communicate between each other. We chose an API that adds some duplication but remains simple and doesn't rely on "hacks" to work. Common styles are centralized. ## Demo "Workflow" mode with variables:  FormFieldInput mode, without variables:  Behavior difference between fields that can contain variables and static content, and inputs that can have either a variable value or a static value: 
This commit is contained in:
committed by
GitHub
parent
3573d89c3c
commit
d73dc1a728
@ -2,6 +2,7 @@ import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilte
|
||||
import { FormFieldInput } from '@/object-record/record-field/components/FormFieldInput';
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
||||
import { WorkflowVariablePicker } from '@/workflow/components/WorkflowVariablePicker';
|
||||
import { WorkflowRecordCreateAction } from '@/workflow/types/Workflow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
@ -11,6 +12,7 @@ import {
|
||||
isDefined,
|
||||
useIcons,
|
||||
} from 'twenty-ui';
|
||||
import { JsonValue } from 'type-fest';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
@ -55,7 +57,7 @@ export const WorkflowEditActionFormRecordCreate = ({
|
||||
|
||||
const handleFieldChange = (
|
||||
fieldName: keyof SendEmailFormData,
|
||||
updatedValue: string,
|
||||
updatedValue: JsonValue,
|
||||
) => {
|
||||
const newFormData: SendEmailFormData = {
|
||||
...formData,
|
||||
@ -163,17 +165,21 @@ export const WorkflowEditActionFormRecordCreate = ({
|
||||
|
||||
<HorizontalSeparator noMargin />
|
||||
|
||||
{editableFields.map((field) => (
|
||||
<FormFieldInput
|
||||
key={field.id}
|
||||
recordFieldInputdId={field.id}
|
||||
label={field.label}
|
||||
value={formData[field.name] as string}
|
||||
onChange={(value) => {
|
||||
handleFieldChange(field.name, value);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{editableFields.map((field) => {
|
||||
const currentValue = formData[field.name] as JsonValue;
|
||||
|
||||
return (
|
||||
<FormFieldInput
|
||||
key={field.id}
|
||||
defaultValue={currentValue}
|
||||
field={field}
|
||||
onPersist={(value) => {
|
||||
handleFieldChange(field.name, value);
|
||||
}}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</WorkflowEditGenericFormBase>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,10 +2,11 @@ import { GMAIL_SEND_SCOPE } from '@/accounts/constants/GmailSendScope';
|
||||
import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput';
|
||||
import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth';
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
||||
import { VariableTagInput } from '@/workflow/search-variables/components/VariableTagInput';
|
||||
import { WorkflowVariablePicker } from '@/workflow/components/WorkflowVariablePicker';
|
||||
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
||||
import { WorkflowSendEmailAction } from '@/workflow/types/Workflow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
@ -214,16 +215,16 @@ export const WorkflowEditActionFormSendEmail = ({
|
||||
name="email"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<VariableTagInput
|
||||
inputId="email-input"
|
||||
<FormTextFieldInput
|
||||
label="Email"
|
||||
placeholder="Enter receiver email"
|
||||
value={field.value}
|
||||
onChange={(email) => {
|
||||
field.onChange(email);
|
||||
readonly={actionOptions.readonly}
|
||||
defaultValue={field.value}
|
||||
onPersist={(value) => {
|
||||
field.onChange(value);
|
||||
handleSave();
|
||||
}}
|
||||
readonly={actionOptions.readonly}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@ -231,16 +232,16 @@ export const WorkflowEditActionFormSendEmail = ({
|
||||
name="subject"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<VariableTagInput
|
||||
inputId="email-subject-input"
|
||||
<FormTextFieldInput
|
||||
label="Subject"
|
||||
placeholder="Enter email subject"
|
||||
value={field.value}
|
||||
onChange={(email) => {
|
||||
field.onChange(email);
|
||||
readonly={actionOptions.readonly}
|
||||
defaultValue={field.value}
|
||||
onPersist={(value) => {
|
||||
field.onChange(value);
|
||||
handleSave();
|
||||
}}
|
||||
readonly={actionOptions.readonly}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@ -248,17 +249,16 @@ export const WorkflowEditActionFormSendEmail = ({
|
||||
name="body"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<VariableTagInput
|
||||
inputId="email-body-input"
|
||||
<FormTextFieldInput
|
||||
label="Body"
|
||||
placeholder="Enter email body"
|
||||
value={field.value}
|
||||
onChange={(email) => {
|
||||
field.onChange(email);
|
||||
readonly={actionOptions.readonly}
|
||||
defaultValue={field.value}
|
||||
onPersist={(value) => {
|
||||
field.onChange(value);
|
||||
handleSave();
|
||||
}}
|
||||
multiline
|
||||
readonly={actionOptions.readonly}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput';
|
||||
import { useGetManyServerlessFunctions } from '@/settings/serverless-functions/hooks/useGetManyServerlessFunctions';
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
||||
import VariableTagInput from '@/workflow/search-variables/components/VariableTagInput';
|
||||
import { WorkflowVariablePicker } from '@/workflow/components/WorkflowVariablePicker';
|
||||
import { FunctionInput } from '@/workflow/types/FunctionInput';
|
||||
import { WorkflowCodeAction } from '@/workflow/types/Workflow';
|
||||
import { getDefaultFunctionInputFromInputSchema } from '@/workflow/utils/getDefaultFunctionInputFromInputSchema';
|
||||
@ -203,14 +204,16 @@ export const WorkflowEditActionFormServerlessFunctionInner = ({
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<VariableTagInput
|
||||
<FormTextFieldInput
|
||||
key={pathKey}
|
||||
inputId={`input-${inputKey}`}
|
||||
label={inputKey}
|
||||
placeholder="Enter value"
|
||||
defaultValue={inputValue ? String(inputValue) : ''}
|
||||
readonly={actionOptions.readonly}
|
||||
value={`${inputValue || ''}`}
|
||||
onChange={(value) => handleInputChange(value, currentPath)}
|
||||
onPersist={(value) => {
|
||||
handleInputChange(value, currentPath);
|
||||
}}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent';
|
||||
import SearchVariablesDropdown from '@/workflow/search-variables/components/SearchVariablesDropdown';
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const StyledSearchVariablesDropdownContainer = styled.div<{
|
||||
multiline?: boolean;
|
||||
readonly?: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
${({ theme, readonly }) =>
|
||||
!readonly &&
|
||||
css`
|
||||
:hover {
|
||||
background-color: ${theme.background.transparent.light};
|
||||
}
|
||||
`}
|
||||
|
||||
${({ theme, multiline }) =>
|
||||
multiline
|
||||
? css`
|
||||
border-radius: ${theme.border.radius.sm};
|
||||
padding: ${theme.spacing(0.5)} ${theme.spacing(0)};
|
||||
position: absolute;
|
||||
right: ${theme.spacing(0)};
|
||||
top: ${theme.spacing(0)};
|
||||
`
|
||||
: css`
|
||||
background-color: ${theme.background.transparent.lighter};
|
||||
border-top-right-radius: ${theme.border.radius.sm};
|
||||
border-bottom-right-radius: ${theme.border.radius.sm};
|
||||
border: 1px solid ${theme.border.color.medium};
|
||||
`}
|
||||
`;
|
||||
|
||||
export const WorkflowVariablePicker: VariablePickerComponent = ({
|
||||
inputId,
|
||||
disabled,
|
||||
multiline,
|
||||
onVariableSelect,
|
||||
}) => {
|
||||
return (
|
||||
<StyledSearchVariablesDropdownContainer
|
||||
multiline={multiline}
|
||||
readonly={disabled}
|
||||
>
|
||||
<SearchVariablesDropdown
|
||||
inputId={inputId}
|
||||
onVariableSelect={onVariableSelect}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</StyledSearchVariablesDropdownContainer>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user