From bbb0c9a761f5f0635d3089ceb402eae06a3935d8 Mon Sep 17 00:00:00 2001 From: Baptiste Devessier Date: Thu, 23 Jan 2025 16:12:37 +0100 Subject: [PATCH] Improve the design of workflow nodes (#9810) - Go over every node in the workflows and fix the styles to conform to Figma - Create stories for every node type --- .../WorkflowDiagramBaseStepNode.tsx | 31 ++-- .../WorkflowDiagramCreateStepNode.tsx | 12 +- .../WorkflowDiagramEmptyTrigger.tsx | 5 +- .../WorkflowDiagramStepNodeBase.tsx | 14 +- .../WorkflowDiagramStepNodeEditable.tsx | 20 +-- ...WorkflowDiagramStepNodeEditableContent.tsx | 28 ++++ .../WorkflowDiagramCreateStepNode.stories.tsx | 42 +++++ .../WorkflowDiagramEmptyTrigger.stories.tsx | 43 +++++ ...DiagramStepNodeEditableContent.stories.tsx | 150 ++++++++++++++++++ .../constants/NodeBorderWidth.ts | 1 + .../constants/NodeHandleHeightPx.ts | 3 + .../constants/NodeHandleWidthPx.ts | 1 + .../constants/NodeIconLeftMargin.ts | 5 + .../constants/NodeIconWidth.ts | 5 + .../testing/decorators/CatalogDecorator.tsx | 6 +- 15 files changed, 329 insertions(+), 37 deletions(-) create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramCreateStepNode.stories.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramEmptyTrigger.stories.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramStepNodeEditableContent.stories.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeBorderWidth.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeHandleHeightPx.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeHandleWidthPx.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeIconLeftMargin.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeIconWidth.ts 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 e421d3ccd796..920d75a7e04b 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 @@ -1,3 +1,8 @@ +import { NODE_BORDER_WIDTH } from '@/workflow/workflow-diagram/constants/NodeBorderWidth'; +import { NODE_HANDLE_HEIGHT_PX } from '@/workflow/workflow-diagram/constants/NodeHandleHeightPx'; +import { NODE_HANDLE_WIDTH_PX } from '@/workflow/workflow-diagram/constants/NodeHandleWidthPx'; +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 styled from '@emotion/styled'; import { Handle, Position } from '@xyflow/react'; @@ -21,7 +26,7 @@ const StyledStepNodeType = styled.div` ${({ theme }) => theme.border.radius.sm} 0 0; color: ${({ theme }) => theme.font.color.light}; - font-size: ${({ theme }) => theme.font.size.md}; + font-size: 9px; font-weight: ${({ theme }) => theme.font.weight.semiBold}; margin-left: ${({ theme }) => theme.spacing(2)}; @@ -38,9 +43,8 @@ const StyledStepNodeType = styled.div` const StyledStepNodeInnerContainer = styled.div<{ variant?: Variant }>` background-color: ${({ theme }) => theme.background.secondary}; - border: 1px solid ${({ theme }) => theme.border.color.medium}; - border-style: ${({ variant }) => - variant === 'placeholder' ? 'dashed' : null}; + border: ${NODE_BORDER_WIDTH}px solid + ${({ theme }) => theme.border.color.medium}; border-radius: ${({ theme }) => theme.border.radius.md}; display: flex; gap: ${({ theme }) => theme.spacing(2)}; @@ -61,7 +65,7 @@ const StyledStepNodeInnerContainer = styled.div<{ variant?: Variant }>` const StyledStepNodeLabel = styled.div<{ variant?: Variant }>` align-items: center; display: flex; - font-size: ${({ theme }) => theme.font.size.lg}; + font-size: 13px; font-weight: ${({ theme }) => theme.font.weight.medium}; column-gap: ${({ theme }) => theme.spacing(2)}; color: ${({ variant, theme }) => @@ -71,16 +75,19 @@ const StyledStepNodeLabel = styled.div<{ variant?: Variant }>` max-width: 200px; `; -const StyledSourceHandle = styled(Handle)` +export const StyledHandle = styled(Handle)` background-color: ${({ theme }) => theme.grayScale.gray25}; border: none; - width: 4px; - height: 4px; - left: ${({ theme }) => theme.spacing(10)}; + width: ${NODE_HANDLE_WIDTH_PX}px; + height: ${NODE_HANDLE_HEIGHT_PX}px; `; -export const StyledTargetHandle = styled(Handle)` - left: ${({ theme }) => theme.spacing(10)}; +const StyledSourceHandle = styled(StyledHandle)` + left: ${NODE_ICON_WIDTH + NODE_ICON_LEFT_MARGIN + NODE_BORDER_WIDTH}px; +`; + +const StyledTargetHandle = styled(StyledSourceHandle)` + left: ${NODE_ICON_WIDTH + NODE_ICON_LEFT_MARGIN + NODE_BORDER_WIDTH}px; visibility: hidden; `; @@ -88,7 +95,7 @@ const StyledRightFloatingElementContainer = styled.div` display: flex; align-items: center; position: absolute; - right: ${({ theme }) => theme.spacing(-3)}; + right: ${({ theme }) => theme.spacing(-4)}; bottom: 0; top: 0; transform: translateX(100%); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCreateStepNode.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCreateStepNode.tsx index 493b00d3beb1..1b6b84365cca 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCreateStepNode.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCreateStepNode.tsx @@ -1,15 +1,19 @@ +import { StyledHandle } from '@/workflow/workflow-diagram/components/WorkflowDiagramBaseStepNode'; +import { NODE_BORDER_WIDTH } from '@/workflow/workflow-diagram/constants/NodeBorderWidth'; +import { NODE_ICON_LEFT_MARGIN } from '@/workflow/workflow-diagram/constants/NodeIconLeftMargin'; +import { NODE_ICON_WIDTH } from '@/workflow/workflow-diagram/constants/NodeIconWidth'; import styled from '@emotion/styled'; -import { Handle, Position } from '@xyflow/react'; +import { Position } from '@xyflow/react'; import { IconButton, IconPlus } from 'twenty-ui'; const StyledContainer = styled.div` - padding-left: ${({ theme }) => theme.spacing(6)}; padding-top: ${({ theme }) => theme.spacing(1)}; + transform: translateX(-50%); position: relative; + left: ${NODE_ICON_WIDTH + NODE_ICON_LEFT_MARGIN + NODE_BORDER_WIDTH}px; `; -export const StyledTargetHandle = styled(Handle)` - left: ${({ theme }) => theme.spacing(10)}; +const StyledTargetHandle = styled(StyledHandle)` visibility: hidden; `; 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 0f59db3dcdd0..c5efa221c3af 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 @@ -22,7 +22,10 @@ export const WorkflowDiagramEmptyTrigger = () => { variant="placeholder" Icon={ - + } /> 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 b754defb74ac..69230cc14129 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 @@ -33,7 +33,7 @@ export const WorkflowDiagramStepNodeBase = ({ return ( @@ -43,7 +43,7 @@ export const WorkflowDiagramStepNodeBase = ({ return ( @@ -58,14 +58,18 @@ export const WorkflowDiagramStepNodeBase = ({ case 'CODE': { return ( - + ); } case 'SEND_EMAIL': { return ( - + ); } @@ -75,7 +79,7 @@ export const WorkflowDiagramStepNodeBase = ({ return ( diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable.tsx index 845bb6217bf5..a3d2f5572987 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable.tsx @@ -1,11 +1,10 @@ import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { workflowIdState } from '@/workflow/states/workflowIdState'; import { assertWorkflowWithCurrentVersionIsDefined } from '@/workflow/utils/assertWorkflowWithCurrentVersionIsDefined'; -import { WorkflowDiagramStepNodeBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase'; +import { WorkflowDiagramStepNodeEditableContent } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent'; import { WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep'; import { useRecoilValue } from 'recoil'; -import { FloatingIconButton, IconTrash } from 'twenty-ui'; export const WorkflowDiagramStepNodeEditable = ({ id, @@ -26,19 +25,12 @@ export const WorkflowDiagramStepNodeEditable = ({ }); return ( - { - deleteStep(id); - }} - /> - ) : undefined - } + selected={selected ?? false} + onDelete={() => { + 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 new file mode 100644 index 000000000000..18398886367e --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditableContent.tsx @@ -0,0 +1,28 @@ +import { WorkflowDiagramStepNodeBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase'; +import { WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; +import { FloatingIconButton, IconTrash } from 'twenty-ui'; + +export const WorkflowDiagramStepNodeEditableContent = ({ + data, + selected, + onDelete, +}: { + data: WorkflowDiagramStepNodeData; + selected: boolean; + onDelete: () => void; +}) => { + return ( + + ) : undefined + } + /> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramCreateStepNode.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramCreateStepNode.stories.tsx new file mode 100644 index 000000000000..1c029d6ec91b --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramCreateStepNode.stories.tsx @@ -0,0 +1,42 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { ReactFlowProvider } from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; +import { ComponentDecorator } from 'twenty-ui'; +import { WorkflowDiagramCreateStepNode } from '../WorkflowDiagramCreateStepNode'; + +const meta: Meta = { + title: 'Modules/Workflow/WorkflowDiagramCreateStepNode', + component: WorkflowDiagramCreateStepNode, + decorators: [ + ComponentDecorator, + (Story) => ( + + + + ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export const Selected: Story = { + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramEmptyTrigger.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramEmptyTrigger.stories.tsx new file mode 100644 index 000000000000..e7b5aac82f3e --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramEmptyTrigger.stories.tsx @@ -0,0 +1,43 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { ComponentDecorator } from 'twenty-ui'; + +import { ReactFlowProvider } from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; +import { WorkflowDiagramEmptyTrigger } from '../WorkflowDiagramEmptyTrigger'; + +const meta: Meta = { + title: 'Modules/Workflow/WorkflowDiagramEmptyTrigger', + component: WorkflowDiagramEmptyTrigger, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + decorators: [ + (Story) => ( + +
+ +
+
+ ), + ComponentDecorator, + ], +}; + +export const Selected: Story = { + decorators: [ + (Story) => ( +
+ +
+ ), + (Story) => ( + + + + ), + ComponentDecorator, + ], +}; 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 new file mode 100644 index 000000000000..45e979ea4475 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/__stories__/WorkflowDiagramStepNodeEditableContent.stories.tsx @@ -0,0 +1,150 @@ +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 { graphqlMocks } from '~/testing/graphqlMocks'; +import { WorkflowDiagramStepNodeEditableContent } from '../WorkflowDiagramStepNodeEditableContent'; + +const meta: Meta = { + title: 'Modules/Workflow/WorkflowDiagramStepNodeEditableContent', + component: WorkflowDiagramStepNodeEditableContent, +}; + +export default meta; + +type Story = StoryObj; + +export const All: CatalogStory< + Story, + typeof WorkflowDiagramStepNodeEditableContent +> = { + args: { + onDelete: fn(), + selected: false, + }, + parameters: { + msw: graphqlMocks, + catalog: { + options: { + elementContainer: { + width: 250, + style: { position: 'relative' }, + }, + }, + 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[], + props: (data: WorkflowDiagramStepNodeData) => ({ data }), + }, + ], + }, + }, + decorators: [ + CatalogDecorator, + (Story) => { + return ( + + + + ); + }, + ], +}; +export const AllSelected: CatalogStory< + Story, + typeof WorkflowDiagramStepNodeEditableContent +> = { + args: { + onDelete: fn(), + selected: true, + }, + parameters: { + msw: graphqlMocks, + catalog: { + options: { + elementContainer: { + width: 250, + style: { position: 'relative' }, + className: 'selectable selected', + }, + }, + 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[], + props: (data: WorkflowDiagramStepNodeData) => ({ data }), + }, + ], + }, + }, + decorators: [ + CatalogDecorator, + (Story) => { + return ( + + + + ); + }, + ], +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeBorderWidth.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeBorderWidth.ts new file mode 100644 index 000000000000..da353c77b018 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeBorderWidth.ts @@ -0,0 +1 @@ +export const NODE_BORDER_WIDTH = 1; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeHandleHeightPx.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeHandleHeightPx.ts new file mode 100644 index 000000000000..3a0b4349ec30 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeHandleHeightPx.ts @@ -0,0 +1,3 @@ +import { NODE_HANDLE_WIDTH_PX } from './NodeHandleWidthPx'; + +export const NODE_HANDLE_HEIGHT_PX = NODE_HANDLE_WIDTH_PX; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeHandleWidthPx.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeHandleWidthPx.ts new file mode 100644 index 000000000000..81ce92f81ad6 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeHandleWidthPx.ts @@ -0,0 +1 @@ +export const NODE_HANDLE_WIDTH_PX = 4; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeIconLeftMargin.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeIconLeftMargin.ts new file mode 100644 index 000000000000..4f719adfbfd7 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeIconLeftMargin.ts @@ -0,0 +1,5 @@ +import { THEME_COMMON } from 'twenty-ui'; + +export const NODE_ICON_LEFT_MARGIN = Number( + THEME_COMMON.spacing(2).replace('px', ''), +); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeIconWidth.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeIconWidth.ts new file mode 100644 index 000000000000..49e17655def3 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/NodeIconWidth.ts @@ -0,0 +1,5 @@ +import { THEME_COMMON } from 'twenty-ui'; + +export const NODE_ICON_WIDTH = Number( + THEME_COMMON.spacing(6).replace('px', ''), +); diff --git a/packages/twenty-ui/src/testing/decorators/CatalogDecorator.tsx b/packages/twenty-ui/src/testing/decorators/CatalogDecorator.tsx index ab43a7df139f..97920be14527 100644 --- a/packages/twenty-ui/src/testing/decorators/CatalogDecorator.tsx +++ b/packages/twenty-ui/src/testing/decorators/CatalogDecorator.tsx @@ -1,7 +1,7 @@ -import { ComponentProps, JSX } from 'react'; import styled from '@emotion/styled'; import { isNumber, isString } from '@sniptt/guards'; import { Decorator } from '@storybook/react'; +import { ComponentProps, JSX } from 'react'; const StyledColumnTitle = styled.h1` font-size: ${({ theme }) => theme.font.size.lg}; @@ -91,6 +91,8 @@ export type CatalogDimension< export type CatalogOptions = { elementContainer?: { width?: number; + style?: React.CSSProperties; + className?: string; }; }; @@ -135,6 +137,8 @@ export const CatalogDecorator: Decorator = (Story, context) => {