From 607305db6d0cff3b627cf9c13ab13d4f3e1d503f Mon Sep 17 00:00:00 2001 From: Noam Neeman Date: Fri, 5 May 2023 08:12:45 +0300 Subject: [PATCH 1/4] feat(NodeToolbar): add align prop --- packages/node-toolbar/src/NodeToolbar.tsx | 49 +++++++++++++++++++++-- packages/node-toolbar/src/types.ts | 7 ++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/node-toolbar/src/NodeToolbar.tsx b/packages/node-toolbar/src/NodeToolbar.tsx index 428708da..d6394acf 100644 --- a/packages/node-toolbar/src/NodeToolbar.tsx +++ b/packages/node-toolbar/src/NodeToolbar.tsx @@ -14,7 +14,7 @@ import cc from 'classcat'; import { shallow } from 'zustand/shallow'; import NodeToolbarPortal from './NodeToolbarPortal'; -import { NodeToolbarProps } from './types'; +import { Align, NodeToolbarProps } from './types'; const nodeEqualityFn = (a: Node | undefined, b: Node | undefined) => a?.positionAbsolute?.x === b?.positionAbsolute?.x && @@ -34,12 +34,22 @@ const storeSelector = (state: ReactFlowState) => ({ 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 { // position === Position.Top let xPos = (nodeRect.x + nodeRect.width / 2) * transform[2] + transform[0]; let yPos = nodeRect.y * transform[2] + transform[1] - offset; let xShift = -50; let yShift = -100; + switch (align) { + case Align.Start: + xPos = nodeRect.x * transform[2] + transform[0]; + xShift = 0; + break; + case Align.End: + xPos = (nodeRect.x + nodeRect.width) * transform[2] + transform[0]; + xShift = -100; + break; + } switch (position) { case Position.Right: @@ -47,16 +57,48 @@ function getTransform(nodeRect: Rect, transform: Transform, position: Position, yPos = (nodeRect.y + nodeRect.height / 2) * transform[2] + transform[1]; xShift = 0; yShift = -50; + + switch (align) { + case Align.Start: + yPos = nodeRect.y * transform[2] + transform[1]; + yShift = 0; + break; + case Align.End: + yPos = (nodeRect.y + nodeRect.height) * transform[2] + transform[1]; + yShift = -100; + break; + } break; case Position.Bottom: yPos = (nodeRect.y + nodeRect.height) * transform[2] + transform[1] + offset; yShift = 0; + switch (align) { + case Align.Start: + xPos = nodeRect.x * transform[2] + transform[0]; + xShift = 0; + break; + case Align.End: + xPos = (nodeRect.x + nodeRect.width) * transform[2] + transform[0]; + xShift = -100; + break; + } break; case Position.Left: xPos = nodeRect.x * transform[2] + transform[0] - offset; yPos = (nodeRect.y + nodeRect.height / 2) * transform[2] + transform[1]; xShift = -100; yShift = -50; + + switch (align) { + case Align.Start: + yPos = nodeRect.y * transform[2] + transform[1]; + yShift = 0; + break; + case Align.End: + yPos = (nodeRect.y + nodeRect.height) * transform[2] + transform[1]; + yShift = -100; + break; + } break; } @@ -71,6 +113,7 @@ function NodeToolbar({ isVisible, position = Position.Top, offset = 10, + align = Align.Center, ...rest }: NodeToolbarProps) { const contextNodeId = useNodeId(); @@ -103,7 +146,7 @@ function NodeToolbar({ const wrapperStyle: CSSProperties = { position: 'absolute', - transform: getTransform(nodeRect, transform, position, offset), + transform: getTransform(nodeRect, transform, position, offset, align), zIndex, ...style, }; diff --git a/packages/node-toolbar/src/types.ts b/packages/node-toolbar/src/types.ts index e2b0d73c..33f82762 100644 --- a/packages/node-toolbar/src/types.ts +++ b/packages/node-toolbar/src/types.ts @@ -6,4 +6,11 @@ export type NodeToolbarProps = HTMLAttributes & { isVisible?: boolean; position?: Position; offset?: number; + align?: Align; }; + +export enum Align { + Center = 'center', + Start = 'start', + End = 'end', +} From 921512eec4b7f319a58b555719a3e9c2a9024a4d Mon Sep 17 00:00:00 2001 From: Noam Neeman Date: Fri, 5 May 2023 11:28:55 +0300 Subject: [PATCH 2/4] more --- .../src/examples/NodeToolbar/CustomNode.tsx | 2 +- .../src/examples/NodeToolbar/index.tsx | 2 +- packages/node-toolbar/src/NodeToolbar.tsx | 57 +++++++------------ packages/node-toolbar/src/types.ts | 6 +- 4 files changed, 25 insertions(+), 42 deletions(-) diff --git a/examples/vite-app/src/examples/NodeToolbar/CustomNode.tsx b/examples/vite-app/src/examples/NodeToolbar/CustomNode.tsx index ca2ac0b7..7d179b7e 100644 --- a/examples/vite-app/src/examples/NodeToolbar/CustomNode.tsx +++ b/examples/vite-app/src/examples/NodeToolbar/CustomNode.tsx @@ -4,7 +4,7 @@ import { Handle, Position, NodeProps, NodeToolbar } from 'reactflow'; const CustomNode: FC = ({ id, data }) => { return ( <> - + diff --git a/examples/vite-app/src/examples/NodeToolbar/index.tsx b/examples/vite-app/src/examples/NodeToolbar/index.tsx index 601d8618..70d8e36e 100644 --- a/examples/vite-app/src/examples/NodeToolbar/index.tsx +++ b/examples/vite-app/src/examples/NodeToolbar/index.tsx @@ -35,7 +35,7 @@ const initialNodes: Node[] = [ { id: '3', type: 'custom', - data: { label: 'toolbar bottom', toolbarPosition: Position.Bottom }, + data: { label: 'toolbar bottom', toolbarPosition: Position.Bottom, toolbarAlign: 'end' }, position: { x: 400, y: 100 }, className: 'react-flow__node-default', }, diff --git a/packages/node-toolbar/src/NodeToolbar.tsx b/packages/node-toolbar/src/NodeToolbar.tsx index d6394acf..86ea993c 100644 --- a/packages/node-toolbar/src/NodeToolbar.tsx +++ b/packages/node-toolbar/src/NodeToolbar.tsx @@ -40,16 +40,6 @@ function getTransform(nodeRect: Rect, transform: Transform, position: Position, let yPos = nodeRect.y * transform[2] + transform[1] - offset; let xShift = -50; let yShift = -100; - switch (align) { - case Align.Start: - xPos = nodeRect.x * transform[2] + transform[0]; - xShift = 0; - break; - case Align.End: - xPos = (nodeRect.x + nodeRect.width) * transform[2] + transform[0]; - xShift = -100; - break; - } switch (position) { case Position.Right: @@ -57,50 +47,47 @@ function getTransform(nodeRect: Rect, transform: Transform, position: Position, yPos = (nodeRect.y + nodeRect.height / 2) * transform[2] + transform[1]; xShift = 0; yShift = -50; - - switch (align) { - case Align.Start: - yPos = nodeRect.y * transform[2] + transform[1]; - yShift = 0; - break; - case Align.End: - yPos = (nodeRect.y + nodeRect.height) * transform[2] + transform[1]; - yShift = -100; - break; - } break; case Position.Bottom: yPos = (nodeRect.y + nodeRect.height) * transform[2] + transform[1] + offset; yShift = 0; - switch (align) { - case Align.Start: - xPos = nodeRect.x * transform[2] + transform[0]; - xShift = 0; - break; - case Align.End: - xPos = (nodeRect.x + nodeRect.width) * transform[2] + transform[0]; - xShift = -100; - break; - } break; case Position.Left: xPos = nodeRect.x * transform[2] + transform[0] - offset; yPos = (nodeRect.y + nodeRect.height / 2) * transform[2] + transform[1]; xShift = -100; yShift = -50; + break; + } + switch (position) { + case Position.Right: + case Position.Left: switch (align) { - case Align.Start: + case 'start': yPos = nodeRect.y * transform[2] + transform[1]; yShift = 0; break; - case Align.End: + case 'end': yPos = (nodeRect.y + nodeRect.height) * transform[2] + transform[1]; yShift = -100; break; } break; - } + case Position.Top: + case Position.Bottom: + switch (align) { + case 'start': + xPos = nodeRect.x * transform[2] + transform[0]; + xShift = 0; + break; + case 'end': + xPos = (nodeRect.x + nodeRect.width) * transform[2] + transform[0]; + xShift = -100; + break; + } + break +} return `translate(${xPos}px, ${yPos}px) translate(${xShift}%, ${yShift}%)`; } @@ -113,7 +100,7 @@ function NodeToolbar({ isVisible, position = Position.Top, offset = 10, - align = Align.Center, + align = 'center', ...rest }: NodeToolbarProps) { const contextNodeId = useNodeId(); diff --git a/packages/node-toolbar/src/types.ts b/packages/node-toolbar/src/types.ts index 33f82762..7ff2087b 100644 --- a/packages/node-toolbar/src/types.ts +++ b/packages/node-toolbar/src/types.ts @@ -9,8 +9,4 @@ export type NodeToolbarProps = HTMLAttributes & { align?: Align; }; -export enum Align { - Center = 'center', - Start = 'start', - End = 'end', -} +export type Align = 'center' | 'start' | 'end'; From 8386c7af6ce90c46f9c5da81946592978ec50dfc Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 15 May 2023 10:56:21 +0200 Subject: [PATCH 3/4] refactor(node-toolbar): align prop --- .../src/examples/NodeToolbar/index.tsx | 59 +++++++-------- packages/node-toolbar/src/NodeToolbar.tsx | 72 ++++++++----------- 2 files changed, 52 insertions(+), 79 deletions(-) diff --git a/examples/vite-app/src/examples/NodeToolbar/index.tsx b/examples/vite-app/src/examples/NodeToolbar/index.tsx index 70d8e36e..b4d3d1c5 100644 --- a/examples/vite-app/src/examples/NodeToolbar/index.tsx +++ b/examples/vite-app/src/examples/NodeToolbar/index.tsx @@ -17,49 +17,38 @@ const nodeTypes: NodeTypes = { custom: CustomNode, }; +const positions = ['top', 'right', 'bottom', 'left']; +const alignments = ['start', 'center', 'end']; + 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, toolbarAlign: 'end' }, - position: { x: 400, y: 100 }, - className: 'react-flow__node-default', - }, { id: '4', type: 'custom', - data: { label: 'toolbar left', toolbarPosition: Position.Left }, - position: { x: 400, 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 }, + data: { label: 'toolbar top', toolbarPosition: Position.Top }, + position: { x: 0, y: -200 }, className: 'react-flow__node-default', }, ]; -const initialEdges: Edge[] = [ - { id: 'e1-2', source: '1', target: '2' }, - { id: 'e1-3', source: '1', target: '3' }, - { id: 'e1-4', source: '1', target: '4' }, -]; +positions.forEach((position, posIndex) => { + alignments.forEach((align, alignIndex) => { + const id = `node-${align}-${position}`; + 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 nodeOrigin: NodeOrigin = [0.5, 0.5]; diff --git a/packages/node-toolbar/src/NodeToolbar.tsx b/packages/node-toolbar/src/NodeToolbar.tsx index 86ea993c..3b02b8bf 100644 --- a/packages/node-toolbar/src/NodeToolbar.tsx +++ b/packages/node-toolbar/src/NodeToolbar.tsx @@ -35,61 +35,45 @@ const storeSelector = (state: ReactFlowState) => ({ }); 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 - let xPos = (nodeRect.x + nodeRect.width / 2) * transform[2] + transform[0]; - let yPos = nodeRect.y * transform[2] + transform[1] - offset; - let xShift = -50; - let yShift = -100; + // we set the x any y position of the toolbar based on the nodes position + let pos = [ + (nodeRect.x + nodeRect.width * alignmentOffset) * transform[2] + transform[0], + 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) { case Position.Right: - xPos = (nodeRect.x + nodeRect.width) * transform[2] + transform[0] + offset; - yPos = (nodeRect.y + nodeRect.height / 2) * transform[2] + transform[1]; - xShift = 0; - yShift = -50; + pos = [ + (nodeRect.x + nodeRect.width) * transform[2] + transform[0] + offset, + (nodeRect.y + nodeRect.height * alignmentOffset) * transform[2] + transform[1], + ]; + shift = [0, -100 * alignmentOffset]; break; case Position.Bottom: - yPos = (nodeRect.y + nodeRect.height) * transform[2] + transform[1] + offset; - yShift = 0; + pos[1] = (nodeRect.y + nodeRect.height) * transform[2] + transform[1] + offset; + shift[1] = 0; break; case Position.Left: - xPos = nodeRect.x * transform[2] + transform[0] - offset; - yPos = (nodeRect.y + nodeRect.height / 2) * transform[2] + transform[1]; - xShift = -100; - yShift = -50; + pos = [ + nodeRect.x * transform[2] + transform[0] - offset, + (nodeRect.y + nodeRect.height * alignmentOffset) * transform[2] + transform[1], + ]; + shift = [-100, -100 * alignmentOffset]; break; } - switch (position) { - case Position.Right: - case Position.Left: - switch (align) { - case 'start': - yPos = nodeRect.y * transform[2] + transform[1]; - yShift = 0; - break; - case 'end': - yPos = (nodeRect.y + nodeRect.height) * transform[2] + transform[1]; - yShift = -100; - break; - } - break; - case Position.Top: - case Position.Bottom: - switch (align) { - case 'start': - xPos = nodeRect.x * transform[2] + transform[0]; - xShift = 0; - break; - case 'end': - xPos = (nodeRect.x + nodeRect.width) * transform[2] + transform[0]; - xShift = -100; - 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({ From 55e05cf76ae21863691153e76dbd51d1eecd2c60 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 15 May 2023 10:58:00 +0200 Subject: [PATCH 4/4] chore(changeset): add --- .changeset/large-seals-reflect.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/large-seals-reflect.md diff --git a/.changeset/large-seals-reflect.md b/.changeset/large-seals-reflect.md new file mode 100644 index 00000000..12a7eff8 --- /dev/null +++ b/.changeset/large-seals-reflect.md @@ -0,0 +1,5 @@ +--- +'@reactflow/node-toolbar': minor +--- + +feat(align): add prop to align bar at start, center or end