Merge pull request #1987 from mermaid-js/sidv/examplesSelection

feat: add example picker dropdown to sample diagrams
This commit is contained in:
Sidharth Vinod
2026-06-10 18:43:03 +00:00
committed by GitHub
3 changed files with 87 additions and 27 deletions
+53 -16
View File
@@ -1,13 +1,20 @@
<script lang="ts"> <script lang="ts">
import Card from '$/components/Card/Card.svelte'; import Card from '$/components/Card/Card.svelte';
import { Button } from '$/components/ui/button'; import { Button, buttonVariants } from '$/components/ui/button';
import { getSampleDiagrams } from '$/util/mermaid'; import * as Popover from '$/components/ui/popover';
import { getSampleDiagrams, type SampleExample } from '$/util/mermaid';
import { updateCode } from '$lib/util/state.svelte'; import { updateCode } from '$lib/util/state.svelte';
import { logEvent } from '$lib/util/stats'; import { logEvent } from '$lib/util/stats';
import { cn } from '$lib/utils';
import ShapesIcon from '~icons/material-symbols/account-tree-outline-rounded'; import ShapesIcon from '~icons/material-symbols/account-tree-outline-rounded';
import ChevronDownIcon from '~icons/material-symbols/keyboard-arrow-down-rounded';
const extras = { const extras: Record<string, SampleExample[]> = {
ZenUML: `zenuml ZenUML: [
{
title: 'Order Service',
isDefault: true,
code: `zenuml
title Order Service title Order Service
@Actor Client #FFEBE6 @Actor Client #FFEBE6
@Boundary OrderController #0747A6 @Boundary OrderController #0747A6
@@ -25,21 +32,24 @@
if(order != null) { if(order != null) {
par { par {
PurchaseService.createPO(order) PurchaseService.createPO(order)
InvoiceService.createInvoice(order) InvoiceService.createInvoice(order)
} }
} }
} }
} }
` `
}
]
}; };
const samples = { ...getSampleDiagrams(), ...extras } as const; const samples = { ...getSampleDiagrams(), ...extras };
const loadSampleDiagram = (diagramType: string): void => {
updateCode(samples[diagramType as keyof typeof samples], { const loadSampleDiagram = (diagramType: string, example: SampleExample): void => {
updateCode(example.code, {
resetPanZoom: true, resetPanZoom: true,
updateDiagram: true updateDiagram: true
}); });
logEvent('loadSampleDiagram', { diagramType }); logEvent('loadSampleDiagram', { diagramType, exampleTitle: example.title });
}; };
const mainDiagrams = [ const mainDiagrams = [
@@ -62,12 +72,39 @@
<Card title="Sample Diagrams" isOpen isStackable icon={{ component: ShapesIcon }}> <Card title="Sample Diagrams" isOpen isStackable icon={{ component: ShapesIcon }}>
<div class="flex h-fit max-h-52 flex-wrap gap-2 overflow-y-auto p-2"> <div class="flex h-fit max-h-52 flex-wrap gap-2 overflow-y-auto p-2">
{#each diagramOrder as sample (sample)} {#each diagramOrder as sample (sample)}
<Button {@const examples = samples[sample]}
size="sm" <div class="flex min-w-20 flex-grow">
class="w-fit min-w-20 flex-grow normal-case" <Button
onclick={() => loadSampleDiagram(sample)}> size="sm"
{sample} class={cn('flex-grow normal-case', examples.length > 1 && 'rounded-r-none')}
</Button> onclick={() => loadSampleDiagram(sample, examples[0])}>
{sample}
</Button>
{#if examples.length > 1}
<Popover.Root>
<Popover.Trigger
aria-label="Choose a {sample} example"
class={cn(
buttonVariants({ size: 'sm' }),
'rounded-l-none border-l border-primary-foreground/30 px-0.5 [&_svg]:size-5'
)}>
<ChevronDownIcon />
</Popover.Trigger>
<Popover.Content align="start" class="flex w-fit flex-col gap-1 p-1">
{#each examples as example (example.title)}
<Popover.Close
class={cn(
buttonVariants({ variant: 'ghost', size: 'sm' }),
'justify-start normal-case'
)}
onclick={() => loadSampleDiagram(sample, example)}>
{example.title}
</Popover.Close>
{/each}
</Popover.Content>
</Popover.Root>
{/if}
</div>
{/each} {/each}
</div> </div>
</Card> </Card>
+23
View File
@@ -0,0 +1,23 @@
import { describe, expect, it } from 'vitest';
import { getSampleDiagrams } from './mermaid';
describe('getSampleDiagrams', () => {
const samples = getSampleDiagrams();
it('should return at least one example per diagram', () => {
expect(Object.keys(samples).length).toBeGreaterThan(0);
for (const [name, examples] of Object.entries(samples)) {
expect(examples.length, `${name} should have at least one example`).toBeGreaterThan(0);
for (const example of examples) {
expect(example.title, `${name} has an example without a title`).toBeTruthy();
expect(example.code, `${name} example "${example.title}" has no code`).toBeTruthy();
}
}
});
it('should list the default example first', () => {
for (const [name, examples] of Object.entries(samples)) {
expect(examples[0].isDefault, `${name} should have its default example first`).toBe(true);
}
});
});
+11 -11
View File
@@ -49,20 +49,20 @@ export const standardizeDiagramType = (diagramType: string) => {
type DiagramDefinition = (typeof diagramData)[number]; type DiagramDefinition = (typeof diagramData)[number];
export type SampleExample = DiagramDefinition['examples'][number];
const isValidDiagram = (diagram: DiagramDefinition): diagram is Required<DiagramDefinition> => { const isValidDiagram = (diagram: DiagramDefinition): diagram is Required<DiagramDefinition> => {
return Boolean(diagram.name && diagram.examples && diagram.examples.length > 0); return Boolean(diagram.name && diagram.examples && diagram.examples.length > 0);
}; };
export const getSampleDiagrams = () => { export const getSampleDiagrams = (): Record<string, SampleExample[]> => {
const diagrams = diagramData const samples: Record<string, SampleExample[]> = {};
.filter((d) => isValidDiagram(d)) for (const diagram of diagramData.filter((d) => isValidDiagram(d))) {
.map(({ examples, ...rest }) => ({ // The default example comes first, so it is loaded when clicking the
...rest, // diagram name and shown at the top of the example dropdown.
example: examples?.filter(({ isDefault }) => isDefault)[0] samples[diagram.name.replace(/ (Diagram|Chart|Graph)/, '')] = [...diagram.examples].sort(
})); (a, b) => Number(b.isDefault ?? false) - Number(a.isDefault ?? false)
const examples: Record<string, string> = {}; );
for (const diagram of diagrams) {
examples[diagram.name.replace(/ (Diagram|Chart|Graph)/, '')] = diagram.example.code;
} }
return examples; return samples;
}; };