@djangocfg/ui-tools 2.1.382 → 2.1.383
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DictationField-U25MEYAL.mjs +4 -0
- package/dist/{DictationField-2ZLQWLYV.mjs.map → DictationField-U25MEYAL.mjs.map} +1 -1
- package/dist/DictationField-XWR5VOID.cjs +13 -0
- package/dist/{DictationField-IPPJ54CU.cjs.map → DictationField-XWR5VOID.cjs.map} +1 -1
- package/dist/{chunk-KMSBGNVC.cjs → chunk-4PFW7MIJ.cjs} +4 -2
- package/dist/chunk-4PFW7MIJ.cjs.map +1 -0
- package/dist/{chunk-4LXG3NBV.mjs → chunk-C2YN6WEO.mjs} +3 -3
- package/dist/chunk-C2YN6WEO.mjs.map +1 -0
- package/dist/index.cjs +139 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -1
- package/dist/index.d.ts +68 -1
- package/dist/index.mjs +141 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -13
- package/src/tools/Chat/index.ts +15 -0
- package/dist/DictationField-2ZLQWLYV.mjs +0 -4
- package/dist/DictationField-IPPJ54CU.cjs +0 -13
- package/dist/chunk-4LXG3NBV.mjs.map +0 -1
- package/dist/chunk-KMSBGNVC.cjs.map +0 -1
- package/src/components/markdown/MarkdownMessage/MarkdownMessage.story.tsx +0 -771
- package/src/stories/index.ts +0 -63
- package/src/tools/AudioPlayer/AudioPlayer.story.tsx +0 -481
- package/src/tools/Chat/stories/01-basic.story.tsx +0 -64
- package/src/tools/Chat/stories/02-bubbles.story.tsx +0 -21
- package/src/tools/Chat/stories/03-tool-calls.story.tsx +0 -59
- package/src/tools/Chat/stories/04-personas.story.tsx +0 -78
- package/src/tools/Chat/stories/05-launcher.story.tsx +0 -321
- package/src/tools/Chat/stories/06-header.story.tsx +0 -147
- package/src/tools/Chat/stories/07-audio-actions.story.tsx +0 -112
- package/src/tools/Chat/stories/shared/Frame.tsx +0 -21
- package/src/tools/Chat/stories/shared/index.ts +0 -5
- package/src/tools/Chat/stories/shared/messages.ts +0 -39
- package/src/tools/Chat/stories/shared/personas.ts +0 -13
- package/src/tools/Chat/stories/shared/seeds.ts +0 -92
- package/src/tools/Chat/stories/shared/transports.ts +0 -36
- package/src/tools/CodeEditor/CodeEditor.story.tsx +0 -202
- package/src/tools/CronScheduler/CronScheduler.story.tsx +0 -300
- package/src/tools/Gallery/Gallery.story.tsx +0 -237
- package/src/tools/ImageViewer/ImageViewer.story.tsx +0 -85
- package/src/tools/JsonForm/JsonForm.story.tsx +0 -350
- package/src/tools/JsonTree/JsonTree.story.tsx +0 -141
- package/src/tools/LottiePlayer/LottiePlayer.story.tsx +0 -95
- package/src/tools/Map/Map.story.tsx +0 -458
- package/src/tools/MarkdownEditor/MarkdownEditor.story.tsx +0 -225
- package/src/tools/Mermaid/Mermaid.story.tsx +0 -251
- package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +0 -230
- package/src/tools/PrettyCode/PrettyCode.story.tsx +0 -304
- package/src/tools/SpeechRecognition/stories/01-basic.story.tsx +0 -32
- package/src/tools/SpeechRecognition/stories/02-dictation-field.story.tsx +0 -32
- package/src/tools/SpeechRecognition/stories/03-push-to-talk.story.tsx +0 -27
- package/src/tools/SpeechRecognition/stories/04-mic-meter.story.tsx +0 -35
- package/src/tools/SpeechRecognition/stories/05-custom-engine-http.story.tsx +0 -40
- package/src/tools/SpeechRecognition/stories/06-custom-engine-ws.story.tsx +0 -48
- package/src/tools/SpeechRecognition/stories/07-language-device.story.tsx +0 -57
- package/src/tools/SpeechRecognition/stories/08-errors-permissions.story.tsx +0 -25
- package/src/tools/SpeechRecognition/stories/09-chat-voice.story.tsx +0 -90
- package/src/tools/SpeechRecognition/stories/shared.tsx +0 -123
- package/src/tools/Tour/Tour.story.tsx +0 -279
- package/src/tools/Tree/Tree.story.tsx +0 -620
- package/src/tools/Uploader/Uploader.story.tsx +0 -415
- package/src/tools/VideoPlayer/VideoPlayer.story.tsx +0 -87
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import { defineStory } from '@djangocfg/playground';
|
|
2
|
-
import { MarkdownEditor } from './MarkdownEditor';
|
|
3
|
-
import { mentionPresets } from './mentionPresets';
|
|
4
|
-
import { useState, type ReactNode } from 'react';
|
|
5
|
-
import type { MentionConfig, MentionMarkdownRenderer } from './types';
|
|
6
|
-
|
|
7
|
-
export default defineStory({
|
|
8
|
-
title: 'Tools/Markdown Editor',
|
|
9
|
-
component: MarkdownEditor,
|
|
10
|
-
description: 'WYSIWYG markdown editor with Tiptap. Supports headings, lists, mentions, and more.',
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const SAMPLE_MARKDOWN = `# Character Bio
|
|
14
|
-
|
|
15
|
-
**Type:** Maltipoo (apricot coat)
|
|
16
|
-
**Age:** 3 years
|
|
17
|
-
|
|
18
|
-
## Visual Design
|
|
19
|
-
|
|
20
|
-
Medium-small Maltipoo, compact and agile.
|
|
21
|
-
|
|
22
|
-
- Sharp, intelligent eyes (dark brown)
|
|
23
|
-
- Simple collar (hides micro-gear)
|
|
24
|
-
- Expressions shift quickly
|
|
25
|
-
|
|
26
|
-
## Personality
|
|
27
|
-
|
|
28
|
-
> Pixar Note: Her face must carry duality.
|
|
29
|
-
|
|
30
|
-
1. Emotionally guarded
|
|
31
|
-
2. Independent and decisive
|
|
32
|
-
3. Dry sense of humor
|
|
33
|
-
`;
|
|
34
|
-
|
|
35
|
-
const MENTION_ITEMS: MentionConfig = {
|
|
36
|
-
items: [
|
|
37
|
-
{ id: '1', label: 'Alice', description: 'Protagonist', thumbnail: 'https://i.pravatar.cc/48?u=alice' },
|
|
38
|
-
{ id: '2', label: 'Bob', description: 'Antagonist', thumbnail: 'https://i.pravatar.cc/48?u=bob' },
|
|
39
|
-
{ id: '3', label: 'Charlie', description: 'Side character', thumbnail: 'https://i.pravatar.cc/48?u=charlie' },
|
|
40
|
-
{ id: '4', label: 'Diana', description: 'Narrator' },
|
|
41
|
-
{ id: '5', label: 'Eve', description: 'Mystery character' },
|
|
42
|
-
],
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export function Default() {
|
|
46
|
-
const [value, setValue] = useState(SAMPLE_MARKDOWN);
|
|
47
|
-
return (
|
|
48
|
-
<div style={{ maxWidth: 700 }}>
|
|
49
|
-
<MarkdownEditor value={value} onChange={setValue} />
|
|
50
|
-
<RawPreview value={value} />
|
|
51
|
-
</div>
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function WithMentions() {
|
|
56
|
-
const [value, setValue] = useState('Hello @Alice! This scene features @Bob too.\n\nType @ to mention characters.');
|
|
57
|
-
const [ids, setIds] = useState<string[]>([]);
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div style={{ maxWidth: 700 }}>
|
|
61
|
-
<MarkdownEditor
|
|
62
|
-
value={value}
|
|
63
|
-
onChange={setValue}
|
|
64
|
-
mentions={MENTION_ITEMS}
|
|
65
|
-
onMentionIdsChange={setIds}
|
|
66
|
-
placeholder="Describe the scene... Use @ to mention characters"
|
|
67
|
-
/>
|
|
68
|
-
{ids.length > 0 && (
|
|
69
|
-
<div style={{ marginTop: 8, fontSize: 12, opacity: 0.6 }}>
|
|
70
|
-
Mentioned IDs: {ids.join(', ')}
|
|
71
|
-
</div>
|
|
72
|
-
)}
|
|
73
|
-
<RawPreview value={value} />
|
|
74
|
-
</div>
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function Empty() {
|
|
79
|
-
const [value, setValue] = useState('');
|
|
80
|
-
return (
|
|
81
|
-
<div style={{ maxWidth: 600 }}>
|
|
82
|
-
<MarkdownEditor value={value} onChange={setValue} placeholder="Start writing..." />
|
|
83
|
-
</div>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function Disabled() {
|
|
88
|
-
return (
|
|
89
|
-
<div style={{ maxWidth: 600 }}>
|
|
90
|
-
<MarkdownEditor value="This editor is **read-only**." onChange={() => {}} disabled />
|
|
91
|
-
</div>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function NoToolbar() {
|
|
96
|
-
const [value, setValue] = useState('Plain text without toolbar.\n\nStill supports **markdown** shortcuts.');
|
|
97
|
-
return (
|
|
98
|
-
<div style={{ maxWidth: 600 }}>
|
|
99
|
-
<MarkdownEditor value={value} onChange={setValue} showToolbar={false} />
|
|
100
|
-
</div>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function Compact() {
|
|
105
|
-
const [value, setValue] = useState('Short note');
|
|
106
|
-
return (
|
|
107
|
-
<div style={{ maxWidth: 400 }}>
|
|
108
|
-
<MarkdownEditor value={value} onChange={setValue} minHeight={60} placeholder="Quick note..." />
|
|
109
|
-
</div>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function WithCustomUriPreset() {
|
|
114
|
-
const [value, setValue] = useState(
|
|
115
|
-
'Reference @Alice and @Bob — markdown will carry machine-readable URIs.\n\nType @ to add more.',
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<SerializationDemo
|
|
120
|
-
description={
|
|
121
|
-
<>
|
|
122
|
-
<code>mentionPresets.customUri('myapp', 'user')</code> — emits
|
|
123
|
-
<code>{' @[Label](myapp://user/id)'}</code>. Useful when downstream
|
|
124
|
-
parses the markdown back into deep-links while keeping the visible
|
|
125
|
-
<code>@</code> handle.
|
|
126
|
-
</>
|
|
127
|
-
}
|
|
128
|
-
value={value}
|
|
129
|
-
onChange={setValue}
|
|
130
|
-
renderMarkdown={mentionPresets.customUri('myapp', 'user')}
|
|
131
|
-
/>
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function WithMarkdownLinkPreset() {
|
|
136
|
-
const [value, setValue] = useState('Ping @Alice when ready, cc @Charlie.');
|
|
137
|
-
|
|
138
|
-
return (
|
|
139
|
-
<SerializationDemo
|
|
140
|
-
description={
|
|
141
|
-
<>
|
|
142
|
-
<code>mentionPresets.markdownLink('https://example.com/u/')</code> —
|
|
143
|
-
serializes mentions as ordinary clickable links: <code>[@Label](https://example.com/u/id)</code>.
|
|
144
|
-
</>
|
|
145
|
-
}
|
|
146
|
-
value={value}
|
|
147
|
-
onChange={setValue}
|
|
148
|
-
renderMarkdown={mentionPresets.markdownLink('https://example.com/u/')}
|
|
149
|
-
/>
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function WithCustomRenderer() {
|
|
154
|
-
const [value, setValue] = useState('Tag @Alice and @Bob to assign the task.');
|
|
155
|
-
|
|
156
|
-
// Hand-rolled serializer — any (attrs) => string works.
|
|
157
|
-
const renderMarkdown: MentionMarkdownRenderer = ({ id, label }) =>
|
|
158
|
-
`{{user:${id}|${label || 'unknown'}}}`;
|
|
159
|
-
|
|
160
|
-
return (
|
|
161
|
-
<SerializationDemo
|
|
162
|
-
description={
|
|
163
|
-
<>
|
|
164
|
-
Inline custom renderer: <code>{`({ id, label }) => \`{{user:\${id}|\${label}}}\``}</code>.
|
|
165
|
-
Shows that the serializer is just a function — you control the wire format.
|
|
166
|
-
</>
|
|
167
|
-
}
|
|
168
|
-
value={value}
|
|
169
|
-
onChange={setValue}
|
|
170
|
-
renderMarkdown={renderMarkdown}
|
|
171
|
-
/>
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
interface SerializationDemoProps {
|
|
176
|
-
description: ReactNode;
|
|
177
|
-
value: string;
|
|
178
|
-
onChange: (v: string) => void;
|
|
179
|
-
renderMarkdown: MentionMarkdownRenderer;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function SerializationDemo({ description, value, onChange, renderMarkdown }: SerializationDemoProps) {
|
|
183
|
-
const mentions: MentionConfig = { ...MENTION_ITEMS, renderMarkdown };
|
|
184
|
-
|
|
185
|
-
return (
|
|
186
|
-
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, maxWidth: 900 }}>
|
|
187
|
-
<div>
|
|
188
|
-
<p style={{ fontSize: 12, opacity: 0.7, marginBottom: 8 }}>{description}</p>
|
|
189
|
-
<MarkdownEditor
|
|
190
|
-
value={value}
|
|
191
|
-
onChange={onChange}
|
|
192
|
-
mentions={mentions}
|
|
193
|
-
placeholder="Type @ to insert a mention..."
|
|
194
|
-
/>
|
|
195
|
-
</div>
|
|
196
|
-
<div>
|
|
197
|
-
<div style={{ fontSize: 11, opacity: 0.5, marginBottom: 4, textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
|
198
|
-
Serialized markdown
|
|
199
|
-
</div>
|
|
200
|
-
<pre
|
|
201
|
-
style={{
|
|
202
|
-
fontSize: 12,
|
|
203
|
-
padding: 12,
|
|
204
|
-
background: 'rgba(127,127,127,0.08)',
|
|
205
|
-
borderRadius: 6,
|
|
206
|
-
whiteSpace: 'pre-wrap',
|
|
207
|
-
wordBreak: 'break-word',
|
|
208
|
-
margin: 0,
|
|
209
|
-
}}
|
|
210
|
-
>
|
|
211
|
-
{value || '(empty)'}
|
|
212
|
-
</pre>
|
|
213
|
-
</div>
|
|
214
|
-
</div>
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function RawPreview({ value }: { value: string }) {
|
|
219
|
-
return (
|
|
220
|
-
<details style={{ marginTop: 16 }}>
|
|
221
|
-
<summary style={{ cursor: 'pointer', opacity: 0.5, fontSize: 12 }}>Raw markdown</summary>
|
|
222
|
-
<pre style={{ fontSize: 11, opacity: 0.6, whiteSpace: 'pre-wrap', marginTop: 8 }}>{value}</pre>
|
|
223
|
-
</details>
|
|
224
|
-
);
|
|
225
|
-
}
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import { defineStory, useSelect, useBoolean } from '@djangocfg/playground';
|
|
2
|
-
import Mermaid, {
|
|
3
|
-
FlowDiagram,
|
|
4
|
-
SequenceDiagram,
|
|
5
|
-
JourneyDiagram,
|
|
6
|
-
useStylePresets,
|
|
7
|
-
useBoxColors,
|
|
8
|
-
} from './index';
|
|
9
|
-
|
|
10
|
-
export default defineStory({
|
|
11
|
-
title: 'Tools/Mermaid',
|
|
12
|
-
component: Mermaid,
|
|
13
|
-
description: 'Mermaid diagram renderer with declarative type-safe builders.',
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// Diagram Generators
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
|
-
function useFlowDiagram() {
|
|
21
|
-
type Nodes = 'start' | 'check' | 'success' | 'debug' | 'finish';
|
|
22
|
-
const flow = FlowDiagram<Nodes>({ direction: 'TB' });
|
|
23
|
-
const presets = useStylePresets();
|
|
24
|
-
|
|
25
|
-
flow.node('start').rect('Start');
|
|
26
|
-
flow.node('check').rhombus('Is it working?');
|
|
27
|
-
flow.node('success').rect('Great!');
|
|
28
|
-
flow.node('debug').rect('Debug');
|
|
29
|
-
flow.node('finish').stadium('End');
|
|
30
|
-
|
|
31
|
-
flow.edge('start').to('check').solid();
|
|
32
|
-
flow.edge('check').to('success').solid('Yes');
|
|
33
|
-
flow.edge('check').to('debug').solid('No');
|
|
34
|
-
flow.edge('debug').to('check').dotted();
|
|
35
|
-
flow.edge('success').to('finish').solid();
|
|
36
|
-
|
|
37
|
-
flow.style.define('success', presets.success);
|
|
38
|
-
flow.style.define('primary', presets.primary);
|
|
39
|
-
flow.style.apply('success', 'success', 'finish');
|
|
40
|
-
flow.style.apply('primary', 'start');
|
|
41
|
-
|
|
42
|
-
return flow.toString();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function useSequenceDiagram() {
|
|
46
|
-
const boxes = useBoxColors();
|
|
47
|
-
|
|
48
|
-
const { d, rect, alt, loop, blank, toString } = SequenceDiagram({
|
|
49
|
-
User: 'actor',
|
|
50
|
-
App: 'participant',
|
|
51
|
-
Auth: 'participant',
|
|
52
|
-
DB: 'participant',
|
|
53
|
-
}, { autoNumber: true });
|
|
54
|
-
|
|
55
|
-
rect(boxes.primary, () => {
|
|
56
|
-
d.User.sync.App.msg('Enter credentials');
|
|
57
|
-
d.App.sync.Auth.msg('Validate credentials');
|
|
58
|
-
|
|
59
|
-
alt('Valid credentials', () => {
|
|
60
|
-
d.Auth.sync.DB.msg('Get user profile');
|
|
61
|
-
d.DB.syncReply.Auth.msg('Profile data');
|
|
62
|
-
d.Auth.syncReply.App.msg('Auth token');
|
|
63
|
-
d.App.syncReply.User.msg('Welcome!');
|
|
64
|
-
}).else('Invalid credentials', () => {
|
|
65
|
-
d.Auth.syncReply.App.msg('Auth failed');
|
|
66
|
-
d.App.syncReply.User.msg('Error message');
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
blank();
|
|
71
|
-
|
|
72
|
-
loop('Every 15 minutes', () => {
|
|
73
|
-
d.App.async.Auth.msg('Refresh token');
|
|
74
|
-
d.Auth.asyncReply.App.msg('New token');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
return toString();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function useJourneyDiagram() {
|
|
81
|
-
const journey = JourneyDiagram({ title: 'User Onboarding' });
|
|
82
|
-
|
|
83
|
-
journey.section('Discovery')
|
|
84
|
-
.task('Visit landing page', 5, 'User')
|
|
85
|
-
.task('Read features', 4, 'User');
|
|
86
|
-
|
|
87
|
-
journey.section('Sign Up')
|
|
88
|
-
.task('Click Sign Up', 5, 'User')
|
|
89
|
-
.task('Fill form', 2, 'User')
|
|
90
|
-
.task('Verify email', 4, ['User', 'System']);
|
|
91
|
-
|
|
92
|
-
journey.section('Onboarding')
|
|
93
|
-
.task('Complete profile', 3, 'User')
|
|
94
|
-
.task('Create first project', 5, 'User');
|
|
95
|
-
|
|
96
|
-
return journey.toString();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Example using dynamic methods for runtime participant names
|
|
101
|
-
* Useful when participants are not known at compile time
|
|
102
|
-
*/
|
|
103
|
-
function useDynamicSequenceDiagram() {
|
|
104
|
-
const boxes = useBoxColors();
|
|
105
|
-
|
|
106
|
-
// Participants from dynamic data (e.g., from API)
|
|
107
|
-
const participants = ['Alice', 'Bob', 'Charlie'];
|
|
108
|
-
const participantsObj: Record<string, 'participant'> = {};
|
|
109
|
-
participants.forEach(p => { participantsObj[p] = 'participant'; });
|
|
110
|
-
|
|
111
|
-
const seq = SequenceDiagram(participantsObj, { autoNumber: true });
|
|
112
|
-
|
|
113
|
-
// Using dynamic methods - no type assertions needed
|
|
114
|
-
seq.rect(boxes.primary, () => {
|
|
115
|
-
seq.noteOver('Alice', 'Starting conversation');
|
|
116
|
-
seq.message('Alice', 'Bob', 'Hello Bob!');
|
|
117
|
-
seq.message('Bob', 'Alice', 'Hi Alice!', 'syncReply');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
seq.blank();
|
|
121
|
-
|
|
122
|
-
seq.rect(boxes.success, () => {
|
|
123
|
-
seq.noteOverSpan('Alice', 'Charlie', 'Group discussion');
|
|
124
|
-
seq.message('Alice', 'Charlie', 'Hey Charlie!');
|
|
125
|
-
seq.message('Charlie', 'Bob', 'Bob, join us!');
|
|
126
|
-
seq.message('Bob', 'Charlie', 'On my way!', 'async');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
return seq.toString();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function useArchitectureDiagram() {
|
|
133
|
-
type Nodes = 'client' | 'nginx' | 'api1' | 'api2' | 'db' | 'cache' | 'queue';
|
|
134
|
-
const flow = FlowDiagram<Nodes>({ direction: 'TB' });
|
|
135
|
-
const presets = useStylePresets();
|
|
136
|
-
|
|
137
|
-
flow.subgraph('Frontend', (sub) => {
|
|
138
|
-
sub.direction('LR');
|
|
139
|
-
sub.node('client').round('Browser');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
flow.subgraph('Load Balancer', (sub) => {
|
|
143
|
-
sub.node('nginx').hexagon('Nginx');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
flow.subgraph('API Servers', (sub) => {
|
|
147
|
-
sub.direction('LR');
|
|
148
|
-
sub.node('api1').rect('API Server 1');
|
|
149
|
-
sub.node('api2').rect('API Server 2');
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
flow.subgraph('Data Layer', (sub) => {
|
|
153
|
-
sub.direction('LR');
|
|
154
|
-
sub.node('db').cylinder('PostgreSQL');
|
|
155
|
-
sub.node('cache').cylinder('Redis');
|
|
156
|
-
sub.node('queue').cylinder('RabbitMQ');
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
flow.edge('client').to('nginx').solid();
|
|
160
|
-
flow.edge('nginx').to('api1').solid();
|
|
161
|
-
flow.edge('nginx').to('api2').solid();
|
|
162
|
-
flow.edge('api1').to('db').solid();
|
|
163
|
-
flow.edge('api2').to('db').solid();
|
|
164
|
-
flow.edge('api1').to('cache').dotted();
|
|
165
|
-
flow.edge('api2').to('cache').dotted();
|
|
166
|
-
flow.edge('api1').to('queue').dotted();
|
|
167
|
-
|
|
168
|
-
flow.style.define('frontend', presets.info);
|
|
169
|
-
flow.style.define('backend', presets.success);
|
|
170
|
-
flow.style.define('data', presets.warning);
|
|
171
|
-
flow.style.apply('frontend', 'client', 'nginx');
|
|
172
|
-
flow.style.apply('backend', 'api1', 'api2');
|
|
173
|
-
flow.style.apply('data', 'db', 'cache', 'queue');
|
|
174
|
-
|
|
175
|
-
return flow.toString();
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ============================================================================
|
|
179
|
-
// Stories
|
|
180
|
-
// ============================================================================
|
|
181
|
-
|
|
182
|
-
export const Interactive = () => {
|
|
183
|
-
const [diagramType] = useSelect('diagramType', {
|
|
184
|
-
options: ['flow', 'sequence', 'sequenceDynamic', 'journey', 'architecture'] as const,
|
|
185
|
-
defaultValue: 'flow',
|
|
186
|
-
label: 'Diagram Type',
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
const [fullscreen] = useBoolean('fullscreen', {
|
|
190
|
-
defaultValue: false,
|
|
191
|
-
label: 'Enable Fullscreen',
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
const flowChart = useFlowDiagram();
|
|
195
|
-
const sequenceChart = useSequenceDiagram();
|
|
196
|
-
const sequenceDynamicChart = useDynamicSequenceDiagram();
|
|
197
|
-
const journeyChart = useJourneyDiagram();
|
|
198
|
-
const architectureChart = useArchitectureDiagram();
|
|
199
|
-
|
|
200
|
-
const charts = {
|
|
201
|
-
flow: flowChart,
|
|
202
|
-
sequence: sequenceChart,
|
|
203
|
-
sequenceDynamic: sequenceDynamicChart,
|
|
204
|
-
journey: journeyChart,
|
|
205
|
-
architecture: architectureChart,
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const chart = charts[diagramType];
|
|
209
|
-
|
|
210
|
-
return (
|
|
211
|
-
<div className="space-y-4">
|
|
212
|
-
<Mermaid chart={chart} fullscreen={fullscreen} />
|
|
213
|
-
<details className="text-xs">
|
|
214
|
-
<summary className="cursor-pointer text-muted-foreground">Generated Mermaid code</summary>
|
|
215
|
-
<pre className="mt-2 p-3 bg-muted rounded overflow-auto">{chart}</pre>
|
|
216
|
-
</details>
|
|
217
|
-
</div>
|
|
218
|
-
);
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
export const Flow = () => {
|
|
222
|
-
const chart = useFlowDiagram();
|
|
223
|
-
return <Mermaid chart={chart} />;
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
export const Sequence = () => {
|
|
227
|
-
const chart = useSequenceDiagram();
|
|
228
|
-
return <Mermaid chart={chart} />;
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
export const Journey = () => {
|
|
232
|
-
const chart = useJourneyDiagram();
|
|
233
|
-
return <Mermaid chart={chart} />;
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
export const Architecture = () => {
|
|
237
|
-
const chart = useArchitectureDiagram();
|
|
238
|
-
return <Mermaid chart={chart} />;
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
export const WithFullscreen = () => {
|
|
242
|
-
const chart = useFlowDiagram();
|
|
243
|
-
return (
|
|
244
|
-
<div className="space-y-2">
|
|
245
|
-
<p className="text-sm text-muted-foreground">
|
|
246
|
-
Click the expand button to open fullscreen
|
|
247
|
-
</p>
|
|
248
|
-
<Mermaid chart={chart} fullscreen />
|
|
249
|
-
</div>
|
|
250
|
-
);
|
|
251
|
-
};
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { defineStory, useSelect, useValue, useBoolean } from '@djangocfg/playground';
|
|
2
|
-
import { Playground } from './index';
|
|
3
|
-
import { LazyOpenapiViewer } from './lazy';
|
|
4
|
-
import type { PlaygroundConfig } from './types';
|
|
5
|
-
|
|
6
|
-
export default defineStory({
|
|
7
|
-
title: 'Tools/OpenapiViewer',
|
|
8
|
-
component: Playground,
|
|
9
|
-
description:
|
|
10
|
-
'Interactive OpenAPI schema viewer. Sidebar + docs longread with a slide-in two-column playground (Request + Response) when an endpoint is selected.',
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// Schema fixtures
|
|
15
|
-
// ============================================================================
|
|
16
|
-
|
|
17
|
-
// Petstore's own ``servers[0].url`` is just ``/api/v3`` (relative),
|
|
18
|
-
// which makes code samples look like they target ``localhost`` when
|
|
19
|
-
// viewed from the playground. Override with the canonical public host
|
|
20
|
-
// so the generated curl/fetch/etc snippets are runnable as-is.
|
|
21
|
-
const PETSTORE_SCHEMA = {
|
|
22
|
-
id: 'petstore',
|
|
23
|
-
name: 'Petstore API',
|
|
24
|
-
url: 'https://petstore3.swagger.io/api/v3/openapi.json',
|
|
25
|
-
baseUrl: 'https://petstore3.swagger.io/api/v3',
|
|
26
|
-
} as const;
|
|
27
|
-
|
|
28
|
-
const HTTPBIN_SCHEMA = {
|
|
29
|
-
id: 'httpbin',
|
|
30
|
-
name: 'HTTPBin API',
|
|
31
|
-
url: 'https://httpbin.org/spec.json',
|
|
32
|
-
} as const;
|
|
33
|
-
|
|
34
|
-
const MULTI_SCHEMAS = [PETSTORE_SCHEMA, HTTPBIN_SCHEMA] as const;
|
|
35
|
-
|
|
36
|
-
// Simulates a real multi-group setup (like cmdop with 5+ API groups).
|
|
37
|
-
// All clones share the canonical Petstore host so code samples
|
|
38
|
-
// render with a real runnable URL.
|
|
39
|
-
const PETSTORE_BASE = 'https://petstore3.swagger.io/api/v3';
|
|
40
|
-
const MANY_SCHEMAS = [
|
|
41
|
-
PETSTORE_SCHEMA,
|
|
42
|
-
HTTPBIN_SCHEMA,
|
|
43
|
-
{ id: 'machines', name: 'Machines API', url: 'https://petstore3.swagger.io/api/v3/openapi.json', baseUrl: PETSTORE_BASE },
|
|
44
|
-
{ id: 'terminal', name: 'Terminal API', url: 'https://petstore3.swagger.io/api/v3/openapi.json', baseUrl: PETSTORE_BASE },
|
|
45
|
-
{ id: 'skills', name: 'Skills API', url: 'https://petstore3.swagger.io/api/v3/openapi.json', baseUrl: PETSTORE_BASE },
|
|
46
|
-
{ id: 'system', name: 'System API', url: 'https://petstore3.swagger.io/api/v3/openapi.json', baseUrl: PETSTORE_BASE },
|
|
47
|
-
{ id: 'site', name: 'Site API', url: 'https://petstore3.swagger.io/api/v3/openapi.json', baseUrl: PETSTORE_BASE },
|
|
48
|
-
] as const;
|
|
49
|
-
|
|
50
|
-
// ============================================================================
|
|
51
|
-
// Stories
|
|
52
|
-
// ============================================================================
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Default — single-schema mode with the most common configuration knobs
|
|
56
|
-
* exposed as toggles. Replaces the earlier Interactive / SingleSchema /
|
|
57
|
-
* WithBaseUrl / NoDefault / CustomUrl stories, which were all "this
|
|
58
|
-
* component with different props" variations.
|
|
59
|
-
*/
|
|
60
|
-
export const Default = () => {
|
|
61
|
-
const [schemaUrl] = useValue('schemaUrl', {
|
|
62
|
-
defaultValue: PETSTORE_SCHEMA.url,
|
|
63
|
-
label: 'Schema URL',
|
|
64
|
-
description: 'Any OpenAPI 3.x JSON endpoint',
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const [baseUrl] = useValue('baseUrl', {
|
|
68
|
-
defaultValue: '',
|
|
69
|
-
label: 'Base URL override',
|
|
70
|
-
description: 'Empty = use servers[0].url from the schema',
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const [autoSelect] = useBoolean('autoSelectDefault', {
|
|
74
|
-
defaultValue: true,
|
|
75
|
-
label: 'Auto-select default schema',
|
|
76
|
-
description: 'When off, tests the "no defaultSchemaId" code path',
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const config: PlaygroundConfig = {
|
|
80
|
-
schemas: [{ id: 'custom', name: 'Schema', url: schemaUrl }],
|
|
81
|
-
defaultSchemaId: autoSelect ? 'custom' : undefined,
|
|
82
|
-
baseUrl: baseUrl || undefined,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<div className="min-h-[600px]">
|
|
87
|
-
<Playground config={config} />
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* MultiSchema — multiple schemas with the Combobox switcher. Count
|
|
94
|
-
* is adjustable to exercise both the short-list layout and the
|
|
95
|
-
* long-list behaviour (cmdop-style setups with 7+ APIs).
|
|
96
|
-
*/
|
|
97
|
-
export const MultiSchema = () => {
|
|
98
|
-
const [count] = useSelect('count', {
|
|
99
|
-
options: ['2', '7'] as const,
|
|
100
|
-
defaultValue: '2',
|
|
101
|
-
label: 'Schema count',
|
|
102
|
-
description: '2 uses Petstore+HTTPBin; 7 stress-tests the Combobox',
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const config: PlaygroundConfig = {
|
|
106
|
-
schemas: count === '2' ? [...MULTI_SCHEMAS] : [...MANY_SCHEMAS],
|
|
107
|
-
defaultSchemaId: 'petstore',
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
return (
|
|
111
|
-
<div className="min-h-[600px]">
|
|
112
|
-
<Playground config={config} />
|
|
113
|
-
</div>
|
|
114
|
-
);
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* SectionsGrouping — alternate layout: every schema's endpoints get
|
|
119
|
-
* rendered on the same page as top-level sections, with a sidebar
|
|
120
|
-
* that stacks them. URL hash sync writes ``#<schemaId>/<anchor>``
|
|
121
|
-
* as the user scrolls so deep-linking keeps working.
|
|
122
|
-
*/
|
|
123
|
-
export const SectionsGrouping = () => {
|
|
124
|
-
const [urlSync] = useBoolean('urlSync', {
|
|
125
|
-
defaultValue: true,
|
|
126
|
-
label: 'URL hash sync',
|
|
127
|
-
description: 'Writes #<schemaId>/<anchor> as you scroll',
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const config: PlaygroundConfig = {
|
|
131
|
-
schemas: [...MULTI_SCHEMAS],
|
|
132
|
-
defaultSchemaId: 'petstore',
|
|
133
|
-
schemaGrouping: 'sections',
|
|
134
|
-
urlSync,
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<div className="min-h-[600px]">
|
|
139
|
-
<Playground config={config} />
|
|
140
|
-
</div>
|
|
141
|
-
);
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Errors — consolidated failure paths. Pick a scenario via the toggle:
|
|
146
|
-
* - ``broken``: single schema that 404s → full-panel error state.
|
|
147
|
-
* - ``mixed``: valid + broken side-by-side → tests switching between.
|
|
148
|
-
* - ``empty``: no schemas configured at all → empty-state UI.
|
|
149
|
-
*/
|
|
150
|
-
export const Errors = () => {
|
|
151
|
-
const [scenario] = useSelect('scenario', {
|
|
152
|
-
options: ['broken', 'mixed', 'empty'] as const,
|
|
153
|
-
defaultValue: 'broken',
|
|
154
|
-
label: 'Failure scenario',
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
const config: PlaygroundConfig = (() => {
|
|
158
|
-
switch (scenario) {
|
|
159
|
-
case 'broken':
|
|
160
|
-
return {
|
|
161
|
-
schemas: [{ id: 'broken', name: 'Broken API', url: 'https://httpbin.org/status/404' }],
|
|
162
|
-
defaultSchemaId: 'broken',
|
|
163
|
-
};
|
|
164
|
-
case 'mixed':
|
|
165
|
-
return {
|
|
166
|
-
schemas: [
|
|
167
|
-
PETSTORE_SCHEMA,
|
|
168
|
-
{ id: 'broken', name: 'Broken API (404)', url: 'https://httpbin.org/status/404' },
|
|
169
|
-
],
|
|
170
|
-
defaultSchemaId: 'petstore',
|
|
171
|
-
};
|
|
172
|
-
case 'empty':
|
|
173
|
-
return { schemas: [] };
|
|
174
|
-
}
|
|
175
|
-
})();
|
|
176
|
-
|
|
177
|
-
return (
|
|
178
|
-
<div className="min-h-[600px]">
|
|
179
|
-
<Playground config={config} />
|
|
180
|
-
</div>
|
|
181
|
-
);
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* UnfilledPathParams — regression guard for the ``%7Bid%7D`` bug.
|
|
186
|
-
*
|
|
187
|
-
* When a path template like ``/pet/{petId}`` is rendered before the
|
|
188
|
-
* user types a value, the path stays a template — not a percent-
|
|
189
|
-
* encoded string. Previously the URL parser turned ``{`` / ``}`` into
|
|
190
|
-
* ``%7B`` / ``%7D``, and the same encoded form leaked into every
|
|
191
|
-
* generated code sample (curl, axios, python, …) via the HAR
|
|
192
|
-
* intermediate.
|
|
193
|
-
*
|
|
194
|
-
* To verify visually:
|
|
195
|
-
* 1. Open any endpoint with a path param, e.g. ``GET /pet/{petId}``,
|
|
196
|
-
* ``GET /store/order/{orderId}``, or ``GET /user/{username}``.
|
|
197
|
-
* 2. Without filling the parameter, look at the header path and
|
|
198
|
-
* every Code Samples tab.
|
|
199
|
-
* 3. All occurrences must read ``{petId}``, never ``%7BpetId%7D``.
|
|
200
|
-
*/
|
|
201
|
-
export const UnfilledPathParams = () => {
|
|
202
|
-
const config: PlaygroundConfig = {
|
|
203
|
-
schemas: [PETSTORE_SCHEMA],
|
|
204
|
-
defaultSchemaId: 'petstore',
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
return (
|
|
208
|
-
<div className="min-h-[600px]">
|
|
209
|
-
<Playground config={config} />
|
|
210
|
-
</div>
|
|
211
|
-
);
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Lazy — production lazy-loaded variant. Shows the loading spinner
|
|
216
|
-
* while the ~400 KB OpenAPI viewer chunk is fetched. Only path you
|
|
217
|
-
* should use in an actual app bundle.
|
|
218
|
-
*/
|
|
219
|
-
export const Lazy = () => {
|
|
220
|
-
const config: PlaygroundConfig = {
|
|
221
|
-
schemas: [...MULTI_SCHEMAS],
|
|
222
|
-
defaultSchemaId: 'petstore',
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
<div className="min-h-[600px]">
|
|
227
|
-
<LazyOpenapiViewer config={config} />
|
|
228
|
-
</div>
|
|
229
|
-
);
|
|
230
|
-
};
|