feat: add example picker dropdown to sample diagrams
Each diagram type in the Sample Diagrams panel is now a split button: clicking the diagram name loads its default example, and a dropdown arrow on the right (shown when a diagram has more than one example) opens a popover listing all examples for that diagram. - getSampleDiagrams now returns all examples per diagram with the default example first, instead of flattening to the default's code - loadSampleDiagram analytics now include the chosen example title https://claude.ai/code/session_015rUgHChBEkquLu77fMxmQ6 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
6a9a306ed7
commit
a257f78d8e
@@ -1,13 +1,20 @@
|
||||
<script lang="ts">
|
||||
import Card from '$/components/Card/Card.svelte';
|
||||
import { Button } from '$/components/ui/button';
|
||||
import { getSampleDiagrams } from '$/util/mermaid';
|
||||
import { Button, buttonVariants } from '$/components/ui/button';
|
||||
import * as Popover from '$/components/ui/popover';
|
||||
import { getSampleDiagrams, type SampleExample } from '$/util/mermaid';
|
||||
import { updateCode } from '$lib/util/state.svelte';
|
||||
import { logEvent } from '$lib/util/stats';
|
||||
import { cn } from '$lib/utils';
|
||||
import ShapesIcon from '~icons/material-symbols/account-tree-outline-rounded';
|
||||
import ChevronDownIcon from '~icons/material-symbols/keyboard-arrow-down-rounded';
|
||||
|
||||
const extras = {
|
||||
ZenUML: `zenuml
|
||||
const extras: Record<string, SampleExample[]> = {
|
||||
ZenUML: [
|
||||
{
|
||||
title: 'Order Service',
|
||||
isDefault: true,
|
||||
code: `zenuml
|
||||
title Order Service
|
||||
@Actor Client #FFEBE6
|
||||
@Boundary OrderController #0747A6
|
||||
@@ -31,15 +38,18 @@
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const samples = { ...getSampleDiagrams(), ...extras } as const;
|
||||
const loadSampleDiagram = (diagramType: string): void => {
|
||||
updateCode(samples[diagramType as keyof typeof samples], {
|
||||
const samples = { ...getSampleDiagrams(), ...extras };
|
||||
|
||||
const loadSampleDiagram = (diagramType: string, example: SampleExample): void => {
|
||||
updateCode(example.code, {
|
||||
resetPanZoom: true,
|
||||
updateDiagram: true
|
||||
});
|
||||
logEvent('loadSampleDiagram', { diagramType });
|
||||
logEvent('loadSampleDiagram', { diagramType, exampleTitle: example.title });
|
||||
};
|
||||
|
||||
const mainDiagrams = [
|
||||
@@ -62,12 +72,39 @@
|
||||
<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">
|
||||
{#each diagramOrder as sample (sample)}
|
||||
{@const examples = samples[sample]}
|
||||
<div class="flex min-w-20 flex-grow">
|
||||
<Button
|
||||
size="sm"
|
||||
class="w-fit min-w-20 flex-grow normal-case"
|
||||
onclick={() => loadSampleDiagram(sample)}>
|
||||
class={cn('flex-grow normal-case', examples.length > 1 && 'rounded-r-none')}
|
||||
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}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -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
@@ -49,20 +49,20 @@ export const standardizeDiagramType = (diagramType: string) => {
|
||||
|
||||
type DiagramDefinition = (typeof diagramData)[number];
|
||||
|
||||
export type SampleExample = DiagramDefinition['examples'][number];
|
||||
|
||||
const isValidDiagram = (diagram: DiagramDefinition): diagram is Required<DiagramDefinition> => {
|
||||
return Boolean(diagram.name && diagram.examples && diagram.examples.length > 0);
|
||||
};
|
||||
|
||||
export const getSampleDiagrams = () => {
|
||||
const diagrams = diagramData
|
||||
.filter((d) => isValidDiagram(d))
|
||||
.map(({ examples, ...rest }) => ({
|
||||
...rest,
|
||||
example: examples?.filter(({ isDefault }) => isDefault)[0]
|
||||
}));
|
||||
const examples: Record<string, string> = {};
|
||||
for (const diagram of diagrams) {
|
||||
examples[diagram.name.replace(/ (Diagram|Chart|Graph)/, '')] = diagram.example.code;
|
||||
export const getSampleDiagrams = (): Record<string, SampleExample[]> => {
|
||||
const samples: Record<string, SampleExample[]> = {};
|
||||
for (const diagram of diagramData.filter((d) => isValidDiagram(d))) {
|
||||
// The default example comes first, so it is loaded when clicking the
|
||||
// diagram name and shown at the top of the example dropdown.
|
||||
samples[diagram.name.replace(/ (Diagram|Chart|Graph)/, '')] = [...diagram.examples].sort(
|
||||
(a, b) => Number(b.isDefault ?? false) - Number(a.isDefault ?? false)
|
||||
);
|
||||
}
|
||||
return examples;
|
||||
return samples;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user