@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,237 +0,0 @@
|
|
|
1
|
-
import { defineStory, useBoolean, useSelect, useNumber } from '@djangocfg/playground';
|
|
2
|
-
import { Gallery, GalleryCompact, type GalleryMediaItem, type GalleryAspectRatio } from './index';
|
|
3
|
-
|
|
4
|
-
// Sample images for testing
|
|
5
|
-
const SAMPLE_IMAGES: GalleryMediaItem[] = [
|
|
6
|
-
{
|
|
7
|
-
id: '1',
|
|
8
|
-
src: 'https://images.unsplash.com/photo-1494976388531-d1058494cdd8?w=800',
|
|
9
|
-
thumbnail: 'https://images.unsplash.com/photo-1494976388531-d1058494cdd8?w=200',
|
|
10
|
-
alt: 'Classic car',
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
id: '2',
|
|
14
|
-
src: 'https://images.unsplash.com/photo-1503376780353-7e6692767b70?w=800',
|
|
15
|
-
thumbnail: 'https://images.unsplash.com/photo-1503376780353-7e6692767b70?w=200',
|
|
16
|
-
alt: 'Porsche',
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
id: '3',
|
|
20
|
-
src: 'https://images.unsplash.com/photo-1542362567-b07e54358753?w=800',
|
|
21
|
-
thumbnail: 'https://images.unsplash.com/photo-1542362567-b07e54358753?w=200',
|
|
22
|
-
alt: 'BMW',
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: '4',
|
|
26
|
-
src: 'https://images.unsplash.com/photo-1555215695-3004980ad54e?w=800',
|
|
27
|
-
thumbnail: 'https://images.unsplash.com/photo-1555215695-3004980ad54e?w=200',
|
|
28
|
-
alt: 'Mercedes',
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
id: '5',
|
|
32
|
-
src: 'https://images.unsplash.com/photo-1617531653332-bd46c24f2068?w=800',
|
|
33
|
-
thumbnail: 'https://images.unsplash.com/photo-1617531653332-bd46c24f2068?w=200',
|
|
34
|
-
alt: 'Tesla',
|
|
35
|
-
},
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
export default defineStory({
|
|
39
|
-
title: 'Tools/Gallery',
|
|
40
|
-
component: Gallery,
|
|
41
|
-
description: 'Full-featured gallery with lightbox, thumbnails, and keyboard navigation.',
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
export const CarouselMode = () => (
|
|
45
|
-
<div className="max-w-4xl">
|
|
46
|
-
<Gallery
|
|
47
|
-
images={SAMPLE_IMAGES}
|
|
48
|
-
previewMode="carousel"
|
|
49
|
-
showThumbnails
|
|
50
|
-
showControls
|
|
51
|
-
showCounter
|
|
52
|
-
enableLightbox
|
|
53
|
-
enableKeyboard
|
|
54
|
-
aspectRatio={16 / 9}
|
|
55
|
-
/>
|
|
56
|
-
</div>
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
export const GridMode = () => (
|
|
60
|
-
<div className="max-w-4xl">
|
|
61
|
-
<Gallery
|
|
62
|
-
images={SAMPLE_IMAGES}
|
|
63
|
-
previewMode="grid"
|
|
64
|
-
previewCount={5}
|
|
65
|
-
enableLightbox
|
|
66
|
-
aspectRatio={16 / 9}
|
|
67
|
-
/>
|
|
68
|
-
</div>
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
export const WithoutThumbnails = () => (
|
|
72
|
-
<div className="max-w-4xl">
|
|
73
|
-
<Gallery
|
|
74
|
-
images={SAMPLE_IMAGES}
|
|
75
|
-
previewMode="carousel"
|
|
76
|
-
showThumbnails={false}
|
|
77
|
-
showControls
|
|
78
|
-
enableLightbox
|
|
79
|
-
aspectRatio={16 / 9}
|
|
80
|
-
/>
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
export const Compact = {
|
|
85
|
-
render: (args: {
|
|
86
|
-
showDots: boolean;
|
|
87
|
-
showArrows: boolean;
|
|
88
|
-
enableZoom: boolean;
|
|
89
|
-
showCounter: boolean;
|
|
90
|
-
}) => (
|
|
91
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
92
|
-
<div className="rounded-xl border border-border overflow-hidden">
|
|
93
|
-
<div className="aspect-[4/3]">
|
|
94
|
-
<GalleryCompact
|
|
95
|
-
images={SAMPLE_IMAGES}
|
|
96
|
-
showDots={args.showDots}
|
|
97
|
-
showArrows={args.showArrows}
|
|
98
|
-
enableZoom={args.enableZoom}
|
|
99
|
-
showCounter={args.showCounter}
|
|
100
|
-
/>
|
|
101
|
-
</div>
|
|
102
|
-
<div className="p-4">
|
|
103
|
-
<h3 className="font-medium">Vehicle Title</h3>
|
|
104
|
-
<p className="text-sm text-muted-foreground">$25,000</p>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
</div>
|
|
108
|
-
),
|
|
109
|
-
args: {
|
|
110
|
-
showDots: true,
|
|
111
|
-
showArrows: true,
|
|
112
|
-
enableZoom: true,
|
|
113
|
-
showCounter: false,
|
|
114
|
-
},
|
|
115
|
-
argTypes: {
|
|
116
|
-
showDots: { control: 'boolean', description: 'Show navigation dots' },
|
|
117
|
-
showArrows: { control: 'boolean', description: 'Show arrow buttons' },
|
|
118
|
-
enableZoom: { control: 'boolean', description: 'Enable zoom on hover' },
|
|
119
|
-
showCounter: { control: 'boolean', description: 'Show image counter' },
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
export const SingleImage = () => (
|
|
124
|
-
<div className="w-64">
|
|
125
|
-
<div className="rounded-xl border border-border overflow-hidden">
|
|
126
|
-
<div className="aspect-[4/3]">
|
|
127
|
-
<GalleryCompact images={SAMPLE_IMAGES.slice(0, 1)} enableZoom />
|
|
128
|
-
</div>
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
export const EmptyState = () => (
|
|
134
|
-
<div className="w-64">
|
|
135
|
-
<div className="rounded-xl border border-border overflow-hidden">
|
|
136
|
-
<div className="aspect-[4/3]">
|
|
137
|
-
<GalleryCompact images={[]} />
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
</div>
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Interactive story using fixture hooks
|
|
145
|
-
* Controls appear automatically in the right sidebar
|
|
146
|
-
*/
|
|
147
|
-
export const Interactive = () => {
|
|
148
|
-
// These hooks auto-register controls in the Properties panel
|
|
149
|
-
const [showDots] = useBoolean('showDots', {
|
|
150
|
-
defaultValue: true,
|
|
151
|
-
label: 'Show Dots',
|
|
152
|
-
description: 'Display navigation dots at the bottom',
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
const [showArrows] = useBoolean('showArrows', {
|
|
156
|
-
defaultValue: true,
|
|
157
|
-
label: 'Show Arrows',
|
|
158
|
-
description: 'Display navigation arrows on hover',
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const [enableZoom] = useBoolean('enableZoom', {
|
|
162
|
-
defaultValue: true,
|
|
163
|
-
label: 'Enable Zoom',
|
|
164
|
-
description: 'Zoom effect on image hover',
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
const [showCounter] = useBoolean('showCounter', {
|
|
168
|
-
defaultValue: false,
|
|
169
|
-
label: 'Show Counter',
|
|
170
|
-
description: 'Display image counter badge',
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
const [maxDots] = useNumber('maxDots', {
|
|
174
|
-
defaultValue: 5,
|
|
175
|
-
min: 1,
|
|
176
|
-
max: 10,
|
|
177
|
-
label: 'Max Dots',
|
|
178
|
-
description: 'Maximum number of dots to display',
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const [previewMode] = useSelect('previewMode', {
|
|
182
|
-
options: ['carousel', 'grid'] as const,
|
|
183
|
-
defaultValue: 'carousel',
|
|
184
|
-
label: 'Preview Mode',
|
|
185
|
-
description: 'Gallery preview display mode',
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const [aspectRatio] = useSelect('aspectRatio', {
|
|
189
|
-
options: ['auto', '16/9', '4/3', '3/2', '1/1'] as const,
|
|
190
|
-
defaultValue: '4/3' as GalleryAspectRatio,
|
|
191
|
-
label: 'Aspect Ratio',
|
|
192
|
-
description: 'Image container aspect ratio',
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
return (
|
|
196
|
-
<div className="space-y-8">
|
|
197
|
-
{/* GalleryCompact */}
|
|
198
|
-
<div>
|
|
199
|
-
<h3 className="text-lg font-semibold mb-4">GalleryCompact</h3>
|
|
200
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
201
|
-
<div className="rounded-xl border border-border overflow-hidden bg-black">
|
|
202
|
-
<GalleryCompact
|
|
203
|
-
images={SAMPLE_IMAGES}
|
|
204
|
-
aspectRatio={aspectRatio}
|
|
205
|
-
showDots={showDots}
|
|
206
|
-
showArrows={showArrows}
|
|
207
|
-
enableZoom={enableZoom}
|
|
208
|
-
showCounter={showCounter}
|
|
209
|
-
maxDots={maxDots}
|
|
210
|
-
/>
|
|
211
|
-
<div className="p-4">
|
|
212
|
-
<h3 className="font-medium">Interactive Card</h3>
|
|
213
|
-
<p className="text-sm text-muted-foreground">Use controls on the right →</p>
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
</div>
|
|
217
|
-
</div>
|
|
218
|
-
|
|
219
|
-
{/* Full Gallery */}
|
|
220
|
-
<div>
|
|
221
|
-
<h3 className="text-lg font-semibold mb-4">Full Gallery ({previewMode})</h3>
|
|
222
|
-
<div className="max-w-4xl">
|
|
223
|
-
<Gallery
|
|
224
|
-
images={SAMPLE_IMAGES}
|
|
225
|
-
previewMode={previewMode}
|
|
226
|
-
showThumbnails
|
|
227
|
-
showControls
|
|
228
|
-
showCounter={showCounter}
|
|
229
|
-
enableLightbox
|
|
230
|
-
enableKeyboard
|
|
231
|
-
aspectRatio={16 / 9}
|
|
232
|
-
/>
|
|
233
|
-
</div>
|
|
234
|
-
</div>
|
|
235
|
-
</div>
|
|
236
|
-
);
|
|
237
|
-
};
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { defineStory, useSelect, useBoolean, useNumber } from '@djangocfg/playground';
|
|
2
|
-
import { ImageViewer } from './index';
|
|
3
|
-
|
|
4
|
-
export default defineStory({
|
|
5
|
-
title: 'Tools/ImageViewer',
|
|
6
|
-
component: ImageViewer,
|
|
7
|
-
description: 'Image viewer with zoom, pan, rotate, flip and gallery navigation.',
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
const IMAGES = [
|
|
11
|
-
{
|
|
12
|
-
file: { name: 'Mountain landscape', path: 'https://picsum.photos/seed/mountain/1200/800' },
|
|
13
|
-
src: 'https://picsum.photos/seed/mountain/1200/800',
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
file: { name: 'City skyline', path: 'https://picsum.photos/seed/city/1200/800' },
|
|
17
|
-
src: 'https://picsum.photos/seed/city/1200/800',
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
file: { name: 'Forest path', path: 'https://picsum.photos/seed/forest/1200/800' },
|
|
21
|
-
src: 'https://picsum.photos/seed/forest/1200/800',
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
file: { name: 'Ocean sunset', path: 'https://picsum.photos/seed/ocean/1200/800' },
|
|
25
|
-
src: 'https://picsum.photos/seed/ocean/1200/800',
|
|
26
|
-
},
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
export const Interactive = () => {
|
|
30
|
-
const [count] = useNumber('imageCount', {
|
|
31
|
-
defaultValue: 4,
|
|
32
|
-
min: 1,
|
|
33
|
-
max: 4,
|
|
34
|
-
label: 'Number of images',
|
|
35
|
-
description: 'How many images to show in gallery',
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const [initialIndex] = useNumber('initialIndex', {
|
|
39
|
-
defaultValue: 0,
|
|
40
|
-
min: 0,
|
|
41
|
-
max: 3,
|
|
42
|
-
label: 'Initial index',
|
|
43
|
-
description: 'Which image to open first',
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const [inDialog] = useBoolean('inDialog', {
|
|
47
|
-
defaultValue: false,
|
|
48
|
-
label: 'inDialog',
|
|
49
|
-
description: 'Hide the fullscreen expand button',
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<div className="w-full h-[500px]">
|
|
54
|
-
<ImageViewer
|
|
55
|
-
images={IMAGES.slice(0, count)}
|
|
56
|
-
initialIndex={Math.min(initialIndex, count - 1)}
|
|
57
|
-
inDialog={inDialog}
|
|
58
|
-
/>
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export const SingleImage = () => (
|
|
64
|
-
<div className="w-full h-[500px]">
|
|
65
|
-
<ImageViewer images={[IMAGES[0]]} />
|
|
66
|
-
</div>
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
export const Gallery = () => (
|
|
70
|
-
<div className="w-full h-[500px]">
|
|
71
|
-
<ImageViewer images={IMAGES} initialIndex={0} />
|
|
72
|
-
</div>
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
export const OpenAtIndex = () => (
|
|
76
|
-
<div className="w-full h-[500px]">
|
|
77
|
-
<ImageViewer images={IMAGES} initialIndex={2} />
|
|
78
|
-
</div>
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
export const InDialog = () => (
|
|
82
|
-
<div className="w-full h-[500px]">
|
|
83
|
-
<ImageViewer images={IMAGES} inDialog />
|
|
84
|
-
</div>
|
|
85
|
-
);
|
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
import { defineStory, useSelect, useBoolean } from '@djangocfg/playground';
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import { JsonSchemaForm } from './index';
|
|
4
|
-
|
|
5
|
-
export default defineStory({
|
|
6
|
-
title: 'Tools/Json Schema Form',
|
|
7
|
-
component: JsonSchemaForm,
|
|
8
|
-
description:
|
|
9
|
-
'Dynamic form generator from JSON Schema using react-jsonschema-form. Supports density (compact), conditional disable via `ui:disabledWhen`, and collapsible sub-sections via `ui:groups`.',
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const SCHEMAS = {
|
|
13
|
-
simple: {
|
|
14
|
-
schema: {
|
|
15
|
-
type: 'object' as const,
|
|
16
|
-
required: ['name', 'email'],
|
|
17
|
-
properties: {
|
|
18
|
-
name: { type: 'string' as const, title: 'Name' },
|
|
19
|
-
email: { type: 'string' as const, title: 'Email', format: 'email' },
|
|
20
|
-
age: { type: 'integer' as const, title: 'Age', minimum: 0, maximum: 120 },
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
uiSchema: {},
|
|
24
|
-
},
|
|
25
|
-
vehicle: {
|
|
26
|
-
schema: {
|
|
27
|
-
type: 'object' as const,
|
|
28
|
-
required: ['make', 'model', 'year'],
|
|
29
|
-
properties: {
|
|
30
|
-
make: {
|
|
31
|
-
type: 'string' as const,
|
|
32
|
-
title: 'Make',
|
|
33
|
-
enum: ['BMW', 'Mercedes', 'Audi', 'Porsche', 'Tesla'],
|
|
34
|
-
},
|
|
35
|
-
model: { type: 'string' as const, title: 'Model' },
|
|
36
|
-
year: { type: 'integer' as const, title: 'Year', minimum: 1990, maximum: 2025 },
|
|
37
|
-
price: { type: 'number' as const, title: 'Price ($)' },
|
|
38
|
-
features: {
|
|
39
|
-
type: 'array' as const,
|
|
40
|
-
title: 'Features',
|
|
41
|
-
items: {
|
|
42
|
-
type: 'string' as const,
|
|
43
|
-
enum: ['Leather', 'Navigation', 'Sunroof', 'Heated Seats', 'Parking Sensors'],
|
|
44
|
-
},
|
|
45
|
-
uniqueItems: true,
|
|
46
|
-
},
|
|
47
|
-
description: { type: 'string' as const, title: 'Description' },
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
uiSchema: {
|
|
51
|
-
description: { 'ui:widget': 'textarea' },
|
|
52
|
-
features: { 'ui:widget': 'checkboxes' },
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
contact: {
|
|
56
|
-
schema: {
|
|
57
|
-
type: 'object' as const,
|
|
58
|
-
required: ['firstName', 'lastName', 'email'],
|
|
59
|
-
properties: {
|
|
60
|
-
firstName: { type: 'string' as const, title: 'First Name' },
|
|
61
|
-
lastName: { type: 'string' as const, title: 'Last Name' },
|
|
62
|
-
email: { type: 'string' as const, title: 'Email', format: 'email' },
|
|
63
|
-
phone: { type: 'string' as const, title: 'Phone' },
|
|
64
|
-
message: { type: 'string' as const, title: 'Message' },
|
|
65
|
-
subscribe: { type: 'boolean' as const, title: 'Subscribe to newsletter' },
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
uiSchema: {
|
|
69
|
-
message: { 'ui:widget': 'textarea' },
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
export const Interactive = () => {
|
|
75
|
-
const [schemaType] = useSelect('schemaType', {
|
|
76
|
-
options: ['simple', 'vehicle', 'contact'] as const,
|
|
77
|
-
defaultValue: 'vehicle',
|
|
78
|
-
label: 'Schema Type',
|
|
79
|
-
description: 'Select form schema',
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const [density] = useSelect('density', {
|
|
83
|
-
options: ['comfortable', 'compact'] as const,
|
|
84
|
-
defaultValue: 'comfortable',
|
|
85
|
-
label: 'Density',
|
|
86
|
-
description: '`compact` shrinks rows and moves description into label tooltip.',
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const [liveValidate] = useBoolean('liveValidate', {
|
|
90
|
-
defaultValue: false,
|
|
91
|
-
label: 'Live Validate',
|
|
92
|
-
description: 'Validate on every change',
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const config = SCHEMAS[schemaType];
|
|
96
|
-
|
|
97
|
-
return (
|
|
98
|
-
<div className="max-w-lg">
|
|
99
|
-
<JsonSchemaForm
|
|
100
|
-
schema={config.schema}
|
|
101
|
-
uiSchema={config.uiSchema}
|
|
102
|
-
liveValidate={liveValidate}
|
|
103
|
-
density={density}
|
|
104
|
-
onSubmit={(data) => console.log('Submitted:', data.formData)}
|
|
105
|
-
/>
|
|
106
|
-
</div>
|
|
107
|
-
);
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
export const SimpleForm = () => (
|
|
111
|
-
<div className="max-w-md">
|
|
112
|
-
<JsonSchemaForm
|
|
113
|
-
schema={SCHEMAS.simple.schema}
|
|
114
|
-
onSubmit={(data) => console.log('Submitted:', data.formData)}
|
|
115
|
-
/>
|
|
116
|
-
</div>
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
export const VehicleForm = () => (
|
|
120
|
-
<div className="max-w-lg">
|
|
121
|
-
<JsonSchemaForm
|
|
122
|
-
schema={SCHEMAS.vehicle.schema}
|
|
123
|
-
uiSchema={SCHEMAS.vehicle.uiSchema}
|
|
124
|
-
onSubmit={(data) => console.log('Submitted:', data.formData)}
|
|
125
|
-
/>
|
|
126
|
-
</div>
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
export const WithDefaultValues = () => (
|
|
130
|
-
<div className="max-w-lg">
|
|
131
|
-
<JsonSchemaForm
|
|
132
|
-
schema={SCHEMAS.vehicle.schema}
|
|
133
|
-
uiSchema={SCHEMAS.vehicle.uiSchema}
|
|
134
|
-
formData={{
|
|
135
|
-
make: 'BMW',
|
|
136
|
-
model: 'X5',
|
|
137
|
-
year: 2023,
|
|
138
|
-
price: 65000,
|
|
139
|
-
features: ['Leather', 'Navigation'],
|
|
140
|
-
}}
|
|
141
|
-
onSubmit={(data) => console.log('Submitted:', data.formData)}
|
|
142
|
-
/>
|
|
143
|
-
</div>
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Compact density — shrinks rows, moves description into a tooltip on the
|
|
148
|
-
* label. Pair with `ui:groups` for dense sidebar / playground configurators.
|
|
149
|
-
*/
|
|
150
|
-
export const Compact = () => (
|
|
151
|
-
<div className="max-w-md">
|
|
152
|
-
<JsonSchemaForm
|
|
153
|
-
schema={SCHEMAS.simple.schema}
|
|
154
|
-
density="compact"
|
|
155
|
-
showSubmitButton={false}
|
|
156
|
-
onSubmit={(data) => console.log('Submitted:', data.formData)}
|
|
157
|
-
/>
|
|
158
|
-
</div>
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* `ui:disabledWhen` greys out a field based on another field's value.
|
|
163
|
-
* Here `phone` is disabled until `subscribe` is checked, and `email` is
|
|
164
|
-
* disabled when `firstName` is empty.
|
|
165
|
-
*/
|
|
166
|
-
export const ConditionalDisable = () => {
|
|
167
|
-
const [data, setData] = useState<Record<string, unknown>>({
|
|
168
|
-
firstName: '',
|
|
169
|
-
subscribe: false,
|
|
170
|
-
});
|
|
171
|
-
return (
|
|
172
|
-
<div className="max-w-lg">
|
|
173
|
-
<JsonSchemaForm
|
|
174
|
-
schema={SCHEMAS.contact.schema}
|
|
175
|
-
uiSchema={{
|
|
176
|
-
...SCHEMAS.contact.uiSchema,
|
|
177
|
-
email: {
|
|
178
|
-
'ui:disabledWhen': { path: 'firstName', falsy: true },
|
|
179
|
-
},
|
|
180
|
-
phone: {
|
|
181
|
-
'ui:disabledWhen': { path: 'subscribe', falsy: true },
|
|
182
|
-
},
|
|
183
|
-
subscribe: { 'ui:widget': 'switch' },
|
|
184
|
-
}}
|
|
185
|
-
formData={data}
|
|
186
|
-
onChange={(e) => setData(e.formData ?? {})}
|
|
187
|
-
showSubmitButton={false}
|
|
188
|
-
/>
|
|
189
|
-
<pre className="mt-3 text-[11px] text-muted-foreground">
|
|
190
|
-
{JSON.stringify(data, null, 2)}
|
|
191
|
-
</pre>
|
|
192
|
-
</div>
|
|
193
|
-
);
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* `ui:groups` splits a flat object into collapsible sub-sections without
|
|
198
|
-
* restructuring the schema. Fields not listed in any group render flat above
|
|
199
|
-
* the groups.
|
|
200
|
-
*/
|
|
201
|
-
export const CollapsibleGroups = () => (
|
|
202
|
-
<div className="max-w-lg">
|
|
203
|
-
<JsonSchemaForm
|
|
204
|
-
schema={{
|
|
205
|
-
type: 'object',
|
|
206
|
-
properties: {
|
|
207
|
-
identity: { type: 'string', title: 'Display name' },
|
|
208
|
-
firstName: { type: 'string', title: 'First name' },
|
|
209
|
-
lastName: { type: 'string', title: 'Last name' },
|
|
210
|
-
email: { type: 'string', title: 'Email', format: 'email' },
|
|
211
|
-
phone: { type: 'string', title: 'Phone' },
|
|
212
|
-
newsletter: { type: 'boolean', title: 'Newsletter' },
|
|
213
|
-
marketing: { type: 'boolean', title: 'Marketing emails' },
|
|
214
|
-
tracking: { type: 'boolean', title: 'Usage analytics' },
|
|
215
|
-
},
|
|
216
|
-
}}
|
|
217
|
-
uiSchema={{
|
|
218
|
-
'ui:groups': [
|
|
219
|
-
{
|
|
220
|
-
title: 'Personal',
|
|
221
|
-
fields: ['firstName', 'lastName'],
|
|
222
|
-
defaultOpen: true,
|
|
223
|
-
},
|
|
224
|
-
{ title: 'Contact', fields: ['email', 'phone'], defaultOpen: false },
|
|
225
|
-
{
|
|
226
|
-
title: 'Preferences',
|
|
227
|
-
fields: ['newsletter', 'marketing', 'tracking'],
|
|
228
|
-
defaultOpen: false,
|
|
229
|
-
},
|
|
230
|
-
],
|
|
231
|
-
newsletter: { 'ui:widget': 'switch' },
|
|
232
|
-
marketing: { 'ui:widget': 'switch' },
|
|
233
|
-
tracking: { 'ui:widget': 'switch' },
|
|
234
|
-
}}
|
|
235
|
-
density="compact"
|
|
236
|
-
showSubmitButton={false}
|
|
237
|
-
onChange={(e) => console.log('changed:', e.formData)}
|
|
238
|
-
/>
|
|
239
|
-
</div>
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Empty-string enum values are now allowed — Radix `<Select.Item value="" />`
|
|
244
|
-
* is reserved, so the ui-core `Select` wrapper substitutes a sentinel
|
|
245
|
-
* internally. From the schema's perspective `''` is just another option.
|
|
246
|
-
*/
|
|
247
|
-
export const EmptyStringSelect = () => {
|
|
248
|
-
const [data, setData] = useState<Record<string, unknown>>({ plan: '' });
|
|
249
|
-
return (
|
|
250
|
-
<div className="max-w-md">
|
|
251
|
-
<JsonSchemaForm
|
|
252
|
-
schema={{
|
|
253
|
-
type: 'object',
|
|
254
|
-
properties: {
|
|
255
|
-
plan: {
|
|
256
|
-
type: 'string',
|
|
257
|
-
title: 'Plan',
|
|
258
|
-
enum: ['', 'Free', 'Pro', 'Max', 'Enterprise'],
|
|
259
|
-
description: 'An empty string is a valid enum value here.',
|
|
260
|
-
},
|
|
261
|
-
},
|
|
262
|
-
}}
|
|
263
|
-
uiSchema={{
|
|
264
|
-
plan: {
|
|
265
|
-
'ui:enumNames': ['— none —', 'Free', 'Pro', 'Max', 'Enterprise'],
|
|
266
|
-
},
|
|
267
|
-
}}
|
|
268
|
-
formData={data}
|
|
269
|
-
onChange={(e) => setData(e.formData ?? {})}
|
|
270
|
-
showSubmitButton={false}
|
|
271
|
-
/>
|
|
272
|
-
<pre className="mt-3 text-[11px] text-muted-foreground">
|
|
273
|
-
{JSON.stringify(data, null, 2)}
|
|
274
|
-
</pre>
|
|
275
|
-
</div>
|
|
276
|
-
);
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Live demo of all extensions wired together — compact density, collapsible
|
|
281
|
-
* groups, conditional disable, slider with unit. Mirrors what the layouts
|
|
282
|
-
* playground sidebar uses.
|
|
283
|
-
*/
|
|
284
|
-
export const PlaygroundStyle = () => {
|
|
285
|
-
const [data, setData] = useState<Record<string, unknown>>({
|
|
286
|
-
shell: { variant: 'boxed', inset: 12, radius: '2xl', border: true },
|
|
287
|
-
sidebar: {
|
|
288
|
-
density: 'default',
|
|
289
|
-
activeIndicator: 'background',
|
|
290
|
-
collapsibleGroups: false,
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
|
-
return (
|
|
294
|
-
<div className="max-w-md">
|
|
295
|
-
<JsonSchemaForm
|
|
296
|
-
schema={{
|
|
297
|
-
type: 'object',
|
|
298
|
-
properties: {
|
|
299
|
-
shell: {
|
|
300
|
-
type: 'object',
|
|
301
|
-
title: 'Shell',
|
|
302
|
-
properties: {
|
|
303
|
-
variant: { type: 'string', title: 'Variant', enum: ['full-bleed', 'boxed'] },
|
|
304
|
-
inset: { type: 'integer', title: 'Inset', minimum: 0, maximum: 32 },
|
|
305
|
-
radius: { type: 'string', title: 'Radius', enum: ['sm', 'md', 'lg', 'xl', '2xl', '3xl'] },
|
|
306
|
-
border: { type: 'boolean', title: 'Border' },
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
sidebar: {
|
|
310
|
-
type: 'object',
|
|
311
|
-
title: 'Sidebar',
|
|
312
|
-
properties: {
|
|
313
|
-
density: { type: 'string', title: 'Density', enum: ['sparse', 'default', 'dense'] },
|
|
314
|
-
activeIndicator: {
|
|
315
|
-
type: 'string',
|
|
316
|
-
title: 'Active indicator',
|
|
317
|
-
enum: ['background', 'rail', 'both'],
|
|
318
|
-
},
|
|
319
|
-
collapsibleGroups: { type: 'boolean', title: 'Collapsible groups' },
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
},
|
|
323
|
-
}}
|
|
324
|
-
uiSchema={{
|
|
325
|
-
shell: {
|
|
326
|
-
'ui:collapsible': true,
|
|
327
|
-
inset: {
|
|
328
|
-
'ui:widget': 'slider',
|
|
329
|
-
'ui:options': { unit: 'px', step: 2, showInput: false },
|
|
330
|
-
'ui:disabledWhen': { path: 'shell.variant', notEq: 'boxed' },
|
|
331
|
-
},
|
|
332
|
-
radius: { 'ui:disabledWhen': { path: 'shell.variant', notEq: 'boxed' } },
|
|
333
|
-
border: {
|
|
334
|
-
'ui:widget': 'switch',
|
|
335
|
-
'ui:disabledWhen': { path: 'shell.variant', notEq: 'boxed' },
|
|
336
|
-
},
|
|
337
|
-
},
|
|
338
|
-
sidebar: {
|
|
339
|
-
'ui:collapsible': true,
|
|
340
|
-
collapsibleGroups: { 'ui:widget': 'switch' },
|
|
341
|
-
},
|
|
342
|
-
}}
|
|
343
|
-
formData={data}
|
|
344
|
-
onChange={(e) => setData(e.formData ?? {})}
|
|
345
|
-
density="compact"
|
|
346
|
-
showSubmitButton={false}
|
|
347
|
-
/>
|
|
348
|
-
</div>
|
|
349
|
-
);
|
|
350
|
-
};
|