@djangocfg/ui-tools 2.1.381 → 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 (178) hide show
  1. package/README.md +132 -899
  2. package/dist/ChatRoot-6IZFM5HM.mjs +5 -0
  3. package/dist/{ChatRoot-EJC5Y2YM.cjs.map → ChatRoot-6IZFM5HM.mjs.map} +1 -1
  4. package/dist/ChatRoot-LW4XNIKP.cjs +14 -0
  5. package/dist/{ChatRoot-QOSKJPM6.mjs.map → ChatRoot-LW4XNIKP.cjs.map} +1 -1
  6. package/dist/DictationField-U25MEYAL.mjs +4 -0
  7. package/dist/DictationField-U25MEYAL.mjs.map +1 -0
  8. package/dist/DictationField-XWR5VOID.cjs +13 -0
  9. package/dist/DictationField-XWR5VOID.cjs.map +1 -0
  10. package/dist/{DocsLayout-2YKPXZYO.mjs → DocsLayout-2P3ONDWJ.mjs} +3 -3
  11. package/dist/{DocsLayout-2YKPXZYO.mjs.map → DocsLayout-2P3ONDWJ.mjs.map} +1 -1
  12. package/dist/{DocsLayout-Q4KS3QWW.cjs → DocsLayout-2YZNS5VK.cjs} +8 -8
  13. package/dist/{DocsLayout-Q4KS3QWW.cjs.map → DocsLayout-2YZNS5VK.cjs.map} +1 -1
  14. package/dist/chunk-4PFW7MIJ.cjs +837 -0
  15. package/dist/chunk-4PFW7MIJ.cjs.map +1 -0
  16. package/dist/chunk-C2YN6WEO.mjs +833 -0
  17. package/dist/chunk-C2YN6WEO.mjs.map +1 -0
  18. package/dist/{chunk-XACCHZH2.cjs → chunk-FIRK5CEH.cjs} +42 -4
  19. package/dist/chunk-FIRK5CEH.cjs.map +1 -0
  20. package/dist/{chunk-NWUT327A.mjs → chunk-HIK6BPL7.mjs} +38 -5
  21. package/dist/chunk-HIK6BPL7.mjs.map +1 -0
  22. package/dist/chunk-OZAU3QWD.cjs +2493 -0
  23. package/dist/chunk-OZAU3QWD.cjs.map +1 -0
  24. package/dist/chunk-UWVP6LCW.mjs +2447 -0
  25. package/dist/chunk-UWVP6LCW.mjs.map +1 -0
  26. package/dist/index.cjs +1668 -99
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +1215 -107
  29. package/dist/index.d.ts +1215 -107
  30. package/dist/index.mjs +1555 -50
  31. package/dist/index.mjs.map +1 -1
  32. package/package.json +16 -15
  33. package/src/audio-assets.d.ts +8 -0
  34. package/src/components/markdown/MarkdownMessage/CollapseToggle.tsx +3 -1
  35. package/src/components/markdown/MarkdownMessage/components.tsx +2 -5
  36. package/src/tools/Chat/README.md +347 -530
  37. package/src/tools/Chat/components/Attachments.tsx +6 -1
  38. package/src/tools/Chat/components/ChatRoot.tsx +30 -2
  39. package/src/tools/Chat/components/Composer.tsx +20 -3
  40. package/src/tools/Chat/components/ErrorBanner.tsx +7 -3
  41. package/src/tools/Chat/components/MessageActions.tsx +3 -1
  42. package/src/tools/Chat/components/MessageBubble.tsx +6 -5
  43. package/src/tools/Chat/components/MessageList.tsx +87 -1
  44. package/src/tools/Chat/components/ToolCalls.tsx +21 -3
  45. package/src/tools/Chat/context/ChatProvider.tsx +21 -3
  46. package/src/tools/Chat/core/audio/audioBus.ts +10 -163
  47. package/src/tools/Chat/core/audio/defaults.ts +43 -0
  48. package/src/tools/Chat/core/audio/index.ts +1 -0
  49. package/src/tools/Chat/core/audio/preferences.ts +5 -59
  50. package/src/tools/Chat/core/audio/sounds/error.mp3 +0 -0
  51. package/src/tools/Chat/core/audio/sounds/mention.mp3 +0 -0
  52. package/src/tools/Chat/core/audio/sounds/notification.mp3 +0 -0
  53. package/src/tools/Chat/core/audio/sounds/received.mp3 +0 -0
  54. package/src/tools/Chat/core/audio/sounds/sent.mp3 +0 -0
  55. package/src/tools/Chat/core/audio/sounds/start.mp3 +0 -0
  56. package/src/tools/Chat/core/audio/types.ts +28 -0
  57. package/src/tools/Chat/core/reducer.ts +33 -0
  58. package/src/tools/Chat/core/transport/index.ts +13 -0
  59. package/src/tools/Chat/core/transport/mappers/index.ts +6 -0
  60. package/src/tools/Chat/core/transport/mappers/pydantic-ai.ts +142 -0
  61. package/src/tools/Chat/core/transport/pydantic-ai-transport.ts +208 -0
  62. package/src/tools/Chat/core/transport/sse.ts +18 -5
  63. package/src/tools/Chat/hooks/index.ts +25 -0
  64. package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +5 -3
  65. package/src/tools/Chat/hooks/useChat.ts +28 -0
  66. package/src/tools/Chat/hooks/useChatAudio.ts +59 -180
  67. package/src/tools/Chat/hooks/useChatDockPrefs.ts +74 -0
  68. package/src/tools/Chat/hooks/useChatReset.ts +70 -0
  69. package/src/tools/Chat/hooks/useChatUnread.ts +87 -0
  70. package/src/tools/Chat/hooks/useFocusOnEmptyClick.ts +111 -0
  71. package/src/tools/Chat/hooks/useVisitorFingerprint.ts +48 -0
  72. package/src/tools/Chat/index.ts +84 -1
  73. package/src/tools/Chat/launcher/ChatDock.tsx +263 -0
  74. package/src/tools/Chat/launcher/ChatFAB.tsx +349 -0
  75. package/src/tools/Chat/launcher/ChatGreeting.tsx +200 -0
  76. package/src/tools/Chat/launcher/ChatHeader.tsx +76 -0
  77. package/src/tools/Chat/launcher/ChatHeaderActionButton.tsx +87 -0
  78. package/src/tools/Chat/launcher/ChatHeaderAudioToggle.tsx +47 -0
  79. package/src/tools/Chat/launcher/ChatHeaderLanguageButton.tsx +179 -0
  80. package/src/tools/Chat/launcher/ChatHeaderModeToggle.tsx +57 -0
  81. package/src/tools/Chat/launcher/ChatHeaderResetButton.tsx +93 -0
  82. package/src/tools/Chat/launcher/ChatLauncher.tsx +321 -0
  83. package/src/tools/Chat/launcher/ChatUnreadPreview.tsx +197 -0
  84. package/src/tools/Chat/launcher/index.ts +46 -0
  85. package/src/tools/Chat/launcher/useChatPresence.ts +44 -0
  86. package/src/tools/Chat/styles/bubbleTokens.ts +71 -0
  87. package/src/tools/Chat/styles/index.ts +16 -0
  88. package/src/tools/Chat/styles/useChatStyles.ts +101 -0
  89. package/src/tools/Chat/types/attachment.ts +25 -0
  90. package/src/tools/Chat/types/config.ts +48 -0
  91. package/src/tools/Chat/types/events.ts +35 -0
  92. package/src/tools/Chat/types/index.ts +34 -0
  93. package/src/tools/Chat/types/labels.ts +38 -0
  94. package/src/tools/Chat/types/message.ts +32 -0
  95. package/src/tools/Chat/types/persona.ts +31 -0
  96. package/src/tools/Chat/types/session.ts +43 -0
  97. package/src/tools/Chat/types/tool-call.ts +17 -0
  98. package/src/tools/Chat/types/transport.ts +28 -0
  99. package/src/tools/Chat/types.ts +5 -240
  100. package/src/tools/MarkdownEditor/MarkdownEditor.tsx +50 -14
  101. package/src/tools/MarkdownEditor/index.ts +1 -1
  102. package/src/tools/SpeechRecognition/README.md +336 -0
  103. package/src/tools/SpeechRecognition/__tests__/ids.test.ts +15 -0
  104. package/src/tools/SpeechRecognition/__tests__/language.test.ts +59 -0
  105. package/src/tools/SpeechRecognition/__tests__/reducer.test.ts +71 -0
  106. package/src/tools/SpeechRecognition/__tests__/transcript.test.ts +52 -0
  107. package/src/tools/SpeechRecognition/components/DevicePicker.tsx +49 -0
  108. package/src/tools/SpeechRecognition/components/DictationButton.tsx +93 -0
  109. package/src/tools/SpeechRecognition/components/EngineBadge.tsx +30 -0
  110. package/src/tools/SpeechRecognition/components/ErrorBanner.tsx +52 -0
  111. package/src/tools/SpeechRecognition/components/LanguagePicker.tsx +63 -0
  112. package/src/tools/SpeechRecognition/components/MicMeter.tsx +63 -0
  113. package/src/tools/SpeechRecognition/components/PushToTalkHint.tsx +51 -0
  114. package/src/tools/SpeechRecognition/components/TranscriptView.tsx +55 -0
  115. package/src/tools/SpeechRecognition/components/index.ts +16 -0
  116. package/src/tools/SpeechRecognition/context/SpeechRecognitionProvider.tsx +47 -0
  117. package/src/tools/SpeechRecognition/context/index.ts +6 -0
  118. package/src/tools/SpeechRecognition/core/audio/defaults.ts +24 -0
  119. package/src/tools/SpeechRecognition/core/engine/external.ts +222 -0
  120. package/src/tools/SpeechRecognition/core/engine/http.ts +147 -0
  121. package/src/tools/SpeechRecognition/core/engine/index.ts +52 -0
  122. package/src/tools/SpeechRecognition/core/engine/mediarecorder.ts +105 -0
  123. package/src/tools/SpeechRecognition/core/engine/websocket.ts +211 -0
  124. package/src/tools/SpeechRecognition/core/engine/webspeech.ts +188 -0
  125. package/src/tools/SpeechRecognition/core/ids.ts +11 -0
  126. package/src/tools/SpeechRecognition/core/index.ts +14 -0
  127. package/src/tools/SpeechRecognition/core/language.ts +78 -0
  128. package/src/tools/SpeechRecognition/core/languages-catalog.ts +229 -0
  129. package/src/tools/SpeechRecognition/core/logger.ts +3 -0
  130. package/src/tools/SpeechRecognition/core/reducer.ts +105 -0
  131. package/src/tools/SpeechRecognition/core/transcript.ts +36 -0
  132. package/src/tools/SpeechRecognition/hooks/index.ts +14 -0
  133. package/src/tools/SpeechRecognition/hooks/useDictation.ts +59 -0
  134. package/src/tools/SpeechRecognition/hooks/useEnginePrefs.ts +15 -0
  135. package/src/tools/SpeechRecognition/hooks/useMicDevices.ts +57 -0
  136. package/src/tools/SpeechRecognition/hooks/useMicLevel.ts +52 -0
  137. package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +85 -0
  138. package/src/tools/SpeechRecognition/hooks/useResolvedLanguage.ts +28 -0
  139. package/src/tools/SpeechRecognition/hooks/useSpeechLanguageInfo.ts +108 -0
  140. package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +188 -0
  141. package/src/tools/SpeechRecognition/hooks/useVoiceSupport.ts +78 -0
  142. package/src/tools/SpeechRecognition/index.ts +82 -0
  143. package/src/tools/SpeechRecognition/lazy.tsx +19 -0
  144. package/src/tools/SpeechRecognition/store/index.ts +2 -0
  145. package/src/tools/SpeechRecognition/store/prefsStore.ts +54 -0
  146. package/src/tools/SpeechRecognition/types.ts +133 -0
  147. package/src/tools/SpeechRecognition/widgets/DictationField.tsx +105 -0
  148. package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +305 -0
  149. package/src/tools/SpeechRecognition/widgets/VoiceMessageRecorder.tsx +88 -0
  150. package/src/tools/SpeechRecognition/widgets/index.ts +6 -0
  151. package/dist/ChatRoot-EJC5Y2YM.cjs +0 -14
  152. package/dist/ChatRoot-QOSKJPM6.mjs +0 -5
  153. package/dist/chunk-NWUT327A.mjs.map +0 -1
  154. package/dist/chunk-QLMKCSR6.mjs +0 -2420
  155. package/dist/chunk-QLMKCSR6.mjs.map +0 -1
  156. package/dist/chunk-SI5RD2GD.cjs +0 -2460
  157. package/dist/chunk-SI5RD2GD.cjs.map +0 -1
  158. package/dist/chunk-XACCHZH2.cjs.map +0 -1
  159. package/src/components/markdown/MarkdownMessage/MarkdownMessage.story.tsx +0 -771
  160. package/src/stories/index.ts +0 -33
  161. package/src/tools/AudioPlayer/AudioPlayer.story.tsx +0 -481
  162. package/src/tools/Chat/Chat.story.tsx +0 -1457
  163. package/src/tools/CodeEditor/CodeEditor.story.tsx +0 -202
  164. package/src/tools/CronScheduler/CronScheduler.story.tsx +0 -300
  165. package/src/tools/Gallery/Gallery.story.tsx +0 -237
  166. package/src/tools/ImageViewer/ImageViewer.story.tsx +0 -85
  167. package/src/tools/JsonForm/JsonForm.story.tsx +0 -350
  168. package/src/tools/JsonTree/JsonTree.story.tsx +0 -141
  169. package/src/tools/LottiePlayer/LottiePlayer.story.tsx +0 -95
  170. package/src/tools/Map/Map.story.tsx +0 -458
  171. package/src/tools/MarkdownEditor/MarkdownEditor.story.tsx +0 -225
  172. package/src/tools/Mermaid/Mermaid.story.tsx +0 -251
  173. package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +0 -230
  174. package/src/tools/PrettyCode/PrettyCode.story.tsx +0 -304
  175. package/src/tools/Tour/Tour.story.tsx +0 -279
  176. package/src/tools/Tree/Tree.story.tsx +0 -620
  177. package/src/tools/Uploader/Uploader.story.tsx +0 -415
  178. 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
- };