diff --git a/.changeset/flow-vertical-orientation.md b/.changeset/flow-vertical-orientation.md
new file mode 100644
index 000000000..6cd18f249
--- /dev/null
+++ b/.changeset/flow-vertical-orientation.md
@@ -0,0 +1,5 @@
+---
+"@cloudflare/kumo": minor
+---
+
+Add `orientation="vertical"` support to `Flow` for laying out nodes top-to-bottom. The `align` prop now controls cross-axis alignment in both orientations.
diff --git a/packages/kumo-docs-astro/src/components/demos/FlowDemo.tsx b/packages/kumo-docs-astro/src/components/demos/FlowDemo.tsx
index 0b0cee076..48dfb1fe5 100644
--- a/packages/kumo-docs-astro/src/components/demos/FlowDemo.tsx
+++ b/packages/kumo-docs-astro/src/components/demos/FlowDemo.tsx
@@ -43,6 +43,32 @@ export function FlowBasicDemo() {
);
}
+/** Vertical flow diagram with sequential nodes */
+export function FlowVerticalDemo() {
+ return (
+
+ Set `orientation="vertical"` to lay nodes out top-to-bottom instead of the + default left-to-right. Connectors, parallel branches, and `align` all adapt + to the vertical axis. +
++ Parallel branches work the same way in vertical flows. In this orientation + `align` controls horizontal alignment of nodes. +
+@@ -238,12 +259,22 @@ export default function Example() {
++ Vertical flow diagram with sequential nodes +
++ Vertical flow diagram with parallel branching +
+Flow diagram with parallel branching
diff --git a/packages/kumo/src/components/flow/diagram.tsx b/packages/kumo/src/components/flow/diagram.tsx index ae519d6b2..fe0f9de4a 100644 --- a/packages/kumo/src/components/flow/diagram.tsx +++ b/packages/kumo/src/components/flow/diagram.tsx @@ -40,7 +40,6 @@ function isEventFromNode(target: EventTarget | null): boolean { /** Minimum scrollbar thumb size in percentage to ensure visibility */ const MIN_SCROLLBAR_THUMB_SIZE = 10; -// Vertical orientation is currently a no-op type Orientation = "horizontal" | "vertical"; type Align = "start" | "center"; @@ -66,9 +65,11 @@ export function useDiagramContext(): DiagramContextValue { interface FlowDiagramProps { orientation?: Orientation; /** - * Controls vertical alignment of nodes in horizontal orientation. - * - `start`: Nodes align to the top (default) - * - `center`: Nodes are vertically centered + * Controls cross-axis alignment of nodes. + * In horizontal orientation this is vertical alignment; in vertical + * orientation it is horizontal alignment. + * - `start`: Nodes align to the start of the cross axis (default) + * - `center`: Nodes are centered on the cross axis */ align?: Align; /** @@ -428,11 +429,26 @@ export function FlowNodeList({ children }: { children: ReactNode }) { if (currentRect && nextRect) { const isDisabled = currentNode.props.disabled || nextNode.props.disabled; + // Horizontal flows left-to-right: connect the right edge of the + // current node to the left edge of the next. Vertical flows + // top-to-bottom: connect the bottom edge of the current node to the + // top edge of the next, using horizontal centers. + const edge = + orientation === "vertical" + ? { + x1: currentRect.left - offsetX + currentRect.width / 2, + y1: currentRect.top - offsetY + currentRect.height, + x2: nextRect.left - offsetX + nextRect.width / 2, + y2: nextRect.top - offsetY, + } + : { + x1: currentRect.left - offsetX + currentRect.width, + y1: currentRect.top - offsetY + currentRect.height / 2, + x2: nextRect.left - offsetX, + y2: nextRect.top - offsetY + nextRect.height / 2, + }; edges.push({ - x1: currentRect.left - offsetX + currentRect.width, - y1: currentRect.top - offsetY + currentRect.height / 2, - x2: nextRect.left - offsetX, - y2: nextRect.top - offsetY + nextRect.height / 2, + ...edge, disabled: isDisabled, single: true, fromId: currentNode.id, @@ -442,7 +458,7 @@ export function FlowNodeList({ children }: { children: ReactNode }) { } setConnectors(edges); - }, [descendants.descendants]); + }, [descendants.descendants, orientation]); /** * Recompute connectors after layout so that containerRect and node rects are @@ -497,12 +513,13 @@ export function FlowNodeList({ children }: { children: ReactNode }) {