docs: change workflow diagram to list (#8602)

This commit is contained in:
Shahed Nasser
2024-08-15 16:47:27 +03:00
committed by GitHub
parent b4d4b10ba5
commit 4bd0917aed
13 changed files with 300 additions and 206 deletions

View File

@@ -0,0 +1,27 @@
"use client"
import React from "react"
import { WorkflowStepUi } from "types"
import { WorkflowDiagramStepNode } from "../../Common/Node"
import { WorkflowDiagramLine } from "../../Common/Line"
export type WorkflowDiagramCanvasDepthProps = {
cluster: WorkflowStepUi[]
next: WorkflowStepUi[]
}
export const WorkflowDiagramCanvasDepth = ({
cluster,
next,
}: WorkflowDiagramCanvasDepthProps) => {
return (
<div className="flex items-start">
<div className="flex flex-col justify-center gap-y-docs_0.5">
{cluster.map((step) => (
<WorkflowDiagramStepNode key={step.name} step={step} />
))}
</div>
<WorkflowDiagramLine step={next} />
</div>
)
}

View File

@@ -0,0 +1,206 @@
"use client"
import clsx from "clsx"
import {
useAnimationControls,
useDragControls,
useMotionValue,
motion,
} from "framer-motion"
import React, { useEffect, useRef, useState } from "react"
import { ArrowPathMini, MinusMini, PlusMini } from "@medusajs/icons"
import { DropdownMenu, Text } from "@medusajs/ui"
import { createNodeClusters, getNextCluster } from "../../../utils"
import { WorkflowDiagramCanvasDepth } from "./Depth"
import { WorkflowDiagramCommonProps } from "../../.."
type ZoomScale = 0.5 | 0.75 | 1
const defaultState = {
x: -1000,
y: -1020,
scale: 1,
}
const MAX_ZOOM = 1.5
const MIN_ZOOM = 0.5
const ZOOM_STEP = 0.25
export const WorkflowDiagramCanvas = ({
workflow,
}: WorkflowDiagramCommonProps) => {
const [zoom, setZoom] = useState<number>(1)
const [isDragging, setIsDragging] = useState(false)
const scale = useMotionValue(defaultState.scale)
const x = useMotionValue(defaultState.x)
const y = useMotionValue(defaultState.y)
const controls = useAnimationControls()
const dragControls = useDragControls()
const dragConstraints = useRef<HTMLDivElement>(null)
const canZoomIn = zoom < MAX_ZOOM
const canZoomOut = zoom > MIN_ZOOM
useEffect(() => {
const unsubscribe = scale.on("change", (latest) => {
setZoom(latest as ZoomScale)
})
return () => {
unsubscribe()
}
}, [scale])
const clusters = createNodeClusters(workflow.steps)
function scaleXandY(
prevScale: number,
newScale: number,
x: number,
y: number
) {
const scaleRatio = newScale / prevScale
return {
x: x * scaleRatio,
y: y * scaleRatio,
}
}
const changeZoom = (newScale: number) => {
const { x: newX, y: newY } = scaleXandY(zoom, newScale, x.get(), y.get())
setZoom(newScale)
controls.set({ scale: newScale, x: newX, y: newY })
}
const zoomIn = () => {
const curr = scale.get()
if (curr < 1.5) {
const newScale = curr + ZOOM_STEP
changeZoom(newScale)
}
}
const zoomOut = () => {
const curr = scale.get()
if (curr > 0.5) {
const newScale = curr - ZOOM_STEP
changeZoom(newScale)
}
}
const resetCanvas = async () => await controls.start(defaultState)
return (
<div className="h-[200px] w-full rounded-docs_DEFAULT">
<div
ref={dragConstraints}
className="relative size-full rounded-docs_DEFAULT"
>
<div className="relative size-full overflow-hidden object-contain rounded-docs_DEFAULT shadow-elevation-card-rest">
<div>
<motion.div
onMouseDown={() => setIsDragging(true)}
onMouseUp={() => setIsDragging(false)}
drag
dragConstraints={dragConstraints}
dragElastic={0}
dragMomentum={false}
dragControls={dragControls}
initial={false}
animate={controls}
transition={{ duration: 0.25 }}
style={{
x,
y,
scale,
}}
className={clsx(
"bg-medusa-bg-subtle relative size-[500rem] origin-top-left items-start justify-start overflow-hidden",
"bg-[radial-gradient(var(--docs-border-base)_1.5px,transparent_0)] bg-[length:20px_20px] bg-repeat",
!isDragging && "cursor-grab",
isDragging && "cursor-grabbing"
)}
>
<main className="size-full">
<div className="absolute left-[1100px] top-[1100px] flex select-none items-start">
{Object.entries(clusters).map(([depth, cluster]) => {
const next = getNextCluster(clusters, Number(depth))
return (
<WorkflowDiagramCanvasDepth
cluster={cluster}
next={next}
key={depth}
/>
)
})}
</div>
</main>
</motion.div>
</div>
</div>
<div className="bg-medusa-bg-base shadow-borders-base text-medusa-fg-subtle absolute bottom-docs_1 left-docs_1.5 flex h-[28px] items-center overflow-hidden rounded-docs_sm">
<div className="flex items-center">
<button
onClick={zoomIn}
type="button"
disabled={!canZoomIn}
aria-label="Zoom in"
className="disabled:text-medusa-fg-disabled transition-fg hover:bg-medusa-bg-base-hover active:bg-medusa-bg-base-pressed focus-visible:bg-medusa-bg-base-pressed border-r p-docs_0.25 outline-none"
>
<PlusMini />
</button>
<div>
<DropdownMenu>
<DropdownMenu.Trigger className="disabled:text-medusa-fg-disabled transition-fg hover:bg-medusa-bg-base-hover active:bg-medusa-bg-base-pressed focus-visible:bg-medusa-bg-base-pressed flex w-[50px] items-center justify-center border-r p-docs_0.25 outline-none">
<Text
as="span"
size="xsmall"
leading="compact"
className="select-none tabular-nums"
>
{Math.round(zoom * 100)}%
</Text>
</DropdownMenu.Trigger>
<DropdownMenu.Content className="bg-medusa-bg-base">
{[50, 75, 100, 125, 150].map((value) => (
<DropdownMenu.Item
key={value}
onClick={() => changeZoom(value / 100)}
className="px-docs_0.5 py-[6px]"
>
{value}%
</DropdownMenu.Item>
))}
</DropdownMenu.Content>
</DropdownMenu>
</div>
<button
onClick={zoomOut}
type="button"
disabled={!canZoomOut}
aria-label="Zoom out"
className="disabled:text-medusa-fg-disabled transition-fg hover:bg-medusa-bg-base-hover active:bg-medusa-bg-base-pressed focus-visible:bg-medusa-bg-base-pressed border-r p-docs_0.25 outline-none"
>
<MinusMini />
</button>
</div>
<button
onClick={resetCanvas}
type="button"
aria-label="Reset canvas"
className="disabled:text-medusa-fg-disabled transition-fg hover:bg-medusa-bg-base-hover active:bg-medusa-bg-base-pressed focus-visible:bg-medusa-bg-base-pressed p-docs_0.25 outline-none"
>
<ArrowPathMini />
</button>
</div>
</div>
</div>
)
}

View File

@@ -2,8 +2,8 @@
import React from "react"
import { WorkflowStepUi } from "types"
import { WorkflowDiagramStepNode } from "../Node"
import { WorkflowDiagramLine } from "../Line"
import { WorkflowDiagramStepNode } from "../../Common/Node"
import { WorkflowDiagramLine } from "../../Common/Line"
export type WorkflowDiagramDepthProps = {
cluster: WorkflowStepUi[]

View File

@@ -45,7 +45,7 @@ export const WorkflowDiagramStepNode = ({ step }: WorkflowDiagramNodeProps) => {
>
<div
className={clsx(
"shadow-borders-base flex min-w-[120px] bg-medusa-bg-base",
"shadow-borders-base flex min-w-[120px] w-min bg-medusa-bg-base",
"items-center rounded-docs_sm py-docs_0.125 px-docs_0.5",
(step.type === "hook" || step.when) && "gap-x-docs_0.125"
)}

View File

@@ -0,0 +1,26 @@
"use client"
import React from "react"
import { WorkflowStepUi } from "types"
import { WorkflowDiagramStepNode } from "../../Common/Node"
import { WorkflowDiagramLine } from "../../Common/Line"
export type WorkflowDiagramListDepthProps = {
cluster: WorkflowStepUi[]
next: WorkflowStepUi[]
}
export const WorkflowDiagramListDepth = ({
cluster,
}: WorkflowDiagramListDepthProps) => {
return (
<div className="flex items-start">
<WorkflowDiagramLine step={cluster} />
<div className="flex flex-col justify-center gap-y-docs_0.5">
{cluster.map((step) => (
<WorkflowDiagramStepNode key={step.name} step={step} />
))}
</div>
</div>
)
}

View File

@@ -0,0 +1,24 @@
"use client"
import React from "react"
import { createNodeClusters, getNextCluster } from "../../../utils"
import { WorkflowDiagramCommonProps } from "../../.."
import { WorkflowDiagramListDepth } from "./Depth"
export const WorkflowDiagramList = ({
workflow,
}: WorkflowDiagramCommonProps) => {
const clusters = createNodeClusters(workflow.steps)
return (
<div className="flex flex-col gap-docs_0.5">
{Object.entries(clusters).map(([depth, cluster]) => {
const next = getNextCluster(clusters, Number(depth))
return (
<WorkflowDiagramListDepth cluster={cluster} next={next} key={depth} />
)
})}
</div>
)
}

View File

@@ -1,218 +1,29 @@
"use client"
import clsx from "clsx"
import {
useAnimationControls,
useDragControls,
useMotionValue,
motion,
} from "framer-motion"
import React, { Suspense, useEffect, useRef, useState } from "react"
import { ArrowPathMini, MinusMini, PlusMini } from "@medusajs/icons"
import { DropdownMenu, Text } from "@medusajs/ui"
import { createNodeClusters, getNextCluster } from "../../utils"
import React, { Suspense } from "react"
import { Workflow } from "types"
import { WorkflowDiagramDepth } from "./Depth"
import { Loading } from "../.."
import { WorkflowDiagramCanvas } from "./Canvas"
import { WorkflowDiagramList } from "./List"
export type WorkflowDiagramProps = {
export type WorkflowDiagramCommonProps = {
workflow: Workflow
}
type ZoomScale = 0.5 | 0.75 | 1
export type WorkflowDiagramType = "canvas" | "list"
const defaultState = {
x: -1000,
y: -1020,
scale: 1,
export type WorkflowDiagramProps = WorkflowDiagramCommonProps & {
type?: WorkflowDiagramType
}
const MAX_ZOOM = 1.5
const MIN_ZOOM = 0.5
const ZOOM_STEP = 0.25
const WorkflowDiagramContent = ({ workflow }: WorkflowDiagramProps) => {
const [zoom, setZoom] = useState<number>(1)
const [isDragging, setIsDragging] = useState(false)
const scale = useMotionValue(defaultState.scale)
const x = useMotionValue(defaultState.x)
const y = useMotionValue(defaultState.y)
const controls = useAnimationControls()
const dragControls = useDragControls()
const dragConstraints = useRef<HTMLDivElement>(null)
const canZoomIn = zoom < MAX_ZOOM
const canZoomOut = zoom > MIN_ZOOM
useEffect(() => {
const unsubscribe = scale.on("change", (latest) => {
setZoom(latest as ZoomScale)
})
return () => {
unsubscribe()
}
}, [scale])
// TODO pass steps here.
const clusters = createNodeClusters(workflow.steps)
function scaleXandY(
prevScale: number,
newScale: number,
x: number,
y: number
) {
const scaleRatio = newScale / prevScale
return {
x: x * scaleRatio,
y: y * scaleRatio,
}
}
const changeZoom = (newScale: number) => {
const { x: newX, y: newY } = scaleXandY(zoom, newScale, x.get(), y.get())
setZoom(newScale)
controls.set({ scale: newScale, x: newX, y: newY })
}
const zoomIn = () => {
const curr = scale.get()
if (curr < 1.5) {
const newScale = curr + ZOOM_STEP
changeZoom(newScale)
}
}
const zoomOut = () => {
const curr = scale.get()
if (curr > 0.5) {
const newScale = curr - ZOOM_STEP
changeZoom(newScale)
}
}
const resetCanvas = async () => await controls.start(defaultState)
return (
<div className="h-[200px] w-full rounded-docs_DEFAULT">
<div
ref={dragConstraints}
className="relative size-full rounded-docs_DEFAULT"
>
<div className="relative size-full overflow-hidden object-contain rounded-docs_DEFAULT shadow-elevation-card-rest">
<div>
<motion.div
onMouseDown={() => setIsDragging(true)}
onMouseUp={() => setIsDragging(false)}
drag
dragConstraints={dragConstraints}
dragElastic={0}
dragMomentum={false}
dragControls={dragControls}
initial={false}
animate={controls}
transition={{ duration: 0.25 }}
style={{
x,
y,
scale,
}}
className={clsx(
"bg-medusa-bg-subtle relative size-[500rem] origin-top-left items-start justify-start overflow-hidden",
"bg-[radial-gradient(var(--docs-border-base)_1.5px,transparent_0)] bg-[length:20px_20px] bg-repeat",
!isDragging && "cursor-grab",
isDragging && "cursor-grabbing"
)}
>
<main className="size-full">
<div className="absolute left-[1100px] top-[1100px] flex select-none items-start">
{Object.entries(clusters).map(([depth, cluster]) => {
const next = getNextCluster(clusters, Number(depth))
return (
<WorkflowDiagramDepth
cluster={cluster}
next={next}
key={depth}
/>
)
})}
</div>
</main>
</motion.div>
</div>
</div>
<div className="bg-medusa-bg-base shadow-borders-base text-medusa-fg-subtle absolute bottom-docs_1 left-docs_1.5 flex h-[28px] items-center overflow-hidden rounded-docs_sm">
<div className="flex items-center">
<button
onClick={zoomIn}
type="button"
disabled={!canZoomIn}
aria-label="Zoom in"
className="disabled:text-medusa-fg-disabled transition-fg hover:bg-medusa-bg-base-hover active:bg-medusa-bg-base-pressed focus-visible:bg-medusa-bg-base-pressed border-r p-docs_0.25 outline-none"
>
<PlusMini />
</button>
<div>
<DropdownMenu>
<DropdownMenu.Trigger className="disabled:text-medusa-fg-disabled transition-fg hover:bg-medusa-bg-base-hover active:bg-medusa-bg-base-pressed focus-visible:bg-medusa-bg-base-pressed flex w-[50px] items-center justify-center border-r p-docs_0.25 outline-none">
<Text
as="span"
size="xsmall"
leading="compact"
className="select-none tabular-nums"
>
{Math.round(zoom * 100)}%
</Text>
</DropdownMenu.Trigger>
<DropdownMenu.Content className="bg-medusa-bg-base">
{[50, 75, 100, 125, 150].map((value) => (
<DropdownMenu.Item
key={value}
onClick={() => changeZoom(value / 100)}
className="px-docs_0.5 py-[6px]"
>
{value}%
</DropdownMenu.Item>
))}
</DropdownMenu.Content>
</DropdownMenu>
</div>
<button
onClick={zoomOut}
type="button"
disabled={!canZoomOut}
aria-label="Zoom out"
className="disabled:text-medusa-fg-disabled transition-fg hover:bg-medusa-bg-base-hover active:bg-medusa-bg-base-pressed focus-visible:bg-medusa-bg-base-pressed border-r p-docs_0.25 outline-none"
>
<MinusMini />
</button>
</div>
<button
onClick={resetCanvas}
type="button"
aria-label="Reset canvas"
className="disabled:text-medusa-fg-disabled transition-fg hover:bg-medusa-bg-base-hover active:bg-medusa-bg-base-pressed focus-visible:bg-medusa-bg-base-pressed p-docs_0.25 outline-none"
>
<ArrowPathMini />
</button>
</div>
</div>
</div>
)
}
export const WorkflowDiagram = (props: WorkflowDiagramProps) => {
export const WorkflowDiagram = ({
type = "list",
...props
}: WorkflowDiagramProps) => {
return (
<Suspense fallback={<Loading />}>
<WorkflowDiagramContent {...props} />
{type === "canvas" && <WorkflowDiagramCanvas {...props} />}
{type === "list" && <WorkflowDiagramList {...props} />}
</Suspense>
)
}

View File

@@ -52,7 +52,7 @@ export default function (theme: MarkdownTheme) {
})
return (
`${Handlebars.helpers.titleLevel()} Diagram\n\n` +
`${Handlebars.helpers.titleLevel()} Steps\n\n` +
formatWorkflowDiagramComponent({
component: workflowDiagramComponent,
componentItem: {