Clarify forms usage (#17772)

Fixes https://github.com/twentyhq/core-team-issues/issues/1704
This commit is contained in:
BOHEUS
2026-02-10 11:35:37 +00:00
committed by GitHub
parent 202a130ac8
commit 382746dd52
7 changed files with 225 additions and 2 deletions
@@ -187,6 +187,7 @@ export const WorkflowStepDetail = ({
case 'FORM': {
return (
<WorkflowEditActionFormBuilder
triggerType={trigger?.type}
key={stepId}
action={stepDefinition.definition}
actionOptions={props}
@@ -49,7 +49,7 @@ export const WorkflowMessage = ({
return (
<StyledMessageContainer>
<FormFieldInputContainer>
<FormFieldInputRowContainer multiline maxHeight={124}>
<FormFieldInputRowContainer multiline maxHeight={145}>
<FormFieldInputInnerContainer
formFieldInputInstanceId="workflow-message"
hasRightElement={false}
@@ -5,7 +5,10 @@ import { FormFieldPlaceholder } from '@/object-record/record-field/ui/form-types
import { InputLabel } from '@/ui/input/components/InputLabel';
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
import { type WorkflowFormAction } from '@/workflow/types/Workflow';
import {
type WorkflowFormAction,
type WorkflowTriggerType,
} from '@/workflow/types/Workflow';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { WorkflowMessage } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowMessage';
@@ -21,6 +24,7 @@ import { useEffect, useState } from 'react';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
Callout,
IconChevronDown,
IconGripVertical,
IconPlus,
@@ -31,6 +35,7 @@ import { useDebouncedCallback } from 'use-debounce';
import { v4 } from 'uuid';
export type WorkflowEditActionFormBuilderProps = {
triggerType: WorkflowTriggerType | undefined;
action: WorkflowFormAction;
actionOptions:
| {
@@ -128,6 +133,7 @@ const StyledAddFieldButtonContentContainer = styled.div`
`;
export const WorkflowEditActionFormBuilder = ({
triggerType,
action,
actionOptions,
}: WorkflowEditActionFormBuilderProps) => {
@@ -136,6 +142,7 @@ export const WorkflowEditActionFormBuilder = ({
const [formData, setFormData] = useState<FormData>(action.settings.input);
const [isCalloutVisible, setIsCalloutVisible] = useState<boolean>(true);
const [selectedField, setSelectedField] = useState<string | null>(null);
const [hoveredField, setHoveredField] = useState<string | null>(null);
@@ -213,6 +220,18 @@ export const WorkflowEditActionFormBuilder = ({
return (
<>
<StyledWorkflowStepBody>
{triggerType && triggerType !== 'MANUAL' && isCalloutVisible && (
<Callout
learnMoreText={t`Learn more`}
variant={'warning'}
title={t`This form will appear in workflow runs.`}
description={t`Because this workflow is not using a manual trigger, the form will not open on top of the interface. To fill it, open the corresponding workflow run and complete the form there.`}
onClose={() => setIsCalloutVisible(false)}
learnMoreUrl={
'https://docs.twenty.com/user-guide/workflows/capabilities/workflow-actions#form'
}
/>
)}
{formData.length === 0 && (
<WorkflowMessage
title={t`Add inputs to your form`}
@@ -0,0 +1,143 @@
import styled from '@emotion/styled';
import { IconHelp, IconX } from '@ui/display/icon/components/TablerIcons';
import { IconButton, LightButton } from '@ui/input';
export type CalloutVariant =
| 'info'
| 'warning'
| 'error'
| 'neutral'
| 'success';
const StyledCalloutContainer = styled.div<{ variant: CalloutVariant }>`
background-color: ${({ theme, variant }) =>
variant === 'info'
? theme.color.blue1
: variant === 'warning'
? theme.color.yellow1
: variant === 'success'
? theme.color.green1
: variant === 'error'
? theme.color.red1
: theme.color.gray1};
border: 1px solid
${({ theme, variant }) =>
variant === 'info'
? theme.color.blue6
: variant === 'warning'
? theme.color.yellow6
: variant === 'success'
? theme.color.green6
: variant === 'error'
? theme.color.red6
: theme.color.gray6};
border-radius: ${({ theme }) => theme.border.radius.md};
box-sizing: border-box;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(4)};
position: relative;
margin-bottom: ${({ theme }) => theme.spacing(2)};
margin-left: ${({ theme }) => theme.spacing(7)};
`;
const StyledIconContainer = styled.div<{ variant: CalloutVariant }>`
align-items: flex-start;
color: ${({ theme, variant }) =>
variant === 'info'
? theme.color.blue9
: variant === 'warning'
? theme.color.orange9
: variant === 'success'
? theme.color.green9
: variant === 'error'
? theme.color.red9
: theme.color.gray9};
display: flex;
flex-shrink: 0;
padding-top: ${({ theme }) => theme.spacing(0.5)};
`;
const StyledContent = styled.div`
display: flex;
flex-direction: column;
flex-grow: 1;
gap: ${({ theme }) => theme.spacing(2)};
min-width: 0;
`;
const StyledTitle = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-family: ${({ theme }) => theme.font.family};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.medium};
line-height: 1.5;
`;
const StyledDescription = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
font-family: ${({ theme }) => theme.font.family};
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.regular};
line-height: 15px;
`;
const StyledFooter = styled.div`
align-items: center;
display: flex;
justify-content: flex-end;
margin-top: ${({ theme }) => theme.spacing(1)};
`;
const StyledCloseButton = styled(IconButton)`
position: absolute;
right: ${({ theme }) => theme.spacing(3)};
top: ${({ theme }) => theme.spacing(3)};
`;
export type CalloutProps = {
variant: CalloutVariant;
title: string;
description: string;
learnMoreText: string;
learnMoreUrl: string;
onClose: () => void;
};
export const Callout = ({
variant,
title,
description,
learnMoreText,
learnMoreUrl,
onClose,
}: CalloutProps) => {
return (
<StyledCalloutContainer variant={variant}>
<StyledIconContainer variant={variant}>
<IconHelp size={16} />
</StyledIconContainer>
<StyledContent>
<StyledTitle>{title}</StyledTitle>
<StyledDescription>{description}</StyledDescription>
<StyledFooter>
<LightButton
type={'button'}
title={learnMoreText}
onClick={() => {
window.open(learnMoreUrl, '_blank', 'noopener noreferrer');
}}
/>
</StyledFooter>
</StyledContent>
<StyledCloseButton
Icon={IconX}
size="small"
variant="tertiary"
ariaLabel={`Close`}
onClick={onClose}
/>
</StyledCalloutContainer>
);
};
@@ -0,0 +1,56 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import {
CatalogDecorator,
type CatalogStory,
ComponentDecorator,
} from '@ui/testing';
import { Callout } from '@ui/display';
import { type CalloutVariant } from '@ui/display/callout/Callout';
const meta: Meta<typeof Callout> = {
title: 'UI/Display/Callout',
component: Callout,
};
export default meta;
type Story = StoryObj<typeof Callout>;
export const Default: Story = {
args: {
variant: 'neutral',
title: 'An callout component',
description: 'Description of callout component',
learnMoreText: 'Learn more link',
},
decorators: [ComponentDecorator],
};
export const Catalog: CatalogStory<Story, typeof Callout> = {
args: {
title: 'An callout component',
description: 'Description of callout component',
learnMoreText: 'Learn more link',
},
argTypes: {
variant: { control: false },
},
parameters: {
catalog: {
dimensions: [
{
name: 'accents',
values: [
'warning',
'neutral',
'error',
'info',
'success',
] satisfies CalloutVariant[],
props: (variant: CalloutVariant) => ({ variant }),
},
],
},
},
decorators: [CatalogDecorator],
};
@@ -203,6 +203,7 @@ export {
IconHeart,
IconHeartOff,
IconHeartRateMonitor,
IconHelp,
IconHelpCircle,
IconHierarchy,
IconHierarchy2,
+3
View File
@@ -19,6 +19,8 @@ export type { BannerVariant } from './banner/components/Banner';
export { Banner } from './banner/components/Banner';
export type { SidePanelInformationBannerProps } from './banner/components/SidePanelInformationBanner';
export { SidePanelInformationBanner } from './banner/components/SidePanelInformationBanner';
export type { CalloutVariant, CalloutProps } from './callout/Callout';
export { Callout } from './callout/Callout';
export type { AnimatedCheckmarkProps } from './checkmark/components/AnimatedCheckmark';
export { AnimatedCheckmark } from './checkmark/components/AnimatedCheckmark';
export type { CheckmarkProps } from './checkmark/components/Checkmark';
@@ -272,6 +274,7 @@ export {
IconHeart,
IconHeartOff,
IconHeartRateMonitor,
IconHelp,
IconHelpCircle,
IconHierarchy,
IconHierarchy2,