diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramBaseStepNode.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramBaseStepNode.tsx
index 7d5b510a4..f6d461c31 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramBaseStepNode.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramBaseStepNode.tsx
@@ -4,14 +4,14 @@ import { NODE_HANDLE_WIDTH_PX } from '@/workflow/workflow-diagram/constants/Node
import { NODE_ICON_LEFT_MARGIN } from '@/workflow/workflow-diagram/constants/NodeIconLeftMargin';
import { NODE_ICON_WIDTH } from '@/workflow/workflow-diagram/constants/NodeIconWidth';
import { WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
+import { WorkflowDiagramNodeVariant } from '@/workflow/workflow-diagram/types/WorkflowDiagramNodeVariant';
+import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { Handle, Position } from '@xyflow/react';
import React from 'react';
import { capitalize, isDefined } from 'twenty-shared';
import { Label, OverflowingTextWithTooltip } from 'twenty-ui';
-type Variant = 'placeholder';
-
const StyledStepNodeContainer = styled.div`
display: flex;
flex-direction: column;
@@ -19,52 +19,109 @@ const StyledStepNodeContainer = styled.div`
padding-block: ${({ theme }) => theme.spacing(3)};
`;
-const StyledStepNodeType = styled(Label)`
- background-color: ${({ theme }) => theme.background.tertiary};
- border-radius: ${({ theme }) => theme.border.radius.sm}
- ${({ theme }) => theme.border.radius.sm} 0 0;
+const StyledStepNodeType = styled.div<{
+ nodeVariant: WorkflowDiagramNodeVariant;
+}>`
+ ${({ nodeVariant, theme }) => {
+ switch (nodeVariant) {
+ case 'success': {
+ return css`
+ background-color: ${theme.tag.background.turquoise};
+ color: ${theme.tag.text.turquoise};
+ `;
+ }
+ case 'failure': {
+ return css`
+ background-color: ${theme.tag.background.red};
+ color: ${theme.color.red};
+ `;
+ }
+ default: {
+ return css`
+ background-color: ${theme.background.tertiary};
+ `;
+ }
+ }
+ }}
+ align-self: flex-start;
+ border-radius: ${({ theme }) =>
+ `${theme.border.radius.sm} ${theme.border.radius.sm} 0 0`};
margin-left: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(1)} ${({ theme }) => theme.spacing(2)};
- align-self: flex-start;
.selectable.selected &,
.selectable:focus &,
.selectable:focus-visible & {
- background-color: ${({ theme }) => theme.color.blue};
- color: ${({ theme }) => theme.font.color.inverted};
+ ${({ nodeVariant, theme }) => {
+ switch (nodeVariant) {
+ case 'empty':
+ case 'default': {
+ return css`
+ background-color: ${theme.color.blue};
+ color: ${theme.font.color.inverted};
+ `;
+ }
+ }
+ }}
}
-`;
+`.withComponent(Label);
-const StyledStepNodeInnerContainer = styled.div<{ variant?: Variant }>`
+const StyledStepNodeInnerContainer = styled.div<{
+ variant: WorkflowDiagramNodeVariant;
+}>`
background-color: ${({ theme }) => theme.background.secondary};
- border: ${NODE_BORDER_WIDTH}px solid
- ${({ theme }) => theme.border.color.medium};
+ border-color: ${({ theme }) => theme.border.color.medium};
+
border-radius: ${({ theme }) => theme.border.radius.md};
+ border-style: solid;
+ border-width: ${NODE_BORDER_WIDTH}px;
+ box-shadow: ${({ variant, theme }) =>
+ variant === 'empty' ? 'none' : theme.boxShadow.strong};
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(2)};
position: relative;
- box-shadow: ${({ variant, theme }) =>
- variant === 'placeholder' ? 'none' : theme.boxShadow.strong};
.selectable.selected &,
.selectable:focus &,
.selectable:focus-visible & {
- background-color: ${({ theme }) => theme.accent.quaternary};
- border-color: ${({ theme }) => theme.color.blue};
+ ${({ theme, variant }) => {
+ switch (variant) {
+ case 'success': {
+ return css`
+ background-color: ${theme.adaptiveColors.turquoise1};
+ border-color: ${theme.adaptiveColors.turquoise4};
+ `;
+ }
+ case 'failure': {
+ return css`
+ background-color: ${theme.background.danger};
+ border-color: ${theme.color.red};
+ `;
+ }
+ default: {
+ return css`
+ background-color: ${theme.accent.quaternary};
+ border-color: ${theme.color.blue};
+ `;
+ }
+ }
+ }}
}
`;
-const StyledStepNodeLabel = styled.div<{ variant?: Variant }>`
+const StyledStepNodeLabel = styled.div<{
+ variant: WorkflowDiagramNodeVariant;
+}>`
align-items: center;
display: flex;
font-size: 13px;
font-weight: ${({ theme }) => theme.font.weight.medium};
column-gap: ${({ theme }) => theme.spacing(2)};
color: ${({ variant, theme }) =>
- variant === 'placeholder'
+ variant === 'empty'
? theme.font.color.extraLight
: theme.font.color.primary};
max-width: 200px;
@@ -106,7 +163,7 @@ export const WorkflowDiagramBaseStepNode = ({
}: {
nodeType: WorkflowDiagramStepNodeData['nodeType'];
name: string;
- variant?: Variant;
+ variant: WorkflowDiagramNodeVariant;
Icon?: React.ReactNode;
RightFloatingElement?: React.ReactNode;
}) => {
@@ -116,7 +173,7 @@ export const WorkflowDiagramBaseStepNode = ({
) : null}
-
+
{capitalize(nodeType)}
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger.tsx
index 33e0fd021..60a6bb96c 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger.tsx
@@ -15,7 +15,7 @@ export const WorkflowDiagramEmptyTrigger = () => {
}
/>
);
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase.tsx
index b9cf73c63..8539c8bd0 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase.tsx
@@ -1,6 +1,7 @@
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
import { WorkflowDiagramBaseStepNode } from '@/workflow/workflow-diagram/components/WorkflowDiagramBaseStepNode';
import { WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
+import { WorkflowDiagramNodeVariant } from '@/workflow/workflow-diagram/types/WorkflowDiagramNodeVariant';
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
@@ -17,9 +18,11 @@ const StyledStepNodeLabelIconContainer = styled.div`
export const WorkflowDiagramStepNodeBase = ({
data,
+ variant,
RightFloatingElement,
}: {
data: WorkflowDiagramStepNodeData;
+ variant: WorkflowDiagramNodeVariant;
RightFloatingElement?: React.ReactNode;
}) => {
const theme = useTheme();
@@ -93,6 +96,7 @@ export const WorkflowDiagramStepNodeBase = ({
return (
{
deleteStep(id);
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent.tsx
index 183988863..3c98a60a1 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent.tsx
@@ -1,19 +1,23 @@
import { WorkflowDiagramStepNodeBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase';
import { WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
+import { WorkflowDiagramNodeVariant } from '@/workflow/workflow-diagram/types/WorkflowDiagramNodeVariant';
import { FloatingIconButton, IconTrash } from 'twenty-ui';
export const WorkflowDiagramStepNodeEditableContent = ({
data,
selected,
+ variant,
onDelete,
}: {
data: WorkflowDiagramStepNodeData;
+ variant: WorkflowDiagramNodeVariant;
selected: boolean;
onDelete: () => void;
}) => {
return (
{
- return ;
+ return ;
};
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramStepNodeEditableContent.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramStepNodeEditableContent.stories.tsx
index 45e979ea4..31e57d049 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramStepNodeEditableContent.stories.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramStepNodeEditableContent.stories.tsx
@@ -2,9 +2,9 @@ import { Meta, StoryObj } from '@storybook/react';
import { WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { fn } from '@storybook/test';
-import { ReactFlowProvider } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { CatalogDecorator, CatalogStory } from 'twenty-ui';
+import { ReactflowDecorator } from '~/testing/decorators/ReactflowDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { WorkflowDiagramStepNodeEditableContent } from '../WorkflowDiagramStepNodeEditableContent';
@@ -17,12 +17,43 @@ export default meta;
type Story = StoryObj;
+const ALL_STEPS = [
+ {
+ nodeType: 'trigger',
+ triggerType: 'DATABASE_EVENT',
+ name: 'Record is Created',
+ },
+ { nodeType: 'trigger', triggerType: 'MANUAL', name: 'Manual' },
+ {
+ nodeType: 'action',
+ actionType: 'CREATE_RECORD',
+ name: 'Create Record',
+ },
+ {
+ nodeType: 'action',
+ actionType: 'UPDATE_RECORD',
+ name: 'Update Record',
+ },
+ {
+ nodeType: 'action',
+ actionType: 'DELETE_RECORD',
+ name: 'Delete Record',
+ },
+ {
+ nodeType: 'action',
+ actionType: 'SEND_EMAIL',
+ name: 'Send Email',
+ },
+ { nodeType: 'action', actionType: 'CODE', name: 'Code' },
+] satisfies WorkflowDiagramStepNodeData[];
+
export const All: CatalogStory<
Story,
typeof WorkflowDiagramStepNodeEditableContent
> = {
args: {
onDelete: fn(),
+ variant: 'default',
selected: false,
},
parameters: {
@@ -37,57 +68,22 @@ export const All: CatalogStory<
dimensions: [
{
name: 'step type',
- values: [
- {
- nodeType: 'trigger',
- triggerType: 'DATABASE_EVENT',
- name: 'Record is Created',
- },
- { nodeType: 'trigger', triggerType: 'MANUAL', name: 'Manual' },
- {
- nodeType: 'action',
- actionType: 'CREATE_RECORD',
- name: 'Create Record',
- },
- {
- nodeType: 'action',
- actionType: 'UPDATE_RECORD',
- name: 'Update Record',
- },
- {
- nodeType: 'action',
- actionType: 'DELETE_RECORD',
- name: 'Delete Record',
- },
- {
- nodeType: 'action',
- actionType: 'SEND_EMAIL',
- name: 'Send Email',
- },
- { nodeType: 'action', actionType: 'CODE', name: 'Code' },
- ] satisfies WorkflowDiagramStepNodeData[],
+ values: ALL_STEPS,
props: (data: WorkflowDiagramStepNodeData) => ({ data }),
},
],
},
},
- decorators: [
- CatalogDecorator,
- (Story) => {
- return (
-
-
-
- );
- },
- ],
+ decorators: [CatalogDecorator, ReactflowDecorator],
};
+
export const AllSelected: CatalogStory<
Story,
typeof WorkflowDiagramStepNodeEditableContent
> = {
args: {
onDelete: fn(),
+ variant: 'default',
selected: true,
},
parameters: {
@@ -103,48 +99,131 @@ export const AllSelected: CatalogStory<
dimensions: [
{
name: 'step type',
- values: [
- {
- nodeType: 'trigger',
- triggerType: 'DATABASE_EVENT',
- name: 'Record is Created',
- },
- { nodeType: 'trigger', triggerType: 'MANUAL', name: 'Manual' },
- {
- nodeType: 'action',
- actionType: 'CREATE_RECORD',
- name: 'Create Record',
- },
- {
- nodeType: 'action',
- actionType: 'UPDATE_RECORD',
- name: 'Update Record',
- },
- {
- nodeType: 'action',
- actionType: 'DELETE_RECORD',
- name: 'Delete Record',
- },
- {
- nodeType: 'action',
- actionType: 'SEND_EMAIL',
- name: 'Send Email',
- },
- { nodeType: 'action', actionType: 'CODE', name: 'Code' },
- ] satisfies WorkflowDiagramStepNodeData[],
+ values: ALL_STEPS,
props: (data: WorkflowDiagramStepNodeData) => ({ data }),
},
],
},
},
- decorators: [
- CatalogDecorator,
- (Story) => {
- return (
-
-
-
- );
- },
- ],
+ decorators: [CatalogDecorator, ReactflowDecorator],
+};
+
+export const AllSuccess: CatalogStory<
+ Story,
+ typeof WorkflowDiagramStepNodeEditableContent
+> = {
+ args: {
+ onDelete: fn(),
+ variant: 'success',
+ },
+ parameters: {
+ msw: graphqlMocks,
+ catalog: {
+ options: {
+ elementContainer: {
+ width: 250,
+ style: { position: 'relative' },
+ },
+ },
+ dimensions: [
+ {
+ name: 'step type',
+ values: ALL_STEPS,
+ props: (data: WorkflowDiagramStepNodeData) => ({ data }),
+ },
+ ],
+ },
+ },
+ decorators: [CatalogDecorator, ReactflowDecorator],
+};
+
+export const AllSuccessSelected: CatalogStory<
+ Story,
+ typeof WorkflowDiagramStepNodeEditableContent
+> = {
+ args: {
+ onDelete: fn(),
+ variant: 'success',
+ selected: true,
+ },
+ parameters: {
+ msw: graphqlMocks,
+ catalog: {
+ options: {
+ elementContainer: {
+ width: 250,
+ style: { position: 'relative' },
+ className: 'selectable selected',
+ },
+ },
+ dimensions: [
+ {
+ name: 'step type',
+ values: ALL_STEPS,
+ props: (data: WorkflowDiagramStepNodeData) => ({ data }),
+ },
+ ],
+ },
+ },
+ decorators: [CatalogDecorator, ReactflowDecorator],
+};
+
+export const AllFailure: CatalogStory<
+ Story,
+ typeof WorkflowDiagramStepNodeEditableContent
+> = {
+ args: {
+ onDelete: fn(),
+ variant: 'failure',
+ },
+ parameters: {
+ msw: graphqlMocks,
+ catalog: {
+ options: {
+ elementContainer: {
+ width: 250,
+ style: { position: 'relative' },
+ },
+ },
+ dimensions: [
+ {
+ name: 'step type',
+ values: ALL_STEPS,
+ props: (data: WorkflowDiagramStepNodeData) => ({ data }),
+ },
+ ],
+ },
+ },
+ decorators: [CatalogDecorator, ReactflowDecorator],
+};
+
+export const AllFailureSelected: CatalogStory<
+ Story,
+ typeof WorkflowDiagramStepNodeEditableContent
+> = {
+ args: {
+ onDelete: fn(),
+ variant: 'failure',
+ selected: true,
+ },
+ parameters: {
+ msw: graphqlMocks,
+ catalog: {
+ options: {
+ elementContainer: {
+ width: 250,
+ style: { position: 'relative' },
+ className: 'selectable selected',
+ },
+ },
+ dimensions: [
+ {
+ name: 'step type',
+ values: ALL_STEPS,
+ props: (data: WorkflowDiagramStepNodeData) => ({ data }),
+ },
+ ],
+ },
+ },
+ decorators: [CatalogDecorator, ReactflowDecorator],
};
diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/types/WorkflowDiagramNodeVariant.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/types/WorkflowDiagramNodeVariant.ts
new file mode 100644
index 000000000..6a7a5ff92
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/types/WorkflowDiagramNodeVariant.ts
@@ -0,0 +1,5 @@
+export type WorkflowDiagramNodeVariant =
+ | 'default'
+ | 'success'
+ | 'failure'
+ | 'empty';
diff --git a/packages/twenty-front/src/testing/decorators/ReactflowDecorator.tsx b/packages/twenty-front/src/testing/decorators/ReactflowDecorator.tsx
new file mode 100644
index 000000000..206604a67
--- /dev/null
+++ b/packages/twenty-front/src/testing/decorators/ReactflowDecorator.tsx
@@ -0,0 +1,10 @@
+import { Decorator } from '@storybook/react';
+import { ReactFlowProvider } from '@xyflow/react';
+
+export const ReactflowDecorator: Decorator = (Story) => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/twenty-ui/src/theme/constants/AdaptiveColorsDark.ts b/packages/twenty-ui/src/theme/constants/AdaptiveColorsDark.ts
new file mode 100644
index 000000000..c021d1201
--- /dev/null
+++ b/packages/twenty-ui/src/theme/constants/AdaptiveColorsDark.ts
@@ -0,0 +1,8 @@
+import { THEME_COMMON } from '@ui/theme/constants/ThemeCommon';
+
+export const ADAPTIVE_COLORS_DARK = {
+ turquoise1: THEME_COMMON.color.turquoise80,
+ turquoise2: THEME_COMMON.color.turquoise70,
+ turquoise3: THEME_COMMON.color.turquoise60,
+ turquoise4: THEME_COMMON.color.turquoise50,
+};
diff --git a/packages/twenty-ui/src/theme/constants/AdaptiveColorsLight.ts b/packages/twenty-ui/src/theme/constants/AdaptiveColorsLight.ts
new file mode 100644
index 000000000..69c9e1593
--- /dev/null
+++ b/packages/twenty-ui/src/theme/constants/AdaptiveColorsLight.ts
@@ -0,0 +1,8 @@
+import { THEME_COMMON } from '@ui/theme/constants/ThemeCommon';
+
+export const ADAPTIVE_COLORS_LIGHT = {
+ turquoise1: THEME_COMMON.color.turquoise10,
+ turquoise2: THEME_COMMON.color.turquoise20,
+ turquoise3: THEME_COMMON.color.turquoise30,
+ turquoise4: THEME_COMMON.color.turquoise40,
+};
diff --git a/packages/twenty-ui/src/theme/constants/ThemeDark.ts b/packages/twenty-ui/src/theme/constants/ThemeDark.ts
index 10a1e54cc..049b3ce87 100644
--- a/packages/twenty-ui/src/theme/constants/ThemeDark.ts
+++ b/packages/twenty-ui/src/theme/constants/ThemeDark.ts
@@ -1,3 +1,4 @@
+import { ADAPTIVE_COLORS_DARK } from '@ui/theme/constants/AdaptiveColorsDark';
import { BLUR_DARK } from '@ui/theme/constants/BlurDark';
import { ILLUSTRATION_ICON_DARK } from '@ui/theme/constants/IllustrationIconDark';
import { SNACK_BAR_DARK, ThemeType } from '..';
@@ -24,5 +25,6 @@ export const THEME_DARK: ThemeType = {
tag: TAG_DARK,
code: CODE_DARK,
IllustrationIcon: ILLUSTRATION_ICON_DARK,
+ adaptiveColors: ADAPTIVE_COLORS_DARK,
},
};
diff --git a/packages/twenty-ui/src/theme/constants/ThemeLight.ts b/packages/twenty-ui/src/theme/constants/ThemeLight.ts
index 781bfc635..51c361413 100644
--- a/packages/twenty-ui/src/theme/constants/ThemeLight.ts
+++ b/packages/twenty-ui/src/theme/constants/ThemeLight.ts
@@ -1,3 +1,4 @@
+import { ADAPTIVE_COLORS_LIGHT } from '@ui/theme/constants/AdaptiveColorsLight';
import { BLUR_LIGHT } from '@ui/theme/constants/BlurLight';
import { ILLUSTRATION_ICON_LIGHT } from '@ui/theme/constants/IllustrationIconLight';
import { SNACK_BAR_LIGHT } from '@ui/theme/constants/SnackBarLight';
@@ -24,5 +25,6 @@ export const THEME_LIGHT = {
tag: TAG_LIGHT,
code: CODE_LIGHT,
IllustrationIcon: ILLUSTRATION_ICON_LIGHT,
+ adaptiveColors: ADAPTIVE_COLORS_LIGHT,
},
};
diff --git a/packages/twenty-ui/src/theme/index.ts b/packages/twenty-ui/src/theme/index.ts
index a1e090271..0aa0af364 100644
--- a/packages/twenty-ui/src/theme/index.ts
+++ b/packages/twenty-ui/src/theme/index.ts
@@ -1,5 +1,7 @@
export * from './constants/AccentDark';
export * from './constants/AccentLight';
+export * from './constants/AdaptiveColorsDark';
+export * from './constants/AdaptiveColorsLight';
export * from './constants/Animation';
export * from './constants/BackgroundDark';
export * from './constants/BackgroundLight';