Fix handle color on workflow run steps (#14091)

## Before

<img width="2878" height="1316" alt="image"
src="https://github.com/user-attachments/assets/0e1238b7-b602-4283-ae55-dccd51aa4d90"
/>


## After
<img width="1322" height="495" alt="image"
src="https://github.com/user-attachments/assets/d1f3c912-ebf4-494f-b606-21d11b3e0058"
/>
This commit is contained in:
martmull
2025-08-27 10:22:13 +02:00
committed by GitHub
parent 56895d785a
commit a6fb4dd589
21 changed files with 258 additions and 389 deletions
@@ -2,7 +2,6 @@ import { type Meta, type StoryObj } from '@storybook/react';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import { type WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { type WorkflowDiagramNodeVariant } from '@/workflow/workflow-diagram/types/WorkflowDiagramNodeVariant';
import { fn } from '@storybook/test';
import '@xyflow/react/dist/style.css';
import { RecoilRoot } from 'recoil';
@@ -121,7 +120,6 @@ export const Catalog: CatalogStory<
args: {
id: 'story-node',
data: ALL_STEPS[0],
variant: 'default',
selected: false,
onDelete: fn(),
},
@@ -140,18 +138,6 @@ export const Catalog: CatalogStory<
values: ALL_STEPS,
props: (data: WorkflowDiagramStepNodeData) => ({ data }),
},
{
name: 'variant',
values: [
'empty',
'default',
'running',
'success',
'failure',
'not-executed',
] satisfies WorkflowDiagramNodeVariant[],
props: (variant: WorkflowDiagramNodeVariant) => ({ variant }),
},
{
name: 'selected',
values: [false, true],
@@ -1,4 +0,0 @@
export type WorkflowDiagramNodeHandles = {
targetHandle: boolean;
sourceHandle: boolean;
};
@@ -1,7 +0,0 @@
export type WorkflowDiagramNodeVariant =
| 'default'
| 'success'
| 'failure'
| 'running'
| 'empty'
| 'not-executed';
@@ -1,16 +0,0 @@
import { getNodeVariantFromStepRunStatus } from '@/workflow/workflow-diagram/utils/getNodeVariantFromStepRunStatus';
import { StepStatus } from 'twenty-shared/workflow';
describe('getNodeVariantFromRunStatus', () => {
it('should return proper variant', () => {
expect(getNodeVariantFromStepRunStatus(StepStatus.SUCCESS)).toBe('success');
expect(getNodeVariantFromStepRunStatus(StepStatus.STOPPED)).toBe('success');
expect(getNodeVariantFromStepRunStatus(StepStatus.FAILED)).toBe('failure');
expect(getNodeVariantFromStepRunStatus(StepStatus.RUNNING)).toBe('running');
expect(getNodeVariantFromStepRunStatus(StepStatus.PENDING)).toBe('running');
expect(getNodeVariantFromStepRunStatus(StepStatus.NOT_STARTED)).toBe(
'not-executed',
);
expect(getNodeVariantFromStepRunStatus(undefined)).toBe('default');
});
});
@@ -1,21 +0,0 @@
import { type WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import { type WorkflowDiagramNodeVariant } from '@/workflow/workflow-diagram/types/WorkflowDiagramNodeVariant';
export const getNodeVariantFromStepRunStatus = (
runStatus: WorkflowRunStepStatus | undefined,
): WorkflowDiagramNodeVariant => {
switch (runStatus) {
case 'SUCCESS':
case 'STOPPED':
return 'success';
case 'FAILED':
return 'failure';
case 'RUNNING':
case 'PENDING':
return 'running';
case 'NOT_STARTED':
return 'not-executed';
default:
return 'default';
}
};
@@ -0,0 +1,90 @@
import { type Theme } from '@emotion/react';
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
type WorkflowDiagramColors = {
background: string;
borderColor: string;
color: string;
titleColor: string;
};
export type WorkflowDiagramNodeColors = {
selected: WorkflowDiagramColors;
unselected: WorkflowDiagramColors;
};
export const getWorkflowDiagramColors = ({
theme,
runStatus,
}: {
theme: Theme;
runStatus?: WorkflowRunStepStatus;
}): WorkflowDiagramNodeColors => {
switch (runStatus) {
case 'PENDING':
case 'RUNNING': {
return {
selected: {
background: theme.adaptiveColors.yellow1,
borderColor: theme.color.yellow,
color: theme.tag.text.yellow,
titleColor: theme.font.color.primary,
},
unselected: {
background: theme.background.secondary,
borderColor: theme.border.color.strong,
color: theme.tag.text.yellow,
titleColor: theme.font.color.primary,
},
};
}
case 'FAILED': {
return {
selected: {
background: theme.adaptiveColors.red1,
borderColor: theme.color.red,
color: theme.tag.text.red,
titleColor: theme.font.color.primary,
},
unselected: {
background: theme.background.secondary,
borderColor: theme.border.color.strong,
color: theme.tag.text.red,
titleColor: theme.font.color.primary,
},
};
}
case 'SUCCESS': {
return {
selected: {
background: theme.adaptiveColors.turquoise1,
borderColor: theme.color.turquoise,
color: theme.tag.text.green,
titleColor: theme.font.color.primary,
},
unselected: {
background: theme.background.secondary,
borderColor: theme.border.color.strong,
color: theme.tag.text.green,
titleColor: theme.font.color.primary,
},
};
}
default: {
return {
selected: {
background: theme.adaptiveColors.blue1,
borderColor: theme.color.blue,
color: theme.tag.text.blue,
titleColor: theme.font.color.primary,
},
unselected: {
background: theme.background.secondary,
borderColor: theme.border.color.strong,
color: theme.font.color.tertiary,
titleColor: theme.font.color.light,
},
};
}
}
};
@@ -1,39 +0,0 @@
import { type WorkflowDiagramNodeVariant } from '@/workflow/workflow-diagram/types/WorkflowDiagramNodeVariant';
import { type Theme } from '@emotion/react';
export type WorkflowDiagramNodeSelectedColors = {
background: string;
borderColor: string;
};
export const getWorkflowDiagramNodeSelectedColors = (
variant: WorkflowDiagramNodeVariant,
theme: Theme,
): WorkflowDiagramNodeSelectedColors => {
switch (variant) {
case 'running': {
return {
background: theme.adaptiveColors.yellow1,
borderColor: theme.adaptiveColors.yellow4,
};
}
case 'success': {
return {
background: theme.adaptiveColors.turquoise1,
borderColor: theme.adaptiveColors.turquoise4,
};
}
case 'failure': {
return {
background: theme.background.danger,
borderColor: theme.color.red,
};
}
default: {
return {
background: theme.adaptiveColors.blue1,
borderColor: theme.color.blue,
};
}
}
};
@@ -1,6 +1,6 @@
import styled from '@emotion/styled';
import { IconButtonGroup, type IconButtonGroupProps } from 'twenty-ui/input';
import { getWorkflowDiagramNodeSelectedColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramNodeSelectedColors';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { css } from '@emotion/react';
const StyledIconButtonGroup = styled(IconButtonGroup)<{ selected?: boolean }>`
@@ -8,10 +8,10 @@ const StyledIconButtonGroup = styled(IconButtonGroup)<{ selected?: boolean }>`
${({ selected, theme }) => {
if (!selected) return '';
const colors = getWorkflowDiagramNodeSelectedColors('default', theme);
const colors = getWorkflowDiagramColors({ theme });
return css`
background-color: ${colors.background};
border: 1px solid ${colors.borderColor};
background-color: ${colors.selected.background};
border: 1px solid ${colors.selected.borderColor};
`;
}}
`;
@@ -23,7 +23,7 @@ import {
type WorkflowDiagramEdge,
type WorkflowDiagramEdgeData,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getWorkflowDiagramNodeSelectedColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramNodeSelectedColors';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { useDeleteEdge } from '@/workflow/workflow-steps/hooks/useDeleteEdge';
import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
@@ -66,10 +66,10 @@ const StyledIconButtonGroup = styled(IconButtonGroup)<{ selected?: boolean }>`
${({ selected, theme }) => {
if (!selected) return '';
const colors = getWorkflowDiagramNodeSelectedColors('default', theme);
const colors = getWorkflowDiagramColors({ theme });
return css`
background-color: ${colors.background};
border: 1px solid ${colors.borderColor};
background-color: ${colors.selected.background};
border: 1px solid ${colors.selected.borderColor};
`;
}}
`;
@@ -10,7 +10,7 @@ import {
type WorkflowDiagramEdge,
type WorkflowDiagramEdgeData,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getWorkflowDiagramNodeSelectedColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramNodeSelectedColors';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
@@ -42,10 +42,10 @@ const StyledIconButtonGroup = styled(IconButtonGroup)<{ selected?: boolean }>`
${({ selected, theme }) => {
if (!selected) return '';
const colors = getWorkflowDiagramNodeSelectedColors('default', theme);
const colors = getWorkflowDiagramColors({ theme });
return css`
background-color: ${colors.background};
border: 1px solid ${colors.borderColor};
background-color: ${colors.selected.background};
border: 1px solid ${colors.selected.borderColor};
`;
}}
`;
@@ -9,9 +9,7 @@ import {
type WorkflowDiagramEdge,
type WorkflowDiagramEdgeData,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { type WorkflowDiagramNodeVariant } from '@/workflow/workflow-diagram/types/WorkflowDiagramNodeVariant';
import { getNodeVariantFromStepRunStatus } from '@/workflow/workflow-diagram/utils/getNodeVariantFromStepRunStatus';
import { getWorkflowDiagramNodeSelectedColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramNodeSelectedColors';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
@@ -23,6 +21,7 @@ import {
import { isDefined } from 'twenty-shared/utils';
import { IconFilter } from 'twenty-ui/display';
import { IconButtonGroup } from 'twenty-ui/input';
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
type WorkflowDiagramFilterEdgeRunProps = EdgeProps<WorkflowDiagramEdge>;
@@ -38,16 +37,16 @@ const assertFilterEdgeDataOrThrow: (
const StyledIconButtonGroup = styled(IconButtonGroup)<{
selected?: boolean;
variant: WorkflowDiagramNodeVariant;
runStatus?: WorkflowRunStepStatus;
}>`
pointer-events: all;
${({ selected, variant, theme }) => {
${({ selected, runStatus, theme }) => {
if (!selected) return '';
const colors = getWorkflowDiagramNodeSelectedColors(variant, theme);
const colors = getWorkflowDiagramColors({ runStatus, theme });
return css`
background-color: ${colors.background};
border: 1px solid ${colors.borderColor};
background-color: ${colors.selected.background};
border: 1px solid ${colors.selected.borderColor};
`;
}}
`;
@@ -122,7 +121,7 @@ export const WorkflowDiagramFilterEdgeRun = ({
},
]}
selected={isFilterNodeSelected}
variant={getNodeVariantFromStepRunStatus(data.runStatus)}
runStatus={data.runStatus}
/>
</StyledConfiguredFilterContainer>
</WorkflowDiagramEdgeV2VisibilityContainer>
@@ -10,47 +10,11 @@ import { WorkflowNodeLabel } from '@/workflow/workflow-diagram/workflow-nodes/co
import { WorkflowNodeLabelWithCounterPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeLabelWithCounterPart';
import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeRightPart';
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useContext } from 'react';
import { useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
const StyledNodeContainer = styled(WorkflowNodeContainer)`
border-color: ${({ theme }) => theme.border.color.strong};
background: ${({ theme }) => theme.background.secondary};
&:hover {
background: linear-gradient(
0deg,
${({ theme }) => theme.background.transparent.lighter} 0%,
${({ theme }) => theme.background.transparent.lighter} 100%
),
${({ theme }) => theme.background.secondary};
}
.selected & {
border-color: ${({ theme }) => theme.color.blue};
background: ${({ theme }) => theme.adaptiveColors.blue1};
}
`;
const StyledNodeLabel = styled(WorkflowNodeLabel)`
color: ${({ theme }) => theme.font.color.tertiary};
.selected & {
color: ${({ theme }) => theme.tag.text.blue};
}
`;
const StyledNodeTitle = styled(WorkflowNodeTitle)`
color: ${({ theme }) => theme.font.color.light};
.selected & {
color: ${({ theme }) => theme.font.color.primary};
}
`;
export const WorkflowDiagramEmptyTriggerEditable = () => {
const { t } = useLingui();
@@ -79,7 +43,7 @@ export const WorkflowDiagramEmptyTriggerEditable = () => {
};
return (
<StyledNodeContainer
<WorkflowNodeContainer
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
onClick={handleClick}
>
@@ -87,11 +51,11 @@ export const WorkflowDiagramEmptyTriggerEditable = () => {
<WorkflowNodeRightPart>
<WorkflowNodeLabelWithCounterPart>
<StyledNodeLabel>{t`Trigger`}</StyledNodeLabel>
<WorkflowNodeLabel>{t`Trigger`}</WorkflowNodeLabel>
</WorkflowNodeLabelWithCounterPart>
<StyledNodeTitle>{t`Add a Trigger`}</StyledNodeTitle>
<WorkflowNodeTitle>{t`Add a Trigger`}</WorkflowNodeTitle>
</WorkflowNodeRightPart>
</StyledNodeContainer>
</WorkflowNodeContainer>
);
};
@@ -13,7 +13,6 @@ import { WorkflowNodeLabel } from '@/workflow/workflow-diagram/workflow-nodes/co
import { WorkflowNodeLabelWithCounterPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeLabelWithCounterPart';
import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeRightPart';
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useContext } from 'react';
import { useSetRecoilState } from 'recoil';
@@ -21,41 +20,6 @@ import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { useIcons } from 'twenty-ui/display';
const StyledNodeContainer = styled(WorkflowNodeContainer)`
border-color: ${({ theme }) => theme.border.color.strong};
background: ${({ theme }) => theme.background.secondary};
&:hover {
background: linear-gradient(
0deg,
${({ theme }) => theme.background.transparent.lighter} 0%,
${({ theme }) => theme.background.transparent.lighter} 100%
),
${({ theme }) => theme.background.secondary};
}
.selected & {
border-color: ${({ theme }) => theme.color.blue};
background: ${({ theme }) => theme.adaptiveColors.blue1};
}
`;
const StyledNodeLabel = styled(WorkflowNodeLabel)`
color: ${({ theme }) => theme.font.color.tertiary};
.selected & {
color: ${({ theme }) => theme.tag.text.blue};
}
`;
const StyledNodeTitle = styled(WorkflowNodeTitle)`
color: ${({ theme }) => theme.font.color.light};
.selected & {
color: ${({ theme }) => theme.font.color.primary};
}
`;
export const WorkflowDiagramEmptyTriggerReadonly = () => {
const { getIcon } = useIcons();
const { t } = useLingui();
@@ -104,7 +68,7 @@ export const WorkflowDiagramEmptyTriggerReadonly = () => {
};
return (
<StyledNodeContainer
<WorkflowNodeContainer
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
onClick={handleClick}
>
@@ -112,11 +76,11 @@ export const WorkflowDiagramEmptyTriggerReadonly = () => {
<WorkflowNodeRightPart>
<WorkflowNodeLabelWithCounterPart>
<StyledNodeLabel>{t`Trigger`}</StyledNodeLabel>
<WorkflowNodeLabel>{t`Trigger`}</WorkflowNodeLabel>
</WorkflowNodeLabelWithCounterPart>
<StyledNodeTitle>{t`Add a Trigger`}</StyledNodeTitle>
<WorkflowNodeTitle>{t`Add a Trigger`}</WorkflowNodeTitle>
</WorkflowNodeRightPart>
</StyledNodeContainer>
</WorkflowNodeContainer>
);
};
@@ -5,36 +5,53 @@ import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { Handle, Position, type HandleProps } from '@xyflow/react';
import { FeatureFlagKey } from '~/generated/graphql';
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
type WorkflowDiagramHandleSourceProps = {
selected: boolean;
hovered?: boolean;
readOnly?: boolean;
runStatus?: WorkflowRunStepStatus;
};
const HANDLE_SCALE_ON_HOVER = 1.5;
const StyledHandle = styled(Handle, {
shouldForwardProp: (prop) =>
prop !== 'disableHoverEffect' && prop !== 'selected' && prop !== 'hovered',
prop !== 'disableHoverEffect' &&
prop !== 'selected' &&
prop !== 'hovered' &&
prop !== 'runStatus',
})<{
type: HandleProps['type'];
disableHoverEffect: boolean;
selected: boolean;
hovered?: boolean;
runStatus?: WorkflowRunStepStatus;
}>`
&.react-flow__handle {
opacity: ${({ type }) => (type === 'target' ? 0 : 1)};
height: ${NODE_HANDLE_HEIGHT_PX}px;
width: ${NODE_HANDLE_WIDTH_PX}px;
border-color: ${({ theme, selected, hovered, disableHoverEffect }) =>
selected
? theme.color.blue
: hovered && !disableHoverEffect
? theme.font.color.light
: theme.border.color.strong};
background: ${({ theme, selected }) =>
selected ? theme.adaptiveColors.blue1 : theme.background.primary};
${({ theme, selected, hovered, disableHoverEffect, runStatus }) => {
if (!selected) {
return css`
background: ${theme.background.primary};
border-color: ${hovered && !disableHoverEffect
? theme.font.color.light
: theme.border.color.strong};
`;
}
const colors = getWorkflowDiagramColors({ theme, runStatus });
return css`
background: ${colors.selected.background};
border-color: ${colors.selected.borderColor};
`;
}}
transition:
transform 0.1s ease-out,
background 0.1s,
@@ -53,10 +70,12 @@ const StyledHandle = styled(Handle, {
return undefined;
}
const colors = getWorkflowDiagramColors({ theme });
return css`
&:hover {
background: ${theme.adaptiveColors.blue1} !important;
border-color: ${theme.color.blue} !important;
background: ${colors.selected.background} !important;
border-color: ${colors.selected.borderColor} !important;
transform: scale(${HANDLE_SCALE_ON_HOVER}) translate(-50%, 50%);
}
`;
@@ -68,6 +87,7 @@ export const WorkflowDiagramHandleSource = ({
selected,
hovered = false,
readOnly = false,
runStatus,
}: WorkflowDiagramHandleSourceProps) => {
const isWorkflowBranchEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
@@ -80,6 +100,7 @@ export const WorkflowDiagramHandleSource = ({
disableHoverEffect={!isWorkflowBranchEnabled || readOnly}
selected={selected}
hovered={hovered}
runStatus={runStatus}
/>
);
};
@@ -47,7 +47,6 @@ export const WorkflowDiagramStepNodeEditable = ({
<WorkflowDiagramStepNodeEditableContent
id={id}
data={data}
variant="default"
selected={selected ?? false}
onClick={() => {
if (!isInRightDrawer) {
@@ -3,7 +3,6 @@ import { WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID } from '@/workflow/wor
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
import { type WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { type WorkflowDiagramNodeVariant } from '@/workflow/workflow-diagram/types/WorkflowDiagramNodeVariant';
import { WorkflowDiagramStepNodeIcon } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon';
import { WorkflowNodeContainer } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeContainer';
import { WorkflowNodeIconContainer } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeIconContainer';
@@ -17,7 +16,6 @@ import { capitalize } from 'twenty-shared/utils';
import { IconTrash } from 'twenty-ui/display';
import { FloatingIconButton } from 'twenty-ui/input';
import { useConnectionState } from '@/workflow/workflow-diagram/workflow-nodes/hooks/useConnectionState';
import { css } from '@emotion/react';
import { WorkflowDiagramHandleTarget } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleTarget';
import { WorkflowDiagramHandleSource } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleSource';
@@ -35,44 +33,6 @@ const StyledAddStepButtonContainer = styled.div<{
transform: translateX(-50%) translateY(100%);
`;
const StyledNodeContainer = styled(WorkflowNodeContainer)<{
isConnectable?: boolean;
}>`
border-color: ${({ theme }) => theme.border.color.strong};
background: ${({ theme }) => theme.background.secondary};
&:hover {
background: linear-gradient(
0deg,
${({ theme }) => theme.background.transparent.lighter} 0%,
${({ theme }) => theme.background.transparent.lighter} 100%
),
${({ theme }) => theme.background.secondary};
${({ theme, isConnectable }) =>
isConnectable &&
css`
border-color: ${theme.color.blue} !important;
`};
}
.selected & {
border-color: ${({ theme }) => theme.color.blue};
background: ${({ theme }) => theme.adaptiveColors.blue1};
}
`;
const StyledNodeLabel = styled(WorkflowNodeLabel)`
color: ${({ theme }) => theme.font.color.tertiary};
.selected & {
color: ${({ theme }) => theme.tag.text.blue};
}
`;
const StyledNodeTitle = styled(WorkflowNodeTitle)`
color: ${({ theme }) => theme.font.color.primary};
`;
const StyledDeleteButtonContainer = styled.div`
display: flex;
align-items: center;
@@ -92,7 +52,6 @@ export const WorkflowDiagramStepNodeEditableContent = ({
}: {
id: string;
data: WorkflowDiagramStepNodeData;
variant: WorkflowDiagramNodeVariant;
selected: boolean;
onDelete: () => void;
onClick?: () => void;
@@ -116,7 +75,7 @@ export const WorkflowDiagramStepNodeEditableContent = ({
return (
<>
<StyledNodeContainer
<WorkflowNodeContainer
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
onClick={onClick}
onMouseEnter={handleMouseEnter}
@@ -130,10 +89,10 @@ export const WorkflowDiagramStepNodeEditableContent = ({
<WorkflowNodeRightPart>
<WorkflowNodeLabelWithCounterPart>
<StyledNodeLabel>{capitalize(data.nodeType)}</StyledNodeLabel>
<WorkflowNodeLabel>{capitalize(data.nodeType)}</WorkflowNodeLabel>
</WorkflowNodeLabelWithCounterPart>
<StyledNodeTitle>{data.name}</StyledNodeTitle>
<WorkflowNodeTitle highlight>{data.name}</WorkflowNodeTitle>
</WorkflowNodeRightPart>
{selected && (
@@ -145,7 +104,7 @@ export const WorkflowDiagramStepNodeEditableContent = ({
/>
</StyledDeleteButtonContainer>
)}
</StyledNodeContainer>
</WorkflowNodeContainer>
{!data.hasNextStepIds && !isInProgressConnection && (
<StyledAddStepButtonContainer
@@ -16,7 +16,6 @@ import { WorkflowNodeLabel } from '@/workflow/workflow-diagram/workflow-nodes/co
import { WorkflowNodeLabelWithCounterPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeLabelWithCounterPart';
import { WorkflowNodeRightPart } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeRightPart';
import { WorkflowNodeTitle } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeTitle';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { useSetRecoilState } from 'recoil';
import { capitalize, isDefined } from 'twenty-shared/utils';
@@ -24,37 +23,6 @@ import { useIcons } from 'twenty-ui/display';
import { WorkflowDiagramHandleTarget } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleTarget';
import { WorkflowDiagramHandleSource } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleSource';
const StyledNodeContainer = styled(WorkflowNodeContainer)`
border-color: ${({ theme }) => theme.border.color.strong};
background: ${({ theme }) => theme.background.secondary};
&:hover {
background: linear-gradient(
0deg,
${({ theme }) => theme.background.transparent.lighter} 0%,
${({ theme }) => theme.background.transparent.lighter} 100%
),
${({ theme }) => theme.background.secondary};
}
.selected & {
border-color: ${({ theme }) => theme.color.blue};
background: ${({ theme }) => theme.adaptiveColors.blue1};
}
`;
const StyledNodeLabel = styled(WorkflowNodeLabel)`
color: ${({ theme }) => theme.font.color.tertiary};
.selected & {
color: ${({ theme }) => theme.tag.text.blue};
}
`;
const StyledNodeTitle = styled(WorkflowNodeTitle)`
color: ${({ theme }) => theme.font.color.primary};
`;
export const WorkflowDiagramStepNodeReadonly = ({
id,
selected,
@@ -109,7 +77,7 @@ export const WorkflowDiagramStepNodeReadonly = ({
return (
<>
<StyledNodeContainer
<WorkflowNodeContainer
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
onClick={handleClick}
>
@@ -120,12 +88,12 @@ export const WorkflowDiagramStepNodeReadonly = ({
<WorkflowNodeRightPart>
<WorkflowNodeLabelWithCounterPart>
<StyledNodeLabel>{capitalize(data.nodeType)}</StyledNodeLabel>
<WorkflowNodeLabel>{capitalize(data.nodeType)}</WorkflowNodeLabel>
</WorkflowNodeLabelWithCounterPart>
<StyledNodeTitle>{data.name}</StyledNodeTitle>
<WorkflowNodeTitle highlight>{data.name}</WorkflowNodeTitle>
</WorkflowNodeRightPart>
</StyledNodeContainer>
</WorkflowNodeContainer>
<WorkflowDiagramHandleSource selected={selected} readOnly />
</>
@@ -1,6 +1,12 @@
import styled from '@emotion/styled';
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import { css } from '@emotion/react';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
const StyledNodeContainer = styled.div`
const StyledNodeContainer = styled.div<{
runStatus?: WorkflowRunStepStatus;
isConnectable?: boolean;
}>`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
@@ -14,6 +20,34 @@ const StyledNodeContainer = styled.div`
cursor: pointer;
position: relative;
transition: border-color 0.1s;
&:hover {
background: linear-gradient(
0deg,
${({ theme }) => theme.background.transparent.lighter} 0%,
${({ theme }) => theme.background.transparent.lighter} 100%
),
${({ theme }) => theme.background.secondary};
${({ theme, isConnectable }) =>
isConnectable &&
css`
border-color: ${theme.color.blue} !important;
`};
}
${({ theme, runStatus }) => {
const colors = getWorkflowDiagramColors({ theme, runStatus });
return css`
border-color: ${colors.unselected.borderColor};
background: ${colors.unselected.background};
.selected & {
background-color: ${colors.selected.background};
border: 1px solid ${colors.selected.borderColor};
}
`;
}}
`;
export { StyledNodeContainer as WorkflowNodeContainer };
@@ -1,9 +1,26 @@
import styled from '@emotion/styled';
import { Label } from 'twenty-ui/display';
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import { css } from '@emotion/react';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
const StyledNodeLabel = styled(Label)`
const StyledNodeLabel = styled(Label)<{
runStatus?: WorkflowRunStepStatus;
}>`
box-sizing: border-box;
flex: 1 0 0;
${({ theme, runStatus }) => {
const colors = getWorkflowDiagramColors({ theme, runStatus });
return css`
color: ${colors.unselected.color};
.selected & {
color: ${colors.selected.color};
}
`;
}}
`;
export { StyledNodeLabel as WorkflowNodeLabel };
@@ -1,6 +1,12 @@
import styled from '@emotion/styled';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { css } from '@emotion/react';
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
const StyledNodeTitle = styled.div`
const StyledNodeTitle = styled.div<{
highlight?: boolean;
runStatus?: WorkflowRunStepStatus;
}>`
box-sizing: border-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
@@ -10,6 +16,24 @@ const StyledNodeTitle = styled.div`
font-weight: ${({ theme }) => theme.font.weight.medium};
overflow: hidden;
text-overflow: ellipsis;
${({ theme, highlight, runStatus }) => {
const colors = getWorkflowDiagramColors({ theme, runStatus });
if (true === highlight) {
return css`
color: ${colors.selected.titleColor};
`;
}
return css`
color: ${colors.unselected.titleColor};
.selected & {
color: ${colors.selected.titleColor};
}
`;
}}
`;
export { StyledNodeTitle as WorkflowNodeTitle };
@@ -27,78 +27,10 @@ import { Loader } from 'twenty-ui/feedback';
import { WorkflowDiagramHandleTarget } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleTarget';
import { WorkflowDiagramHandleSource } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleSource';
const StyledNodeContainer = styled(WorkflowNodeContainer)`
border-color: ${({ theme }) => theme.border.color.strong};
background: ${({ theme }) => theme.background.secondary};
&:hover {
background: linear-gradient(
0deg,
${({ theme }) => theme.background.transparent.lighter} 0%,
${({ theme }) => theme.background.transparent.lighter} 100%
),
${({ theme }) => theme.background.secondary};
}
.selected & {
border-color: ${({ theme }) => theme.color.blue};
background: ${({ theme }) => theme.adaptiveColors.blue1};
}
.selected &[data-status='RUNNING'],
.selected &[data-status='PENDING'] {
border-color: ${({ theme }) => theme.color.yellow};
background: ${({ theme }) => theme.adaptiveColors.yellow1};
}
.selected &[data-status='FAILED'] {
border-color: ${({ theme }) => theme.color.red};
background: ${({ theme }) => theme.adaptiveColors.red1};
}
.selected &[data-status='SUCCESS'] {
border-color: ${({ theme }) => theme.color.turquoise};
background: ${({ theme }) => theme.adaptiveColors.turquoise1};
}
`;
const StyledNodeLabelWithCounterPart = styled(WorkflowNodeLabelWithCounterPart)`
column-gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledNodeLabel = styled(WorkflowNodeLabel)`
color: ${({ theme }) => theme.font.color.tertiary};
.selected & {
color: ${({ theme }) => theme.tag.text.blue};
}
&[data-status='RUNNING'],
&[data-status='PENDING'] {
color: ${({ theme }) => theme.tag.text.yellow};
}
&[data-status='FAILED'] {
color: ${({ theme }) => theme.tag.text.red};
}
&[data-status='SUCCESS'] {
color: ${({ theme }) => theme.tag.text.green};
}
`;
const StyledNodeTitle = styled(WorkflowNodeTitle)`
color: ${({ theme }) => theme.font.color.light};
&[data-status='RUNNING'],
&[data-status='PENDING'],
&[data-status='FAILED'],
&[data-status='SUCCESS'],
.selected & {
color: ${({ theme }) => theme.font.color.primary};
}
`;
const StyledNodeCounter = styled.div`
align-items: center;
display: flex;
@@ -107,22 +39,17 @@ const StyledNodeCounter = styled.div`
box-sizing: border-box;
`;
const StyledColorIcon = styled.div`
const StyledColorIcon = styled.div<{
color: string;
}>`
align-items: center;
border-radius: ${({ theme }) => theme.border.radius.sm};
box-sizing: border-box;
display: flex;
width: 14px;
height: 14px;
justify-content: center;
align-items: center;
box-sizing: border-box;
border-radius: ${({ theme }) => theme.border.radius.sm};
&[data-status='FAILED'] {
background: ${({ theme }) => theme.tag.background.red};
}
&[data-status='SUCCESS'] {
background: ${({ theme }) => theme.tag.background.turquoise};
}
width: 14px;
background: ${({ color }) => color};
`;
export const WorkflowRunDiagramStepNode = ({
@@ -177,9 +104,9 @@ export const WorkflowRunDiagramStepNode = ({
return (
<>
<StyledNodeContainer
<WorkflowNodeContainer
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
data-status={data.runStatus}
runStatus={data.runStatus}
onClick={handleClick}
>
<WorkflowDiagramHandleTarget />
@@ -189,13 +116,13 @@ export const WorkflowRunDiagramStepNode = ({
<WorkflowNodeRightPart>
<StyledNodeLabelWithCounterPart>
<StyledNodeLabel data-status={data.runStatus}>
<WorkflowNodeLabel runStatus={data.runStatus}>
{capitalize(data.nodeType)}
</StyledNodeLabel>
</WorkflowNodeLabel>
{data.runStatus === StepStatus.SUCCESS && (
<StyledNodeCounter>
<StyledColorIcon data-status={StepStatus.SUCCESS}>
<StyledColorIcon color={theme.tag.background.turquoise}>
<IconCheck color={theme.tag.text.turquoise} size={14} />
</StyledColorIcon>
</StyledNodeCounter>
@@ -203,7 +130,7 @@ export const WorkflowRunDiagramStepNode = ({
{data.runStatus === StepStatus.FAILED && (
<StyledNodeCounter>
<StyledColorIcon data-status={StepStatus.FAILED}>
<StyledColorIcon color={theme.tag.background.red}>
<IconX color={theme.tag.text.red} size={14} />
</StyledColorIcon>
</StyledNodeCounter>
@@ -217,13 +144,17 @@ export const WorkflowRunDiagramStepNode = ({
)}
</StyledNodeLabelWithCounterPart>
<StyledNodeTitle data-status={data.runStatus}>
<WorkflowNodeTitle runStatus={data.runStatus}>
{data.name}
</StyledNodeTitle>
</WorkflowNodeTitle>
</WorkflowNodeRightPart>
</StyledNodeContainer>
</WorkflowNodeContainer>
<WorkflowDiagramHandleSource selected={selected} readOnly />
<WorkflowDiagramHandleSource
runStatus={data.runStatus}
selected={selected}
readOnly
/>
</>
);
};