Keep workflow nodes selected unless side panel is closed (#16029)

This PR no longer relies on Reactflow's `selected` property to determine
whether a node is selected. Instead, we rely on the pre-existing
`workflowSelectedNodeComponentState` state. It makes it easier to to
synchronize what's displayed in the side panel and which node is styled
as being selected.

## Demo - workflows


https://github.com/user-attachments/assets/3848c5f9-df51-48e2-8b9c-f083d25d14ed

## Demo - workflow versions


https://github.com/user-attachments/assets/9ac9ec6c-e6b1-4f24-a817-a7349e1b70d6

## Demo - workflow runs


https://github.com/user-attachments/assets/54f955cf-dacc-43f8-b684-01e8a6c15f3b
This commit is contained in:
Baptiste Devessier
2025-11-24 18:02:13 +01:00
committed by GitHub
parent 86aff36035
commit ee761a2594
14 changed files with 195 additions and 111 deletions
@@ -7,6 +7,7 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { WorkflowDiagramRightClickCommandMenu } from '@/workflow/workflow-diagram/components/WorkflowDiagramRightClickCommandMenu';
import { useResetWorkflowInsertStepIds } from '@/workflow/workflow-diagram/hooks/useResetWorkflowInsertStepIds';
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
import { workflowDiagramPanOnDragComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramPanOnDragComponentState';
import { workflowDiagramWaitingNodesDimensionsComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramWaitingNodesDimensionsComponentState';
@@ -24,7 +25,6 @@ import { WorkflowDiagramConnection } from '@/workflow/workflow-diagram/workflow-
import { WorkflowDiagramCustomMarkers } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramCustomMarkers';
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
import { type WorkflowDiagramEdgeComponentProps } from '@/workflow/workflow-diagram/workflow-edges/types/WorkflowDiagramEdgeComponentProps';
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import {
@@ -170,12 +170,10 @@ export const WorkflowDiagramCanvasBase = ({
const setWorkflowDiagram = useSetRecoilComponentState(
workflowDiagramComponentState,
);
const setWorkflowInsertStepIds = useSetRecoilComponentState(
workflowInsertStepIdsComponentState,
);
const setWorkflowSelectedNode = useSetRecoilComponentState(
workflowSelectedNodeComponentState,
);
const { resetWorkflowInsertStepIds } = useResetWorkflowInsertStepIds();
const workflowDiagramWaitingNodesDimensionsState =
useRecoilComponentCallbackState(
workflowDiagramWaitingNodesDimensionsComponentState,
@@ -217,17 +215,7 @@ export const WorkflowDiagramCanvasBase = ({
};
useListenToSidePanelClosing(() => {
reactflow.setNodes((nodes) =>
nodes.map((node) => ({ ...node, selected: false })),
);
reactflow.setEdges((edges) =>
edges.map((edge) => ({ ...edge, selected: false })),
);
setWorkflowInsertStepIds({
parentStepId: undefined,
nextStepId: undefined,
position: undefined,
});
resetWorkflowInsertStepIds();
setWorkflowSelectedNode(undefined);
});
@@ -1,6 +1,7 @@
import { type Meta, type StoryObj } from '@storybook/react';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import '@xyflow/react/dist/style.css';
import { RecoilRoot } from 'recoil';
import { ComponentDecorator } from 'twenty-ui/testing';
@@ -18,6 +19,9 @@ export default meta;
type Story = StoryObj<typeof WorkflowDiagramEmptyTriggerEditable>;
export const Default: Story = {
args: {
id: 'trigger-node',
},
decorators: [
(Story) => (
<div style={{ position: 'relative' }}>
@@ -36,10 +40,22 @@ export const Default: Story = {
};
export const Selected: Story = {
args: {
id: 'trigger-node',
},
decorators: [
(Story) => (
<div className="selectable selected" style={{ position: 'relative' }}>
<RecoilRoot>
<div style={{ position: 'relative' }}>
<RecoilRoot
initializeState={({ set }) => {
set(
workflowSelectedNodeComponentState.atomFamily({
instanceId: 'workflow-visualizer-instance-id',
}),
'trigger-node',
);
}}
>
<WorkflowVisualizerComponentInstanceContext.Provider
value={{ instanceId: 'workflow-visualizer-instance-id' }}
>
@@ -1,6 +1,7 @@
import { type Meta, type StoryObj } from '@storybook/react';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { type WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import '@xyflow/react/dist/style.css';
import { RecoilRoot } from 'recoil';
@@ -149,8 +150,19 @@ export const Catalog: CatalogStory<
decorators: [
(Story, { args }) => {
return (
<div className={`selectable ${args.selected ? 'selected' : ''}`}>
<RecoilRoot>
<div>
<RecoilRoot
initializeState={({ set }) => {
if (args.selected) {
set(
workflowSelectedNodeComponentState.atomFamily({
instanceId: 'workflow-visualizer-instance-id',
}),
args.id,
);
}
}}
>
<WorkflowVisualizerComponentInstanceContext.Provider
value={{ instanceId: 'workflow-visualizer-instance-id' }}
>
@@ -0,0 +1,19 @@
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
import { useRecoilCallback } from 'recoil';
export const useResetWorkflowInsertStepIds = () => {
const workflowInsertStepIdsState = useRecoilComponentCallbackState(
workflowInsertStepIdsComponentState,
);
const resetWorkflowInsertStepIds = useRecoilCallback(
({ reset }) =>
() => {
reset(workflowInsertStepIdsState);
},
[workflowInsertStepIdsState],
);
return { resetWorkflowInsertStepIds };
};
@@ -3,7 +3,9 @@ import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandM
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { type WorkflowStepConnectionOptions } from '@/workflow/workflow-diagram/workflow-iterator/types/WorkflowStepConnectionOptions';
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
import { useCallback, useContext } from 'react';
@@ -16,6 +18,10 @@ export const useStartNodeCreation = () => {
const [workflowInsertStepIds, setWorkflowInsertStepIds] =
useRecoilComponentState(workflowInsertStepIdsComponentState);
const setWorkflowSelectedNode = useSetRecoilComponentState(
workflowSelectedNodeComponentState,
);
const { openWorkflowCreateStepInCommandMenu } = useWorkflowCommandMenu();
const workflowVisualizerWorkflowId = useRecoilComponentValue(
@@ -49,6 +55,8 @@ export const useStartNodeCreation = () => {
connectionOptions,
});
setWorkflowSelectedNode(undefined);
if (!isDefined(workflowVisualizerWorkflowId)) {
return;
}
@@ -61,6 +69,7 @@ export const useStartNodeCreation = () => {
},
[
setWorkflowInsertStepIds,
setWorkflowSelectedNode,
workflowVisualizerWorkflowId,
isInRightDrawer,
openWorkflowCreateStepInCommandMenu,
@@ -1,9 +1,12 @@
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramStepNodeClickOutsideId';
import { useResetWorkflowInsertStepIds } from '@/workflow/workflow-diagram/hooks/useResetWorkflowInsertStepIds';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { WorkflowNodeContainer } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeContainer';
import { WorkflowNodeIconContainer } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeIconContainer';
import { WorkflowNodeLabel } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowNodeLabel';
@@ -15,7 +18,7 @@ import { useContext } from 'react';
import { useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
export const WorkflowDiagramEmptyTriggerEditable = () => {
export const WorkflowDiagramEmptyTriggerEditable = ({ id }: { id: string }) => {
const { t } = useLingui();
const { openWorkflowTriggerTypeInCommandMenu } = useWorkflowCommandMenu();
@@ -24,17 +27,28 @@ export const WorkflowDiagramEmptyTriggerEditable = () => {
workflowVisualizerWorkflowIdComponentState,
);
const [workflowSelectedNode, setWorkflowSelectedNode] =
useRecoilComponentState(workflowSelectedNodeComponentState);
const { isInRightDrawer } = useContext(ActionMenuContext);
const setCommandMenuNavigationStack = useSetRecoilState(
commandMenuNavigationStackState,
);
const selected = workflowSelectedNode === id;
const { resetWorkflowInsertStepIds } = useResetWorkflowInsertStepIds();
const handleClick = () => {
if (!isInRightDrawer) {
setCommandMenuNavigationStack([]);
}
resetWorkflowInsertStepIds();
setWorkflowSelectedNode(id);
if (!isDefined(workflowVisualizerWorkflowId)) {
return;
}
@@ -46,15 +60,20 @@ export const WorkflowDiagramEmptyTriggerEditable = () => {
<WorkflowNodeContainer
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
onClick={handleClick}
selected={selected}
>
<WorkflowNodeIconContainer />
<WorkflowNodeRightPart>
<WorkflowNodeLabelWithCounterPart>
<WorkflowNodeLabel>{t`Trigger`}</WorkflowNodeLabel>
<WorkflowNodeLabel selected={selected}>
{t`Trigger`}
</WorkflowNodeLabel>
</WorkflowNodeLabelWithCounterPart>
<WorkflowNodeTitle>{t`Add a Trigger`}</WorkflowNodeTitle>
<WorkflowNodeTitle selected={selected}>
{t`Add a Trigger`}
</WorkflowNodeTitle>
</WorkflowNodeRightPart>
</WorkflowNodeContainer>
);
@@ -1,8 +1,8 @@
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowVisualizerWorkflowVersionIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowVersionIdComponentState';
import { WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramStepNodeClickOutsideId';
@@ -20,7 +20,7 @@ import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { useIcons } from 'twenty-ui/display';
export const WorkflowDiagramEmptyTriggerReadonly = () => {
export const WorkflowDiagramEmptyTriggerReadonly = ({ id }: { id: string }) => {
const { getIcon } = useIcons();
const { t } = useLingui();
@@ -31,18 +31,19 @@ export const WorkflowDiagramEmptyTriggerReadonly = () => {
workflowVisualizerWorkflowVersionIdComponentState,
);
const [workflowSelectedNode, setWorkflowSelectedNode] =
useRecoilComponentState(workflowSelectedNodeComponentState);
const { isInRightDrawer } = useContext(ActionMenuContext);
const { openWorkflowViewStepInCommandMenu } = useWorkflowCommandMenu();
const setWorkflowSelectedNode = useSetRecoilComponentState(
workflowSelectedNodeComponentState,
);
const setCommandMenuNavigationStack = useSetRecoilState(
commandMenuNavigationStackState,
);
const selected = workflowSelectedNode === id;
const handleClick = () => {
if (
!isDefined(workflowVisualizerWorkflowId) ||
@@ -72,15 +73,20 @@ export const WorkflowDiagramEmptyTriggerReadonly = () => {
<WorkflowNodeContainer
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
onClick={handleClick}
selected={selected}
>
<WorkflowNodeIconContainer />
<WorkflowNodeRightPart>
<WorkflowNodeLabelWithCounterPart>
<WorkflowNodeLabel>{t`Trigger`}</WorkflowNodeLabel>
<WorkflowNodeLabel selected={selected}>
{t`Trigger`}
</WorkflowNodeLabel>
</WorkflowNodeLabelWithCounterPart>
<WorkflowNodeTitle>{t`Add a Trigger`}</WorkflowNodeTitle>
<WorkflowNodeTitle selected={selected}>
{t`Add a Trigger`}
</WorkflowNodeTitle>
</WorkflowNodeRightPart>
</WorkflowNodeContainer>
);
@@ -1,9 +1,10 @@
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { useResetWorkflowInsertStepIds } from '@/workflow/workflow-diagram/hooks/useResetWorkflowInsertStepIds';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { type WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
@@ -16,11 +17,9 @@ import { useIcons } from 'twenty-ui/display';
export const WorkflowDiagramStepNodeEditable = ({
id,
data,
selected,
}: {
id: string;
data: WorkflowDiagramStepNodeData;
selected?: boolean;
}) => {
const { getIcon } = useIcons();
@@ -28,41 +27,48 @@ export const WorkflowDiagramStepNodeEditable = ({
workflowVisualizerWorkflowIdComponentState,
);
const setWorkflowSelectedNode = useSetRecoilComponentState(
workflowSelectedNodeComponentState,
);
const [workflowSelectedNode, setWorkflowSelectedNode] =
useRecoilComponentState(workflowSelectedNodeComponentState);
const selected = workflowSelectedNode === id;
const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu();
const { resetWorkflowInsertStepIds } = useResetWorkflowInsertStepIds();
const { isInRightDrawer } = useContext(ActionMenuContext);
const setCommandMenuNavigationStack = useSetRecoilState(
commandMenuNavigationStackState,
);
const handleClick = () => {
if (!isInRightDrawer) {
setCommandMenuNavigationStack([]);
}
resetWorkflowInsertStepIds();
setWorkflowSelectedNode(id);
if (isDefined(workflowVisualizerWorkflowId)) {
openWorkflowEditStepInCommandMenu(
workflowVisualizerWorkflowId,
data.name,
getIcon(getWorkflowNodeIconKey(data)),
id,
);
return;
}
};
return (
<WorkflowDiagramStepNodeEditableContent
id={id}
data={data}
selected={selected ?? false}
onClick={() => {
if (!isInRightDrawer) {
setCommandMenuNavigationStack([]);
}
setWorkflowSelectedNode(id);
if (isDefined(workflowVisualizerWorkflowId)) {
openWorkflowEditStepInCommandMenu(
workflowVisualizerWorkflowId,
data.name,
getIcon(getWorkflowNodeIconKey(data)),
id,
);
return;
}
}}
selected={selected}
onClick={handleClick}
/>
);
};
@@ -87,6 +87,7 @@ export const WorkflowDiagramStepNodeEditableContent = ({
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
isConnectable={isNodeConnectable}
selected={selected}
>
<WorkflowDiagramHandleTarget isConnectable={isNodeConnectable} />
@@ -96,10 +97,15 @@ export const WorkflowDiagramStepNodeEditableContent = ({
<WorkflowNodeRightPart>
<WorkflowNodeLabelWithCounterPart>
<WorkflowNodeLabel>{capitalize(data.nodeType)}</WorkflowNodeLabel>
<WorkflowNodeLabel selected={selected}>
{capitalize(data.nodeType)}
</WorkflowNodeLabel>
</WorkflowNodeLabelWithCounterPart>
<WorkflowNodeTitle highlight={nodeTitleHighlighted}>
<WorkflowNodeTitle
highlight={nodeTitleHighlighted}
selected={selected}
>
{data.name}
</WorkflowNodeTitle>
</WorkflowNodeRightPart>
@@ -1,8 +1,8 @@
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { workflowVisualizerWorkflowVersionIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowVersionIdComponentState';
import { WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramStepNodeClickOutsideId';
@@ -28,11 +28,9 @@ import { useIcons } from 'twenty-ui/display';
export const WorkflowDiagramStepNodeReadonly = ({
id,
selected,
data,
}: {
id: string;
selected: boolean;
data: WorkflowDiagramStepNodeData;
}) => {
const { getIcon } = useIcons();
@@ -44,9 +42,10 @@ export const WorkflowDiagramStepNodeReadonly = ({
workflowVisualizerWorkflowVersionIdComponentState,
);
const setWorkflowSelectedNode = useSetRecoilComponentState(
workflowSelectedNodeComponentState,
);
const [workflowSelectedNode, setWorkflowSelectedNode] =
useRecoilComponentState(workflowSelectedNodeComponentState);
const selected = workflowSelectedNode === id;
const { openWorkflowViewStepInCommandMenu } = useWorkflowCommandMenu();
@@ -89,6 +88,7 @@ export const WorkflowDiagramStepNodeReadonly = ({
<WorkflowNodeContainer
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
onClick={handleClick}
selected={selected}
>
<WorkflowDiagramHandleTarget />
<WorkflowNodeIconContainer>
@@ -97,10 +97,15 @@ export const WorkflowDiagramStepNodeReadonly = ({
<WorkflowNodeRightPart>
<WorkflowNodeLabelWithCounterPart>
<WorkflowNodeLabel>{capitalize(data.nodeType)}</WorkflowNodeLabel>
<WorkflowNodeLabel selected={selected}>
{capitalize(data.nodeType)}
</WorkflowNodeLabel>
</WorkflowNodeLabelWithCounterPart>
<WorkflowNodeTitle highlight={nodeTitleHighlighted}>
<WorkflowNodeTitle
highlight={nodeTitleHighlighted}
selected={selected}
>
{data.name}
</WorkflowNodeTitle>
</WorkflowNodeRightPart>
@@ -6,6 +6,7 @@ import styled from '@emotion/styled';
const StyledNodeContainer = styled.div<{
runStatus?: WorkflowRunStepStatus;
isConnectable?: boolean;
selected: boolean;
}>`
align-items: center;
display: flex;
@@ -21,30 +22,31 @@ const StyledNodeContainer = styled.div<{
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 }) => {
${({ theme, runStatus, selected, isConnectable }) => {
const colors = getWorkflowDiagramColors({ theme, runStatus });
return css`
border-color: ${colors.unselected.borderColor};
background: ${colors.unselected.background};
const background = selected
? colors.selected.background
: colors.unselected.background;
.selected & {
background-color: ${colors.selected.background};
border-color: ${colors.selected.borderColor};
return css`
background: ${background};
border-color: ${selected
? colors.selected.borderColor
: colors.unselected.borderColor};
&:hover {
background: linear-gradient(
0deg,
${theme.background.transparent.lighter} 0%,
${theme.background.transparent.lighter} 100%
),
${background};
${isConnectable &&
css`
border-color: ${theme.color.blue} !important;
`};
}
`;
}}
@@ -1,24 +1,21 @@
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { css } from '@emotion/react';
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)<{
runStatus?: WorkflowRunStepStatus;
selected: boolean;
}>`
box-sizing: border-box;
flex: 1 0 0;
${({ theme, runStatus }) => {
${({ theme, runStatus, selected }) => {
const colors = getWorkflowDiagramColors({ theme, runStatus });
return css`
color: ${colors.unselected.color};
.selected & {
color: ${colors.selected.color};
}
color: ${selected ? colors.selected.color : colors.unselected.color};
`;
}}
`;
@@ -1,11 +1,12 @@
import styled from '@emotion/styled';
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { css } from '@emotion/react';
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import styled from '@emotion/styled';
const StyledNodeTitle = styled.div<{
highlight?: boolean;
runStatus?: WorkflowRunStepStatus;
selected: boolean;
}>`
box-sizing: border-box;
-webkit-box-orient: vertical;
@@ -17,7 +18,7 @@ const StyledNodeTitle = styled.div<{
overflow: hidden;
text-overflow: ellipsis;
${({ theme, highlight, runStatus }) => {
${({ theme, highlight, runStatus, selected }) => {
const colors = getWorkflowDiagramColors({ theme, runStatus });
if (true === highlight) {
@@ -27,11 +28,9 @@ const StyledNodeTitle = styled.div<{
}
return css`
color: ${colors.unselected.titleColor};
.selected & {
color: ${colors.selected.titleColor};
}
color: ${selected
? colors.selected.titleColor
: colors.unselected.titleColor};
`;
}}
`;
@@ -1,8 +1,8 @@
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
@@ -80,11 +80,9 @@ const StyledRightPartContainer = styled.div`
export const WorkflowRunDiagramStepNode = ({
id,
data,
selected,
}: {
id: string;
data: WorkflowRunDiagramStepNodeData;
selected: boolean;
}) => {
const { getIcon } = useIcons();
const theme = useTheme();
@@ -94,9 +92,10 @@ export const WorkflowRunDiagramStepNode = ({
);
const workflowRunId = useWorkflowRunIdOrThrow();
const setWorkflowSelectedNode = useSetRecoilComponentState(
workflowSelectedNodeComponentState,
);
const [workflowSelectedNode, setWorkflowSelectedNode] =
useRecoilComponentState(workflowSelectedNodeComponentState);
const selected = workflowSelectedNode === id;
const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu();
@@ -142,6 +141,7 @@ export const WorkflowRunDiagramStepNode = ({
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
runStatus={data.runStatus}
onClick={handleClick}
selected={selected}
>
<WorkflowDiagramHandleTarget />
<WorkflowNodeIconContainer>
@@ -150,7 +150,7 @@ export const WorkflowRunDiagramStepNode = ({
<WorkflowNodeRightPart>
<StyledNodeLabelWithCounterPart>
<WorkflowNodeLabel runStatus={data.runStatus}>
<WorkflowNodeLabel runStatus={data.runStatus} selected={selected}>
{capitalize(data.nodeType)}
</WorkflowNodeLabel>
@@ -187,7 +187,7 @@ export const WorkflowRunDiagramStepNode = ({
</StyledRightPartContainer>
</StyledNodeLabelWithCounterPart>
<WorkflowNodeTitle runStatus={data.runStatus}>
<WorkflowNodeTitle runStatus={data.runStatus} selected={selected}>
{data.name}
</WorkflowNodeTitle>
</WorkflowNodeRightPart>