Display + edge icon on hover (#12635)

This commit is contained in:
martmull
2025-06-17 10:17:58 +02:00
committed by GitHub
parent 15c703c01e
commit ccd16fb27f
5 changed files with 79 additions and 28 deletions

View File

@ -39,6 +39,7 @@ import React, {
import { isDefined } from 'twenty-shared/utils';
import { Tag, TagColor } from 'twenty-ui/components';
import { THEME_COMMON } from 'twenty-ui/theme';
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
const StyledResetReactflowStyles = styled.div`
height: 100%;
@ -133,6 +134,11 @@ export const WorkflowDiagramCanvasBase = ({
const workflowDiagram = useRecoilComponentValueV2(
workflowDiagramComponentState,
);
const setWorkflowInsertStepIds = useSetRecoilComponentStateV2(
workflowInsertStepIdsComponentState,
);
const [
workflowDiagramFlowInitializationStatus,
setWorkflowDiagramFlowInitializationStatus,
@ -174,6 +180,10 @@ export const WorkflowDiagramCanvasBase = ({
reactflow.setNodes((nodes) =>
nodes.map((node) => ({ ...node, selected: false })),
);
setWorkflowInsertStepIds({
parentStepId: undefined,
nextStepId: undefined,
});
});
const containerRef = useRef<HTMLDivElement>(null);

View File

@ -1,25 +1,36 @@
import { STEP_ICON_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
import styled from '@emotion/styled';
import { EdgeLabelRenderer } from '@xyflow/react';
import { isDefined } from 'twenty-shared/utils';
import { IconPlus } from 'twenty-ui/display';
import { IconButtonGroup } from 'twenty-ui/input';
const EDGE_OPTION_BUTTON_LEFT_MARGIN = 8;
import { useState } from 'react';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
const StyledIconButtonGroup = styled(IconButtonGroup)`
border: 1px solid ${({ theme }) => theme.border.color.strong};
pointer-events: all;
`;
const StyledContainer = styled.div<{
labelX?: number;
labelY?: number;
}>`
position: absolute;
transform: ${({ labelX, labelY }) =>
`translate(${labelX || 0}px, ${isDefined(labelY) ? labelY - STEP_ICON_WIDTH / 2 : 0}px) translateX(${EDGE_OPTION_BUTTON_LEFT_MARGIN}px)`};
transform: ${({ labelY }) => `translate(${21}px, ${(labelY || 0) - 14}px)`};
`;
const StyledHoverZone = styled.div`
position: absolute;
width: 48px;
height: 52px;
transform: translate(-13px, -16px);
background: transparent;
`;
const StyledWrapper = styled.div`
pointer-events: all;
position: relative;
`;
type WorkflowDiagramEdgeOptionsProps = {
@ -30,31 +41,47 @@ type WorkflowDiagramEdgeOptionsProps = {
};
export const WorkflowDiagramEdgeOptions = ({
labelX,
labelY,
parentStepId,
nextStepId,
}: WorkflowDiagramEdgeOptionsProps) => {
const [hovered, setHovered] = useState(false);
const { startNodeCreation } = useStartNodeCreation();
const workflowInsertStepIds = useRecoilComponentValueV2(
workflowInsertStepIdsComponentState,
);
const isSelected =
workflowInsertStepIds.parentStepId === parentStepId &&
workflowInsertStepIds.nextStepId === nextStepId;
return (
<EdgeLabelRenderer>
<StyledContainer
labelX={labelX}
labelY={labelY}
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
>
<StyledIconButtonGroup
className="nodrag nopan"
iconButtons={[
{
Icon: IconPlus,
onClick: () => {
startNodeCreation({ parentStepId, nextStepId });
},
},
]}
/>
<StyledWrapper
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<StyledHoverZone />
{(hovered || isSelected) && (
<StyledIconButtonGroup
className="nodrag nopan"
iconButtons={[
{
Icon: IconPlus,
onClick: () => {
startNodeCreation({ parentStepId, nextStepId });
},
},
]}
/>
)}
</StyledWrapper>
</StyledContainer>
</EdgeLabelRenderer>
);

View File

@ -11,6 +11,7 @@ export const useStartNodeCreation = () => {
const setWorkflowInsertStepIds = useSetRecoilComponentStateV2(
workflowInsertStepIdsComponentState,
);
const { openStepSelectInCommandMenu } = useWorkflowCommandMenu();
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(

View File

@ -1,4 +1,3 @@
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useGetUpdatableWorkflowVersion } from '@/workflow/hooks/useGetUpdatableWorkflowVersion';
import { workflowLastCreatedStepIdComponentState } from '@/workflow/states/workflowLastCreatedStepIdComponentState';
@ -11,6 +10,7 @@ import { useCreateWorkflowVersionStep } from '@/workflow/workflow-steps/hooks/us
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
import { isDefined } from 'twenty-shared/utils';
import { useState } from 'react';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
export const useCreateStep = ({
workflow,
@ -26,9 +26,8 @@ export const useCreateStep = ({
workflowLastCreatedStepIdComponentState,
);
const workflowInsertStepIds = useRecoilComponentValueV2(
workflowInsertStepIdsComponentState,
);
const [workflowInsertStepIds, setWorkflowInsertStepIds] =
useRecoilComponentStateV2(workflowInsertStepIdsComponentState);
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();
@ -63,9 +62,10 @@ export const useCreateStep = ({
setWorkflowSelectedNode(createdStep.id);
setWorkflowLastCreatedStepId(createdStep.id);
} catch (error) {
setIsLoading(false);
throw error;
setWorkflowInsertStepIds({
parentStepId: undefined,
nextStepId: undefined,
});
} finally {
setIsLoading(false);
}

View File

@ -31,6 +31,15 @@ const StyledButton = styled.button`
}
`;
const StyledAnimatedIconWrapper = styled.span`
display: flex;
transition: transform 0.1s ease;
&:hover {
transform: translateY(-3%);
}
`;
export const InsideButton = ({
className,
Icon,
@ -41,7 +50,11 @@ export const InsideButton = ({
return (
<StyledButton className={className} onClick={onClick} disabled={disabled}>
{Icon && <Icon size={theme.icon.size.sm} />}
{Icon && (
<StyledAnimatedIconWrapper>
<Icon size={theme.icon.size.sm} />
</StyledAnimatedIconWrapper>
)}
</StyledButton>
);
};