Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions packages/components/src/components/mermaid/mermaid.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ const meta: Meta<typeof Mermaid> = {
control: "text",
description: "Additional CSS classes for the root element",
},
actions: {
control: "boolean",
description:
"Show or hide the interactive controls. When set, this overrides the default behavior (controls shown when diagram height exceeds 120px).",
},
placement: {
control: "select",
options: ["top-left", "top-right", "bottom-left", "bottom-right"],
description: "Position of the interactive controls.",
},
ariaLabel: {
control: "text",
description: "Accessible label for the diagram",
},
},
};

Expand Down Expand Up @@ -217,9 +231,18 @@ export const LargeDiagram: Story = {
K -->|Yes| L[Celebrate]
K -->|No| M[Try Again]
M --> B`}
placement="top-right"
/>
</div>
),
parameters: {
docs: {
description: {
story:
"Large diagram with controls in the top-right corner. Controls appear automatically because the diagram height exceeds 120px.",
},
},
},
};

export const AllDiagramTypes: Story = {
Expand All @@ -245,6 +268,51 @@ export const AllDiagramTypes: Story = {
),
};

export const ActionsHidden: Story = {
args: {
chart: flowchartExample,
actions: false,
},
parameters: {
docs: {
description: {
story: "Hide the interactive controls regardless of diagram size.",
},
},
},
};

export const ActionsVisible: Story = {
args: {
chart: `flowchart LR
A --> B`,
actions: true,
},
parameters: {
docs: {
description: {
story: "Always show the interactive controls, even for small diagrams.",
},
},
},
};

export const Placement: Story = {
args: {
chart: flowchartExample,
actions: true,
placement: "bottom-right",
},
parameters: {
docs: {
description: {
story:
"Position of the interactive controls. Use the controls panel to try different placements: top-left, top-right, bottom-left, bottom-right.",
},
},
},
};

export const InvalidSyntax: Story = {
args: {
chart: `graph TD
Expand Down
21 changes: 18 additions & 3 deletions packages/components/src/components/mermaid/mermaid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,26 @@ import { ZoomControls } from "./zoom-controls";

const MIN_HEIGHT_FOR_CONTROLS = 120;

type MermaidPlacement =
| "top-left"
| "top-right"
| "bottom-left"
| "bottom-right";

type MermaidProps = {
chart: string;
className?: string;
ariaLabel?: string;
placement?: MermaidPlacement;
actions?: boolean;
};

const Mermaid = ({
chart,
className,
ariaLabel = "Mermaid diagram",
placement = "bottom-right",
actions,
}: MermaidProps) => {
const [svg, setSvg] = useState("");
const [error, setError] = useState<string | null>(null);
Expand Down Expand Up @@ -85,6 +95,10 @@ const Mermaid = ({
}, [chart, id, isDarkTheme]);

useEffect(() => {
if (actions !== undefined) {
return;
}

const container = containerRef.current;

if (!container) {
Expand All @@ -103,7 +117,7 @@ const Mermaid = ({
return () => {
resizeObserver.disconnect();
};
}, []);
}, [actions]);

if (error) {
return (
Expand Down Expand Up @@ -134,13 +148,14 @@ const Mermaid = ({
className
)}
>
{showControls && (
{(actions === true || (actions === undefined && showControls)) && (
<ZoomControls
onPan={pan}
onReset={reset}
onZoomIn={zoomIn}
onZoomOut={zoomOut}
panStep={panStep}
placement={placement}
/>
)}
<div
Expand All @@ -157,4 +172,4 @@ const Mermaid = ({
};

export { Mermaid };
export type { MermaidProps };
export type { MermaidProps, MermaidPlacement };
11 changes: 10 additions & 1 deletion packages/components/src/components/mermaid/zoom-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import type { ComponentProps } from "react";

import { cn } from "@/utils/cn";
import type { MermaidPlacement } from "./mermaid";

const Button = ({
children,
Expand All @@ -36,6 +37,7 @@ type ZoomControlsProps = {
onReset: () => void;
onPan: (dx: number, dy: number) => void;
panStep: number;
placement?: MermaidPlacement;
};

const ZoomControls = ({
Expand All @@ -44,12 +46,19 @@ const ZoomControls = ({
onReset,
onPan,
panStep,
placement,
}: ZoomControlsProps) => {
return (
// biome-ignore lint/a11y/useSemanticElements: TODO
<div
aria-label="Diagram zoom and pan controls"
className="absolute right-2 bottom-2 z-10 grid grid-cols-3 gap-1"
className={cn(
"absolute z-10 grid grid-cols-3 gap-1",
placement === "top-left" && "top-2 left-2",
placement === "top-right" && "top-2 right-2",
placement === "bottom-left" && "bottom-2 left-2",
placement === "bottom-right" && "right-2 bottom-2"
)}
Comment thread
pqoqubbw marked this conversation as resolved.
data-component-part="zoom-controls"
role="group"
>
Expand Down