Merge pull request #3052 from wbkd/feat/aligned-node-toolbar
Feat/aligned node toolbar
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@reactflow/node-toolbar': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(align): add prop to align bar at start, center or end
|
||||||
@@ -4,7 +4,7 @@ import { Handle, Position, NodeProps, NodeToolbar } from 'reactflow';
|
|||||||
const CustomNode: FC<NodeProps> = ({ id, data }) => {
|
const CustomNode: FC<NodeProps> = ({ id, data }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NodeToolbar isVisible={data.toolbarVisible} position={data.toolbarPosition}>
|
<NodeToolbar isVisible={data.toolbarVisible} position={data.toolbarPosition} align={data.toolbarAlign}>
|
||||||
<button>delete</button>
|
<button>delete</button>
|
||||||
<button>copy</button>
|
<button>copy</button>
|
||||||
<button>expand</button>
|
<button>expand</button>
|
||||||
|
|||||||
@@ -17,49 +17,38 @@ const nodeTypes: NodeTypes = {
|
|||||||
custom: CustomNode,
|
custom: CustomNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const positions = ['top', 'right', 'bottom', 'left'];
|
||||||
|
const alignments = ['start', 'center', 'end'];
|
||||||
|
|
||||||
const initialNodes: Node[] = [
|
const initialNodes: Node[] = [
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
type: 'custom',
|
|
||||||
data: { label: 'toolbar top', toolbarPosition: Position.Top },
|
|
||||||
position: { x: 0, y: 50 },
|
|
||||||
className: 'react-flow__node-default',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
type: 'custom',
|
|
||||||
data: { label: 'toolbar right', toolbarPosition: Position.Right },
|
|
||||||
position: { x: 300, y: 0 },
|
|
||||||
className: 'react-flow__node-default',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
type: 'custom',
|
|
||||||
data: { label: 'toolbar bottom', toolbarPosition: Position.Bottom },
|
|
||||||
position: { x: 400, y: 100 },
|
|
||||||
className: 'react-flow__node-default',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: '4',
|
id: '4',
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
data: { label: 'toolbar left', toolbarPosition: Position.Left },
|
data: { label: 'toolbar top', toolbarPosition: Position.Top },
|
||||||
position: { x: 400, y: 200 },
|
position: { x: 0, y: -200 },
|
||||||
className: 'react-flow__node-default',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '5',
|
|
||||||
type: 'custom',
|
|
||||||
data: { label: 'toolbar always open', toolbarPosition: Position.Top, toolbarVisible: true },
|
|
||||||
position: { x: 0, y: 200 },
|
|
||||||
className: 'react-flow__node-default',
|
className: 'react-flow__node-default',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const initialEdges: Edge[] = [
|
positions.forEach((position, posIndex) => {
|
||||||
{ id: 'e1-2', source: '1', target: '2' },
|
alignments.forEach((align, alignIndex) => {
|
||||||
{ id: 'e1-3', source: '1', target: '3' },
|
const id = `node-${align}-${position}`;
|
||||||
{ id: 'e1-4', source: '1', target: '4' },
|
initialNodes.push({
|
||||||
];
|
id,
|
||||||
|
type: 'custom',
|
||||||
|
data: {
|
||||||
|
label: `toolbar ${position} ${align}`,
|
||||||
|
toolbarPosition: position as Position,
|
||||||
|
toolbarAlign: align,
|
||||||
|
toolbarVisible: true,
|
||||||
|
},
|
||||||
|
className: 'react-flow__node-default',
|
||||||
|
position: { x: posIndex * 300, y: alignIndex * 100 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialEdges: Edge[] = [];
|
||||||
|
|
||||||
const defaultEdgeOptions = { zIndex: 0 };
|
const defaultEdgeOptions = { zIndex: 0 };
|
||||||
const nodeOrigin: NodeOrigin = [0.5, 0.5];
|
const nodeOrigin: NodeOrigin = [0.5, 0.5];
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import cc from 'classcat';
|
|||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
|
|
||||||
import NodeToolbarPortal from './NodeToolbarPortal';
|
import NodeToolbarPortal from './NodeToolbarPortal';
|
||||||
import { NodeToolbarProps } from './types';
|
import { Align, NodeToolbarProps } from './types';
|
||||||
|
|
||||||
const nodeEqualityFn = (a: Node | undefined, b: Node | undefined) =>
|
const nodeEqualityFn = (a: Node | undefined, b: Node | undefined) =>
|
||||||
a?.positionAbsolute?.x === b?.positionAbsolute?.x &&
|
a?.positionAbsolute?.x === b?.positionAbsolute?.x &&
|
||||||
@@ -34,33 +34,46 @@ const storeSelector = (state: ReactFlowState) => ({
|
|||||||
selectedNodesCount: state.getNodes().filter((node) => node.selected).length,
|
selectedNodesCount: state.getNodes().filter((node) => node.selected).length,
|
||||||
});
|
});
|
||||||
|
|
||||||
function getTransform(nodeRect: Rect, transform: Transform, position: Position, offset: number): string {
|
function getTransform(nodeRect: Rect, transform: Transform, position: Position, offset: number, align: Align): string {
|
||||||
|
let alignmentOffset = 0.5;
|
||||||
|
|
||||||
|
if (align === 'start') {
|
||||||
|
alignmentOffset = 0;
|
||||||
|
} else if (align === 'end') {
|
||||||
|
alignmentOffset = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// position === Position.Top
|
// position === Position.Top
|
||||||
let xPos = (nodeRect.x + nodeRect.width / 2) * transform[2] + transform[0];
|
// we set the x any y position of the toolbar based on the nodes position
|
||||||
let yPos = nodeRect.y * transform[2] + transform[1] - offset;
|
let pos = [
|
||||||
let xShift = -50;
|
(nodeRect.x + nodeRect.width * alignmentOffset) * transform[2] + transform[0],
|
||||||
let yShift = -100;
|
nodeRect.y * transform[2] + transform[1] - offset,
|
||||||
|
];
|
||||||
|
// and than shift it based on the alignment. The shift values are in %.
|
||||||
|
let shift = [-100 * alignmentOffset, -100];
|
||||||
|
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case Position.Right:
|
case Position.Right:
|
||||||
xPos = (nodeRect.x + nodeRect.width) * transform[2] + transform[0] + offset;
|
pos = [
|
||||||
yPos = (nodeRect.y + nodeRect.height / 2) * transform[2] + transform[1];
|
(nodeRect.x + nodeRect.width) * transform[2] + transform[0] + offset,
|
||||||
xShift = 0;
|
(nodeRect.y + nodeRect.height * alignmentOffset) * transform[2] + transform[1],
|
||||||
yShift = -50;
|
];
|
||||||
|
shift = [0, -100 * alignmentOffset];
|
||||||
break;
|
break;
|
||||||
case Position.Bottom:
|
case Position.Bottom:
|
||||||
yPos = (nodeRect.y + nodeRect.height) * transform[2] + transform[1] + offset;
|
pos[1] = (nodeRect.y + nodeRect.height) * transform[2] + transform[1] + offset;
|
||||||
yShift = 0;
|
shift[1] = 0;
|
||||||
break;
|
break;
|
||||||
case Position.Left:
|
case Position.Left:
|
||||||
xPos = nodeRect.x * transform[2] + transform[0] - offset;
|
pos = [
|
||||||
yPos = (nodeRect.y + nodeRect.height / 2) * transform[2] + transform[1];
|
nodeRect.x * transform[2] + transform[0] - offset,
|
||||||
xShift = -100;
|
(nodeRect.y + nodeRect.height * alignmentOffset) * transform[2] + transform[1],
|
||||||
yShift = -50;
|
];
|
||||||
|
shift = [-100, -100 * alignmentOffset];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `translate(${xPos}px, ${yPos}px) translate(${xShift}%, ${yShift}%)`;
|
return `translate(${pos[0]}px, ${pos[1]}px) translate(${shift[0]}%, ${shift[1]}%)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function NodeToolbar({
|
function NodeToolbar({
|
||||||
@@ -71,6 +84,7 @@ function NodeToolbar({
|
|||||||
isVisible,
|
isVisible,
|
||||||
position = Position.Top,
|
position = Position.Top,
|
||||||
offset = 10,
|
offset = 10,
|
||||||
|
align = 'center',
|
||||||
...rest
|
...rest
|
||||||
}: NodeToolbarProps) {
|
}: NodeToolbarProps) {
|
||||||
const contextNodeId = useNodeId();
|
const contextNodeId = useNodeId();
|
||||||
@@ -103,7 +117,7 @@ function NodeToolbar({
|
|||||||
|
|
||||||
const wrapperStyle: CSSProperties = {
|
const wrapperStyle: CSSProperties = {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
transform: getTransform(nodeRect, transform, position, offset),
|
transform: getTransform(nodeRect, transform, position, offset, align),
|
||||||
zIndex,
|
zIndex,
|
||||||
...style,
|
...style,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,4 +6,7 @@ export type NodeToolbarProps = HTMLAttributes<HTMLDivElement> & {
|
|||||||
isVisible?: boolean;
|
isVisible?: boolean;
|
||||||
position?: Position;
|
position?: Position;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
|
align?: Align;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Align = 'center' | 'start' | 'end';
|
||||||
|
|||||||
Reference in New Issue
Block a user