@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.
Files changed (62) hide show
  1. package/dist/DictationField-U25MEYAL.mjs +4 -0
  2. package/dist/{DictationField-2ZLQWLYV.mjs.map → DictationField-U25MEYAL.mjs.map} +1 -1
  3. package/dist/DictationField-XWR5VOID.cjs +13 -0
  4. package/dist/{DictationField-IPPJ54CU.cjs.map → DictationField-XWR5VOID.cjs.map} +1 -1
  5. package/dist/{chunk-KMSBGNVC.cjs → chunk-4PFW7MIJ.cjs} +4 -2
  6. package/dist/chunk-4PFW7MIJ.cjs.map +1 -0
  7. package/dist/{chunk-4LXG3NBV.mjs → chunk-C2YN6WEO.mjs} +3 -3
  8. package/dist/chunk-C2YN6WEO.mjs.map +1 -0
  9. package/dist/index.cjs +139 -2
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +68 -1
  12. package/dist/index.d.ts +68 -1
  13. package/dist/index.mjs +141 -6
  14. package/dist/index.mjs.map +1 -1
  15. package/package.json +6 -13
  16. package/src/tools/Chat/index.ts +15 -0
  17. package/dist/DictationField-2ZLQWLYV.mjs +0 -4
  18. package/dist/DictationField-IPPJ54CU.cjs +0 -13
  19. package/dist/chunk-4LXG3NBV.mjs.map +0 -1
  20. package/dist/chunk-KMSBGNVC.cjs.map +0 -1
  21. package/src/components/markdown/MarkdownMessage/MarkdownMessage.story.tsx +0 -771
  22. package/src/stories/index.ts +0 -63
  23. package/src/tools/AudioPlayer/AudioPlayer.story.tsx +0 -481
  24. package/src/tools/Chat/stories/01-basic.story.tsx +0 -64
  25. package/src/tools/Chat/stories/02-bubbles.story.tsx +0 -21
  26. package/src/tools/Chat/stories/03-tool-calls.story.tsx +0 -59
  27. package/src/tools/Chat/stories/04-personas.story.tsx +0 -78
  28. package/src/tools/Chat/stories/05-launcher.story.tsx +0 -321
  29. package/src/tools/Chat/stories/06-header.story.tsx +0 -147
  30. package/src/tools/Chat/stories/07-audio-actions.story.tsx +0 -112
  31. package/src/tools/Chat/stories/shared/Frame.tsx +0 -21
  32. package/src/tools/Chat/stories/shared/index.ts +0 -5
  33. package/src/tools/Chat/stories/shared/messages.ts +0 -39
  34. package/src/tools/Chat/stories/shared/personas.ts +0 -13
  35. package/src/tools/Chat/stories/shared/seeds.ts +0 -92
  36. package/src/tools/Chat/stories/shared/transports.ts +0 -36
  37. package/src/tools/CodeEditor/CodeEditor.story.tsx +0 -202
  38. package/src/tools/CronScheduler/CronScheduler.story.tsx +0 -300
  39. package/src/tools/Gallery/Gallery.story.tsx +0 -237
  40. package/src/tools/ImageViewer/ImageViewer.story.tsx +0 -85
  41. package/src/tools/JsonForm/JsonForm.story.tsx +0 -350
  42. package/src/tools/JsonTree/JsonTree.story.tsx +0 -141
  43. package/src/tools/LottiePlayer/LottiePlayer.story.tsx +0 -95
  44. package/src/tools/Map/Map.story.tsx +0 -458
  45. package/src/tools/MarkdownEditor/MarkdownEditor.story.tsx +0 -225
  46. package/src/tools/Mermaid/Mermaid.story.tsx +0 -251
  47. package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +0 -230
  48. package/src/tools/PrettyCode/PrettyCode.story.tsx +0 -304
  49. package/src/tools/SpeechRecognition/stories/01-basic.story.tsx +0 -32
  50. package/src/tools/SpeechRecognition/stories/02-dictation-field.story.tsx +0 -32
  51. package/src/tools/SpeechRecognition/stories/03-push-to-talk.story.tsx +0 -27
  52. package/src/tools/SpeechRecognition/stories/04-mic-meter.story.tsx +0 -35
  53. package/src/tools/SpeechRecognition/stories/05-custom-engine-http.story.tsx +0 -40
  54. package/src/tools/SpeechRecognition/stories/06-custom-engine-ws.story.tsx +0 -48
  55. package/src/tools/SpeechRecognition/stories/07-language-device.story.tsx +0 -57
  56. package/src/tools/SpeechRecognition/stories/08-errors-permissions.story.tsx +0 -25
  57. package/src/tools/SpeechRecognition/stories/09-chat-voice.story.tsx +0 -90
  58. package/src/tools/SpeechRecognition/stories/shared.tsx +0 -123
  59. package/src/tools/Tour/Tour.story.tsx +0 -279
  60. package/src/tools/Tree/Tree.story.tsx +0 -620
  61. package/src/tools/Uploader/Uploader.story.tsx +0 -415
  62. 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
- };