Skip to content

Commit fb8bf53

Browse files
committed
feat(mermaidViz): add mermaid component
1 parent ad20937 commit fb8bf53

File tree

6 files changed

+214
-0
lines changed

6 files changed

+214
-0
lines changed

src/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Text from './text/text'
2323
import Timestamp from './timestamp/timestamp'
2424
import YoutubeVideo from './video/youtubeVideo'
2525
import RechartsTimeSeries from './visualization/chart/rechartsTimeSeries'
26+
import MermaidViz from './visualization/mermaidViz/mermaidViz'
2627
import VegaLiteViz from './visualization/vegaLiteViz/vegaLiteViz'
2728

2829
export {
@@ -34,6 +35,7 @@ export {
3435
Image,
3536
MarkedMarkdown,
3637
MarkedStreamingMarkdown,
38+
MermaidViz,
3739
MessageCanvas,
3840
MessageSpace,
3941
MultimodalInput,

src/components/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { MermaidConfig } from 'mermaid'
12
import type { Renderers } from 'vega'
23
import type { EmbedOptions, VisualizationSpec } from 'vega-embed'
34
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -157,6 +158,15 @@ export interface VegaLiteFormat extends DataFormat {
157158

158159
export type VegaLiteData = VegaLiteFormat & Updates<VegaLiteFormat>
159160

161+
export interface MermaidFormat extends DataFormat {
162+
/** Follow mermaid's [documentation](https://mermaid.js.org/intro/) to provide a code string. */
163+
code: string
164+
/** Follow mermaid's [documentation](https://mermaid.js.org/config/setup/README.html) to provide a config object. It's used for additional customizations. */
165+
config?: MermaidConfig
166+
}
167+
168+
export type MermaidData = MermaidFormat & Updates<MermaidFormat>
169+
160170
export interface MediaFormat extends DataFormat {
161171
/** URL of the media file to be played. */
162172
src: string
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.rustic-mermaid-container {
2+
height: 100%;
3+
width: 100%;
4+
}
5+
6+
.rustic-mermaid-container div:last-of-type {
7+
flex: 1;
8+
min-height: 0;
9+
display: flex;
10+
justify-content: center;
11+
}
12+
13+
.rustic-mermaid-container div:last-of-type svg {
14+
height: 100%;
15+
width: 100%;
16+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { supportedViewports } from '../../../../cypress/support/variables'
2+
import MermaidViz from './mermaidViz'
3+
4+
describe('MermaidViz', () => {
5+
const codeExample = `
6+
flowchart TD
7+
A[Christmas] -->|Get money| B(Go shopping)
8+
B --> C{Let me think}
9+
C -->|One| D[Laptop]
10+
C -->|Two| E[iPhone]
11+
C -->|Three| F[fa:fa-car Car]`
12+
const mermaidContainer = '[data-cy="mermaid-container"]'
13+
14+
supportedViewports.forEach((viewport) => {
15+
it(`should display the diagram on ${viewport} screen`, () => {
16+
cy.viewport(viewport)
17+
cy.mount(
18+
<MermaidViz
19+
code={codeExample}
20+
title="Christmas Shopping"
21+
description="A flow chart"
22+
/>
23+
)
24+
25+
cy.get(mermaidContainer).should('exist')
26+
cy.get(mermaidContainer).contains('Christmas Shopping')
27+
cy.get(mermaidContainer).contains('A flow chart')
28+
})
29+
})
30+
})
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import type { Meta, StoryFn } from '@storybook/react'
2+
import React from 'react'
3+
4+
import MermaidViz from './mermaidViz'
5+
6+
const meta: Meta<React.ComponentProps<typeof MermaidViz>> = {
7+
title: 'Rustic UI/Visualization/MermaidViz',
8+
component: MermaidViz,
9+
tags: ['autodocs'],
10+
parameters: {
11+
layout: 'centered',
12+
},
13+
}
14+
15+
export default meta
16+
meta.argTypes = {
17+
...meta.argTypes,
18+
}
19+
20+
const decorators = [
21+
(Story: StoryFn) => {
22+
return (
23+
<div
24+
style={{
25+
width: 'clamp(250px, 70vw, 1000px)',
26+
}}
27+
>
28+
<Story />
29+
</div>
30+
)
31+
},
32+
]
33+
34+
export const classDiagram = {
35+
args: {
36+
code: `classDiagram
37+
Animal <|-- Duck
38+
Animal <|-- Fish
39+
Animal <|-- Zebra
40+
Animal : +int age
41+
Animal : +String gender
42+
Animal: +isMammal()
43+
Animal: +mate()
44+
class Duck{
45+
+String beakColor
46+
+swim()
47+
+quack()
48+
}
49+
class Fish{
50+
-int sizeInFeet
51+
-canEat()
52+
}
53+
class Zebra{
54+
+bool is_wild
55+
+run()
56+
}`,
57+
},
58+
decorators,
59+
}
60+
61+
export const Flow = {
62+
args: {
63+
code: `
64+
flowchart TD
65+
A[Christmas] -->|Get money| B(Go shopping)
66+
B --> C{Let me think}
67+
C -->|One| D[Laptop]
68+
C -->|Two| E[iPhone]
69+
C -->|Three| F[fa:fa-car Car]`,
70+
},
71+
decorators,
72+
}
73+
74+
export const ERDiagram = {
75+
args: {
76+
title: 'Customer Order Management System ER Diagram',
77+
description:
78+
'This ER diagram illustrates the relationships in an ordering system where a customer has a delivery address, places orders, and is liable for invoices; delivery addresses receive orders, invoices cover orders, orders include order items, product categories contain products, and products are ordered in order items.',
79+
code: `
80+
erDiagram
81+
CUSTOMER }|..|{ DELIVERY-ADDRESS : has
82+
CUSTOMER ||--o{ ORDER : places
83+
CUSTOMER ||--o{ INVOICE : "liable for"
84+
DELIVERY-ADDRESS ||--o{ ORDER : receives
85+
INVOICE ||--|{ ORDER : covers
86+
ORDER ||--|{ ORDER-ITEM : includes
87+
PRODUCT-CATEGORY ||--|{ PRODUCT : contains
88+
PRODUCT ||--o{ ORDER-ITEM : "ordered in"`,
89+
},
90+
decorators,
91+
}
92+
93+
export const error = {
94+
args: {
95+
code: '',
96+
},
97+
decorators,
98+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import './mermaidViz.css'
2+
3+
import Stack from '@mui/material/Stack'
4+
import Typography from '@mui/material/Typography'
5+
import useTheme from '@mui/system/useTheme'
6+
import mermaid from 'mermaid'
7+
import React, { useEffect, useRef } from 'react'
8+
9+
import type { MermaidData } from '../../types'
10+
11+
/** The `MermaidViz` component leverages [Mermaid.js](https://mermaid.js.org/) to create dynamic and interactive diagrams, including flowcharts, sequence diagrams, class diagrams, and more. It is ideal for visualizing complex data and processes in a clear and structured manner. */
12+
function MermaidViz(props: MermaidData) {
13+
const chartRef = useRef(null)
14+
const rusticTheme = useTheme()
15+
const isDarkTheme = rusticTheme.palette.mode === 'dark'
16+
17+
useEffect(() => {
18+
if (chartRef.current) {
19+
mermaid.initialize({
20+
theme: 'base',
21+
themeVariables: {
22+
primaryColor: rusticTheme.palette.primary.main,
23+
primaryTextColor: rusticTheme.palette.background.paper,
24+
primaryBorderColor: rusticTheme.palette.divider,
25+
lineColor: rusticTheme.palette.divider,
26+
secondaryColor: rusticTheme.palette.secondary.main,
27+
background: rusticTheme.palette.background.paper,
28+
errorTextColor: rusticTheme.palette.error.main,
29+
errorBkgColor: rusticTheme.palette.error.main,
30+
fontFamily: 'Inter',
31+
},
32+
...props.config,
33+
})
34+
35+
mermaid.contentLoaded()
36+
}
37+
}, [isDarkTheme])
38+
39+
return (
40+
<Stack
41+
direction="column"
42+
className="rustic-mermaid-container"
43+
data-cy="mermaid-container"
44+
>
45+
{props.title && (
46+
<Typography variant="subtitle2">{props.title}</Typography>
47+
)}
48+
{props.description && (
49+
<Typography variant="caption">{props.description}</Typography>
50+
)}
51+
<div ref={chartRef} className="mermaid">
52+
{props.code}
53+
</div>
54+
</Stack>
55+
)
56+
}
57+
58+
export default MermaidViz

0 commit comments

Comments
 (0)