@djangocfg/ui-tools 2.1.380 → 2.1.382
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/README.md +132 -899
- package/dist/ChatRoot-6IZFM5HM.mjs +5 -0
- package/dist/{ChatRoot-EJC5Y2YM.cjs.map → ChatRoot-6IZFM5HM.mjs.map} +1 -1
- package/dist/ChatRoot-LW4XNIKP.cjs +14 -0
- package/dist/{ChatRoot-QOSKJPM6.mjs.map → ChatRoot-LW4XNIKP.cjs.map} +1 -1
- package/dist/DictationField-2ZLQWLYV.mjs +4 -0
- package/dist/DictationField-2ZLQWLYV.mjs.map +1 -0
- package/dist/DictationField-IPPJ54CU.cjs +13 -0
- package/dist/DictationField-IPPJ54CU.cjs.map +1 -0
- package/dist/{DocsLayout-2YKPXZYO.mjs → DocsLayout-2P3ONDWJ.mjs} +3 -3
- package/dist/{DocsLayout-2YKPXZYO.mjs.map → DocsLayout-2P3ONDWJ.mjs.map} +1 -1
- package/dist/{DocsLayout-Q4KS3QWW.cjs → DocsLayout-2YZNS5VK.cjs} +8 -8
- package/dist/{DocsLayout-Q4KS3QWW.cjs.map → DocsLayout-2YZNS5VK.cjs.map} +1 -1
- package/dist/chunk-4LXG3NBV.mjs +833 -0
- package/dist/chunk-4LXG3NBV.mjs.map +1 -0
- package/dist/{chunk-XACCHZH2.cjs → chunk-FIRK5CEH.cjs} +42 -4
- package/dist/chunk-FIRK5CEH.cjs.map +1 -0
- package/dist/{chunk-NWUT327A.mjs → chunk-HIK6BPL7.mjs} +38 -5
- package/dist/chunk-HIK6BPL7.mjs.map +1 -0
- package/dist/chunk-KMSBGNVC.cjs +835 -0
- package/dist/chunk-KMSBGNVC.cjs.map +1 -0
- package/dist/chunk-OZAU3QWD.cjs +2493 -0
- package/dist/chunk-OZAU3QWD.cjs.map +1 -0
- package/dist/chunk-UWVP6LCW.mjs +2447 -0
- package/dist/chunk-UWVP6LCW.mjs.map +1 -0
- package/dist/index.cjs +1532 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1148 -107
- package/dist/index.d.ts +1148 -107
- package/dist/index.mjs +1421 -51
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -8
- package/src/audio-assets.d.ts +8 -0
- package/src/components/markdown/MarkdownMessage/CollapseToggle.tsx +3 -1
- package/src/components/markdown/MarkdownMessage/components.tsx +2 -5
- package/src/stories/index.ts +32 -2
- package/src/tools/Chat/README.md +347 -530
- package/src/tools/Chat/components/Attachments.tsx +6 -1
- package/src/tools/Chat/components/ChatRoot.tsx +30 -2
- package/src/tools/Chat/components/Composer.tsx +20 -3
- package/src/tools/Chat/components/ErrorBanner.tsx +7 -3
- package/src/tools/Chat/components/MessageActions.tsx +3 -1
- package/src/tools/Chat/components/MessageBubble.tsx +6 -5
- package/src/tools/Chat/components/MessageList.tsx +87 -1
- package/src/tools/Chat/components/ToolCalls.tsx +21 -3
- package/src/tools/Chat/context/ChatProvider.tsx +21 -3
- package/src/tools/Chat/core/audio/audioBus.ts +10 -163
- package/src/tools/Chat/core/audio/defaults.ts +43 -0
- package/src/tools/Chat/core/audio/index.ts +1 -0
- package/src/tools/Chat/core/audio/preferences.ts +5 -59
- package/src/tools/Chat/core/audio/sounds/error.mp3 +0 -0
- package/src/tools/Chat/core/audio/sounds/mention.mp3 +0 -0
- package/src/tools/Chat/core/audio/sounds/notification.mp3 +0 -0
- package/src/tools/Chat/core/audio/sounds/received.mp3 +0 -0
- package/src/tools/Chat/core/audio/sounds/sent.mp3 +0 -0
- package/src/tools/Chat/core/audio/sounds/start.mp3 +0 -0
- package/src/tools/Chat/core/audio/types.ts +28 -0
- package/src/tools/Chat/core/reducer.ts +33 -0
- package/src/tools/Chat/core/transport/index.ts +13 -0
- package/src/tools/Chat/core/transport/mappers/index.ts +6 -0
- package/src/tools/Chat/core/transport/mappers/pydantic-ai.ts +142 -0
- package/src/tools/Chat/core/transport/pydantic-ai-transport.ts +208 -0
- package/src/tools/Chat/core/transport/sse.ts +18 -5
- package/src/tools/Chat/hooks/index.ts +25 -0
- package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +5 -3
- package/src/tools/Chat/hooks/useChat.ts +28 -0
- package/src/tools/Chat/hooks/useChatAudio.ts +59 -180
- package/src/tools/Chat/hooks/useChatDockPrefs.ts +74 -0
- package/src/tools/Chat/hooks/useChatReset.ts +70 -0
- package/src/tools/Chat/hooks/useChatUnread.ts +87 -0
- package/src/tools/Chat/hooks/useFocusOnEmptyClick.ts +111 -0
- package/src/tools/Chat/hooks/useVisitorFingerprint.ts +48 -0
- package/src/tools/Chat/index.ts +69 -1
- package/src/tools/Chat/launcher/ChatDock.tsx +263 -0
- package/src/tools/Chat/launcher/ChatFAB.tsx +349 -0
- package/src/tools/Chat/launcher/ChatGreeting.tsx +200 -0
- package/src/tools/Chat/launcher/ChatHeader.tsx +76 -0
- package/src/tools/Chat/launcher/ChatHeaderActionButton.tsx +87 -0
- package/src/tools/Chat/launcher/ChatHeaderAudioToggle.tsx +47 -0
- package/src/tools/Chat/launcher/ChatHeaderLanguageButton.tsx +179 -0
- package/src/tools/Chat/launcher/ChatHeaderModeToggle.tsx +57 -0
- package/src/tools/Chat/launcher/ChatHeaderResetButton.tsx +93 -0
- package/src/tools/Chat/launcher/ChatLauncher.tsx +321 -0
- package/src/tools/Chat/launcher/ChatUnreadPreview.tsx +197 -0
- package/src/tools/Chat/launcher/index.ts +46 -0
- package/src/tools/Chat/launcher/useChatPresence.ts +44 -0
- package/src/tools/Chat/stories/01-basic.story.tsx +64 -0
- package/src/tools/Chat/stories/02-bubbles.story.tsx +21 -0
- package/src/tools/Chat/stories/03-tool-calls.story.tsx +59 -0
- package/src/tools/Chat/stories/04-personas.story.tsx +78 -0
- package/src/tools/Chat/stories/05-launcher.story.tsx +321 -0
- package/src/tools/Chat/stories/06-header.story.tsx +147 -0
- package/src/tools/Chat/stories/07-audio-actions.story.tsx +112 -0
- package/src/tools/Chat/stories/shared/Frame.tsx +21 -0
- package/src/tools/Chat/stories/shared/index.ts +5 -0
- package/src/tools/Chat/stories/shared/messages.ts +39 -0
- package/src/tools/Chat/stories/shared/personas.ts +13 -0
- package/src/tools/Chat/stories/shared/seeds.ts +92 -0
- package/src/tools/Chat/stories/shared/transports.ts +36 -0
- package/src/tools/Chat/styles/bubbleTokens.ts +71 -0
- package/src/tools/Chat/styles/index.ts +16 -0
- package/src/tools/Chat/styles/useChatStyles.ts +101 -0
- package/src/tools/Chat/types/attachment.ts +25 -0
- package/src/tools/Chat/types/config.ts +48 -0
- package/src/tools/Chat/types/events.ts +35 -0
- package/src/tools/Chat/types/index.ts +34 -0
- package/src/tools/Chat/types/labels.ts +38 -0
- package/src/tools/Chat/types/message.ts +32 -0
- package/src/tools/Chat/types/persona.ts +31 -0
- package/src/tools/Chat/types/session.ts +43 -0
- package/src/tools/Chat/types/tool-call.ts +17 -0
- package/src/tools/Chat/types/transport.ts +28 -0
- package/src/tools/Chat/types.ts +5 -240
- package/src/tools/MarkdownEditor/MarkdownEditor.tsx +50 -14
- package/src/tools/MarkdownEditor/index.ts +1 -1
- package/src/tools/SpeechRecognition/README.md +336 -0
- package/src/tools/SpeechRecognition/__tests__/ids.test.ts +15 -0
- package/src/tools/SpeechRecognition/__tests__/language.test.ts +59 -0
- package/src/tools/SpeechRecognition/__tests__/reducer.test.ts +71 -0
- package/src/tools/SpeechRecognition/__tests__/transcript.test.ts +52 -0
- package/src/tools/SpeechRecognition/components/DevicePicker.tsx +49 -0
- package/src/tools/SpeechRecognition/components/DictationButton.tsx +93 -0
- package/src/tools/SpeechRecognition/components/EngineBadge.tsx +30 -0
- package/src/tools/SpeechRecognition/components/ErrorBanner.tsx +52 -0
- package/src/tools/SpeechRecognition/components/LanguagePicker.tsx +63 -0
- package/src/tools/SpeechRecognition/components/MicMeter.tsx +63 -0
- package/src/tools/SpeechRecognition/components/PushToTalkHint.tsx +51 -0
- package/src/tools/SpeechRecognition/components/TranscriptView.tsx +55 -0
- package/src/tools/SpeechRecognition/components/index.ts +16 -0
- package/src/tools/SpeechRecognition/context/SpeechRecognitionProvider.tsx +47 -0
- package/src/tools/SpeechRecognition/context/index.ts +6 -0
- package/src/tools/SpeechRecognition/core/audio/defaults.ts +24 -0
- package/src/tools/SpeechRecognition/core/engine/external.ts +222 -0
- package/src/tools/SpeechRecognition/core/engine/http.ts +147 -0
- package/src/tools/SpeechRecognition/core/engine/index.ts +52 -0
- package/src/tools/SpeechRecognition/core/engine/mediarecorder.ts +105 -0
- package/src/tools/SpeechRecognition/core/engine/websocket.ts +211 -0
- package/src/tools/SpeechRecognition/core/engine/webspeech.ts +188 -0
- package/src/tools/SpeechRecognition/core/ids.ts +11 -0
- package/src/tools/SpeechRecognition/core/index.ts +14 -0
- package/src/tools/SpeechRecognition/core/language.ts +78 -0
- package/src/tools/SpeechRecognition/core/languages-catalog.ts +229 -0
- package/src/tools/SpeechRecognition/core/logger.ts +3 -0
- package/src/tools/SpeechRecognition/core/reducer.ts +105 -0
- package/src/tools/SpeechRecognition/core/transcript.ts +36 -0
- package/src/tools/SpeechRecognition/hooks/index.ts +14 -0
- package/src/tools/SpeechRecognition/hooks/useDictation.ts +59 -0
- package/src/tools/SpeechRecognition/hooks/useEnginePrefs.ts +15 -0
- package/src/tools/SpeechRecognition/hooks/useMicDevices.ts +57 -0
- package/src/tools/SpeechRecognition/hooks/useMicLevel.ts +52 -0
- package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +85 -0
- package/src/tools/SpeechRecognition/hooks/useResolvedLanguage.ts +28 -0
- package/src/tools/SpeechRecognition/hooks/useSpeechLanguageInfo.ts +108 -0
- package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +188 -0
- package/src/tools/SpeechRecognition/hooks/useVoiceSupport.ts +78 -0
- package/src/tools/SpeechRecognition/index.ts +82 -0
- package/src/tools/SpeechRecognition/lazy.tsx +19 -0
- package/src/tools/SpeechRecognition/store/index.ts +2 -0
- package/src/tools/SpeechRecognition/store/prefsStore.ts +54 -0
- package/src/tools/SpeechRecognition/stories/01-basic.story.tsx +32 -0
- package/src/tools/SpeechRecognition/stories/02-dictation-field.story.tsx +32 -0
- package/src/tools/SpeechRecognition/stories/03-push-to-talk.story.tsx +27 -0
- package/src/tools/SpeechRecognition/stories/04-mic-meter.story.tsx +35 -0
- package/src/tools/SpeechRecognition/stories/05-custom-engine-http.story.tsx +40 -0
- package/src/tools/SpeechRecognition/stories/06-custom-engine-ws.story.tsx +48 -0
- package/src/tools/SpeechRecognition/stories/07-language-device.story.tsx +57 -0
- package/src/tools/SpeechRecognition/stories/08-errors-permissions.story.tsx +25 -0
- package/src/tools/SpeechRecognition/stories/09-chat-voice.story.tsx +90 -0
- package/src/tools/SpeechRecognition/stories/shared.tsx +123 -0
- package/src/tools/SpeechRecognition/types.ts +133 -0
- package/src/tools/SpeechRecognition/widgets/DictationField.tsx +105 -0
- package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +305 -0
- package/src/tools/SpeechRecognition/widgets/VoiceMessageRecorder.tsx +88 -0
- package/src/tools/SpeechRecognition/widgets/index.ts +6 -0
- package/dist/ChatRoot-EJC5Y2YM.cjs +0 -14
- package/dist/ChatRoot-QOSKJPM6.mjs +0 -5
- package/dist/chunk-NWUT327A.mjs.map +0 -1
- package/dist/chunk-QLMKCSR6.mjs +0 -2420
- package/dist/chunk-QLMKCSR6.mjs.map +0 -1
- package/dist/chunk-SI5RD2GD.cjs +0 -2460
- package/dist/chunk-SI5RD2GD.cjs.map +0 -1
- package/dist/chunk-XACCHZH2.cjs.map +0 -1
- package/src/tools/Chat/Chat.story.tsx +0 -1457
package/README.md
CHANGED
|
@@ -6,997 +6,230 @@
|
|
|
6
6
|
|
|
7
7
|
# @djangocfg/ui-tools
|
|
8
8
|
|
|
9
|
-
Heavy React tools with lazy loading
|
|
9
|
+
Heavy React tools with lazy loading. No Next.js dependency — works with Electron, Vite, CRA, any React environment.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
**Part of [DjangoCFG](https://djangocfg.com)** — modern Django framework for production-ready SaaS applications.
|
|
14
|
-
|
|
15
|
-
## Install
|
|
11
|
+
Part of [DjangoCFG](https://djangocfg.com).
|
|
16
12
|
|
|
17
13
|
```bash
|
|
18
14
|
pnpm add @djangocfg/ui-tools
|
|
19
15
|
```
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
---
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
## What's inside
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|---------|----------|
|
|
27
|
-
| `@djangocfg/ui-core` | Lightweight UI components (60+ components) |
|
|
28
|
-
| `@djangocfg/ui-tools` | Heavy tools with lazy loading |
|
|
29
|
-
| `@djangocfg/ui-nextjs` | Next.js apps (extends ui-core) |
|
|
21
|
+
Sixteen tools, each one lazy-loaded so it doesn't ship until used. Bundle size is the gzipped tool by itself; deps (mapbox, monaco, etc.) load on first render.
|
|
30
22
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
|
34
|
-
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `JsonForm` | ~300KB | JSON Schema form generator |
|
|
41
|
-
| `MarkdownEditor` | ~200KB | WYSIWYG markdown editor with Tiptap, `@`-mentions (auto-flip popup), customizable markdown serialization via presets |
|
|
23
|
+
| Tool | Bundle | Docs |
|
|
24
|
+
|------|--------|------|
|
|
25
|
+
| `Map` | ~800KB | MapLibre maps, markers, clusters, layers. [README](src/tools/Map/README.md) |
|
|
26
|
+
| `Mermaid` | ~800KB | Diagrams + declarative builders |
|
|
27
|
+
| `CodeEditor` | ~550KB | Monaco editor + diff view |
|
|
28
|
+
| `PrettyCode` | ~500KB | Syntax-highlighted read-only code |
|
|
29
|
+
| `OpenapiViewer` | ~400KB | OpenAPI schema viewer + playground |
|
|
30
|
+
| `JsonForm` | ~300KB | JSON-Schema-driven form generator |
|
|
31
|
+
| `MarkdownEditor` | ~200KB | Tiptap WYSIWYG with `@`-mentions |
|
|
42
32
|
| `LottiePlayer` | ~200KB | Lottie animation player |
|
|
43
|
-
| `Chat` | ~150KB |
|
|
44
|
-
| `
|
|
45
|
-
| `VideoPlayer` | ~150KB |
|
|
46
|
-
| `MarkdownMessage` | ~120KB | Read-only chat-tuned markdown
|
|
47
|
-
| `JsonTree` | ~100KB | JSON visualization
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
33
|
+
| `Chat` | ~150KB | Streaming chat (SSE + tool calls + attachments). [README](src/tools/Chat/README.md) |
|
|
34
|
+
| `SpeechRecognition` | ~40KB | Mic capture + STT with pluggable engines (Web Speech / HTTP / WS). [README](src/tools/SpeechRecognition/README.md) |
|
|
35
|
+
| `VideoPlayer` | ~150KB | Vidstack-based pro player |
|
|
36
|
+
| `MarkdownMessage` | ~120KB | Read-only chat-tuned markdown. [README](src/components/markdown/MarkdownMessage/README.md) |
|
|
37
|
+
| `JsonTree` | ~100KB | JSON visualization (full/compact/inline modes) |
|
|
38
|
+
| `AudioPlayer` | ~80KB | WebView-safe waveform player |
|
|
39
|
+
| `Gallery` | ~50KB | Image/video gallery + lightbox |
|
|
40
|
+
| `ImageViewer` | ~50KB | Zoom/pan/rotate viewer |
|
|
41
|
+
| `CronScheduler` | ~15KB | Cron expression builder |
|
|
51
42
|
|
|
52
|
-
|
|
43
|
+
Plus utility primitives: `Tree`, `Tour`, `FileIcon`, `UploadDropzone`.
|
|
53
44
|
|
|
54
|
-
|
|
45
|
+
---
|
|
55
46
|
|
|
56
|
-
|
|
57
|
-
import { LazyJsonTree } from '@djangocfg/ui-tools/json-tree';
|
|
58
|
-
import { Gallery, GalleryLightbox } from '@djangocfg/ui-tools/gallery';
|
|
59
|
-
import { MapContainer, MapMarker } from '@djangocfg/ui-tools/map';
|
|
60
|
-
import { FlowDiagram, SequenceDiagram } from '@djangocfg/ui-tools/mermaid';
|
|
61
|
-
import { Editor, DiffEditor } from '@djangocfg/ui-tools/code-editor';
|
|
62
|
-
import { LazyOpenapiViewer } from '@djangocfg/ui-tools/openapi-viewer';
|
|
63
|
-
import { LazyAudioPlayer } from '@djangocfg/ui-tools/audio-player';
|
|
64
|
-
import { LazyVideoPlayer } from '@djangocfg/ui-tools/video-player';
|
|
65
|
-
import { LazyChat } from '@djangocfg/ui-tools/chat';
|
|
66
|
-
import { LazyImageViewer } from '@djangocfg/ui-tools/image-viewer';
|
|
67
|
-
import { LazyJsonSchemaForm } from '@djangocfg/ui-tools/json-form';
|
|
68
|
-
import { LazyLottiePlayer } from '@djangocfg/ui-tools/lottie-player';
|
|
69
|
-
import { LazyMermaid } from '@djangocfg/ui-tools/mermaid';
|
|
70
|
-
import { LazyPrettyCode } from '@djangocfg/ui-tools/pretty-code';
|
|
71
|
-
import { LazyCronScheduler } from '@djangocfg/ui-tools/cron-scheduler';
|
|
72
|
-
import { MarkdownEditor } from '@djangocfg/ui-tools/markdown-editor';
|
|
73
|
-
import { FileIcon } from '@djangocfg/ui-tools/file-icon';
|
|
74
|
-
import { Tour } from '@djangocfg/ui-tools/tour';
|
|
75
|
-
import { Tree } from '@djangocfg/ui-tools/tree';
|
|
76
|
-
```
|
|
47
|
+
## Imports
|
|
77
48
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
| Path | Tool | Bundle |
|
|
81
|
-
|------|------|--------|
|
|
82
|
-
| `@djangocfg/ui-tools` | All tools (lazy) | full |
|
|
83
|
-
| `@djangocfg/ui-tools/json-tree` | `JsonTree`, `LazyJsonTree` | ~100KB |
|
|
84
|
-
| `@djangocfg/ui-tools/gallery` | `Gallery`, `GalleryLightbox`, hooks | ~50KB |
|
|
85
|
-
| `@djangocfg/ui-tools/map` | `MapContainer`, markers, layers, utils | ~800KB |
|
|
86
|
-
| `@djangocfg/ui-tools/mermaid` | `Mermaid`, declarative builders | ~800KB |
|
|
87
|
-
| `@djangocfg/ui-tools/code-editor` | `Editor`, `DiffEditor`, hooks | ~550KB |
|
|
88
|
-
| `@djangocfg/ui-tools/pretty-code` | `PrettyCode`, `LazyPrettyCode` | ~500KB |
|
|
89
|
-
| `@djangocfg/ui-tools/openapi-viewer` | `OpenapiViewer`, `LazyOpenapiViewer` | ~400KB |
|
|
90
|
-
| `@djangocfg/ui-tools/json-form` | `JsonSchemaForm`, `LazyJsonSchemaForm` | ~300KB |
|
|
91
|
-
| `@djangocfg/ui-tools/markdown-editor` | `MarkdownEditor` | ~200KB |
|
|
92
|
-
| `@djangocfg/ui-tools/lottie-player` | `LottiePlayer`, `LazyLottiePlayer` | ~200KB |
|
|
93
|
-
| `@djangocfg/ui-tools/chat` | `ChatRoot`, `LazyChat`, transports | ~150KB |
|
|
94
|
-
| `@djangocfg/ui-tools/video-player` | `VideoPlayer`, `LazyVideoPlayer` | ~150KB |
|
|
95
|
-
| `@djangocfg/ui-tools/audio-player` | `AudioPlayer`, `LazyAudioPlayer` | ~80KB |
|
|
96
|
-
| `@djangocfg/ui-tools/image-viewer` | `ImageViewer`, `LazyImageViewer` | ~50KB |
|
|
97
|
-
| `@djangocfg/ui-tools/cron-scheduler` | `CronScheduler`, `LazyCronScheduler` | ~15KB |
|
|
98
|
-
| `@djangocfg/ui-tools/tree` | `Tree` (file/folder tree) | ~20KB |
|
|
99
|
-
| `@djangocfg/ui-tools/file-icon` | `FileIcon` | ~10KB |
|
|
100
|
-
| `@djangocfg/ui-tools/tour` | `Tour` | ~10KB |
|
|
101
|
-
| `@djangocfg/ui-tools/upload` | `UploadDropzone` | ~20KB |
|
|
102
|
-
| `@djangocfg/ui-tools/styles` | Tailwind source CSS | — |
|
|
103
|
-
| `@djangocfg/ui-tools/dist.css` | Pre-compiled CSS | — |
|
|
104
|
-
|
|
105
|
-
## Gallery
|
|
106
|
-
|
|
107
|
-
Full-featured image/video gallery with carousel, grid view, and fullscreen lightbox.
|
|
49
|
+
Default barrel works for app-level imports:
|
|
108
50
|
|
|
109
51
|
```tsx
|
|
110
|
-
import { Gallery } from '@djangocfg/ui-tools
|
|
111
|
-
|
|
112
|
-
const images = [
|
|
113
|
-
{ id: '1', src: '/photo1.jpg', alt: 'Photo 1' },
|
|
114
|
-
{ id: '2', src: '/photo2.jpg', alt: 'Photo 2' },
|
|
115
|
-
{ id: '3', src: '/video.mp4', alt: 'Video', type: 'video' },
|
|
116
|
-
];
|
|
117
|
-
|
|
118
|
-
function PhotoGallery() {
|
|
119
|
-
return (
|
|
120
|
-
<Gallery
|
|
121
|
-
images={images}
|
|
122
|
-
previewMode="carousel" // or "grid"
|
|
123
|
-
showThumbnails
|
|
124
|
-
enableLightbox
|
|
125
|
-
aspectRatio={16 / 9}
|
|
126
|
-
/>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
52
|
+
import { LazyChat, LazyJsonTree, LazyMap, Gallery } from '@djangocfg/ui-tools';
|
|
129
53
|
```
|
|
130
54
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
| Component | Description |
|
|
134
|
-
|-----------|-------------|
|
|
135
|
-
| `Gallery` | Complete gallery with carousel/grid + lightbox |
|
|
136
|
-
| `GalleryCompact` | Minimal carousel for cards |
|
|
137
|
-
| `GalleryGrid` | Grid layout with "show more" badge |
|
|
138
|
-
| `GalleryLightbox` | Fullscreen lightbox viewer |
|
|
139
|
-
| `GalleryCarousel` | Embla-based carousel |
|
|
140
|
-
| `GalleryThumbnails` | Thumbnail strip navigation |
|
|
141
|
-
|
|
142
|
-
### Gallery Hooks
|
|
143
|
-
|
|
144
|
-
| Hook | Description |
|
|
145
|
-
|------|-------------|
|
|
146
|
-
| `useGallery` | Gallery state management |
|
|
147
|
-
| `useSwipe` | Touch swipe gestures |
|
|
148
|
-
| `usePinchZoom` | Pinch-to-zoom for mobile |
|
|
149
|
-
| `usePreloadImages` | Image preloading |
|
|
150
|
-
|
|
151
|
-
## Map
|
|
152
|
-
|
|
153
|
-
MapLibre GL maps with React components for markers, clusters, popups, and custom layers.
|
|
55
|
+
For bundle splitting, import from the tool's subpath — only that tool ships:
|
|
154
56
|
|
|
155
57
|
```tsx
|
|
156
|
-
import {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
function LocationMap() {
|
|
164
|
-
return (
|
|
165
|
-
<MapContainer
|
|
166
|
-
initialViewport={{ latitude: 36, longitude: -119, zoom: 5 }}
|
|
167
|
-
style="streets"
|
|
168
|
-
>
|
|
169
|
-
{markers.map((m) => (
|
|
170
|
-
<MapMarker key={m.id} latitude={m.lat} longitude={m.lng}>
|
|
171
|
-
<MapPopup>{m.title}</MapPopup>
|
|
172
|
-
</MapMarker>
|
|
173
|
-
))}
|
|
174
|
-
</MapContainer>
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Map Components
|
|
180
|
-
|
|
181
|
-
| Component | Description |
|
|
182
|
-
|-----------|-------------|
|
|
183
|
-
| `MapContainer` | Main map container with controls |
|
|
184
|
-
| `MapMarker` | Custom marker with React children |
|
|
185
|
-
| `MapPopup` | Popup attached to marker |
|
|
186
|
-
| `MapCluster` | Clustered markers with spiderfy |
|
|
187
|
-
| `MapSource` / `MapLayer` | Custom GeoJSON layers |
|
|
188
|
-
| `MapControls` | Navigation controls |
|
|
189
|
-
| `MapLegend` | Map legend component |
|
|
190
|
-
| `LayerSwitcher` | Toggle map layers |
|
|
191
|
-
| `DrawControl` | Drawing tools (optional) |
|
|
192
|
-
| `GeocoderControl` | Search/geocoding (optional) |
|
|
193
|
-
|
|
194
|
-
### Overlapping Markers
|
|
195
|
-
|
|
196
|
-
When multiple markers are at the same location, use `offsetOverlappingMarkers` to spread them out before rendering:
|
|
197
|
-
|
|
198
|
-
```tsx
|
|
199
|
-
import { MapCluster } from '@djangocfg/ui-tools/map';
|
|
200
|
-
import { offsetOverlappingMarkers } from '@djangocfg/ui-tools/map';
|
|
201
|
-
|
|
202
|
-
// Pre-process data to offset overlapping points
|
|
203
|
-
const processedData = useMemo(() => {
|
|
204
|
-
const markers = geojson.features.map((f, i) => ({
|
|
205
|
-
id: f.properties?.id || `point-${i}`,
|
|
206
|
-
longitude: f.geometry.coordinates[0],
|
|
207
|
-
latitude: f.geometry.coordinates[1],
|
|
208
|
-
data: f.properties,
|
|
209
|
-
}));
|
|
210
|
-
|
|
211
|
-
const offsetMarkers = offsetOverlappingMarkers(markers, {
|
|
212
|
-
spiralRadius: 0.0003, // ~30m spread
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
type: 'FeatureCollection',
|
|
217
|
-
features: offsetMarkers.map((m) => ({
|
|
218
|
-
type: 'Feature',
|
|
219
|
-
properties: m.data,
|
|
220
|
-
geometry: { type: 'Point', coordinates: [m.longitude, m.latitude] },
|
|
221
|
-
})),
|
|
222
|
-
};
|
|
223
|
-
}, [geojson]);
|
|
224
|
-
|
|
225
|
-
<MapCluster sourceId="points" data={processedData} />
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
| Utility | Description |
|
|
229
|
-
|---------|-------------|
|
|
230
|
-
| `offsetOverlappingMarkers(markers, options)` | Spread overlapping markers using Fermat spiral |
|
|
231
|
-
| `hasOverlappingMarkers(markers)` | Check if any markers overlap |
|
|
232
|
-
| `getOverlapStats(markers)` | Get statistics about overlapping markers |
|
|
233
|
-
|
|
234
|
-
### Map Hooks
|
|
235
|
-
|
|
236
|
-
| Hook | Description |
|
|
237
|
-
|------|-------------|
|
|
238
|
-
| `useMap` | Access map instance |
|
|
239
|
-
| `useMapControl` | Programmatic map control |
|
|
240
|
-
| `useMarkers` | Marker management |
|
|
241
|
-
| `useMapEvents` | Map event handlers |
|
|
242
|
-
| `useMapViewport` | Viewport state |
|
|
243
|
-
| `useMapLayers` | Layer management |
|
|
244
|
-
|
|
245
|
-
### Map Styles
|
|
246
|
-
|
|
247
|
-
```tsx
|
|
248
|
-
import { MAP_STYLES, getMapStyle } from '@djangocfg/ui-tools/map';
|
|
249
|
-
|
|
250
|
-
// Available styles: streets, satellite, dark, light, terrain
|
|
251
|
-
<MapContainer style="dark" />
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### Layer Utilities
|
|
255
|
-
|
|
256
|
-
```tsx
|
|
257
|
-
import {
|
|
258
|
-
createClusterLayers,
|
|
259
|
-
createPointLayer,
|
|
260
|
-
createPolygonLayer,
|
|
261
|
-
createLineLayer,
|
|
262
|
-
} from '@djangocfg/ui-tools/map';
|
|
58
|
+
import { LazyJsonTree } from '@djangocfg/ui-tools/json-tree';
|
|
59
|
+
import { Gallery } from '@djangocfg/ui-tools/gallery';
|
|
60
|
+
import { MapContainer } from '@djangocfg/ui-tools/map';
|
|
61
|
+
import { Editor, DiffEditor } from '@djangocfg/ui-tools/code-editor';
|
|
62
|
+
import { LazyChat } from '@djangocfg/ui-tools/chat';
|
|
63
|
+
import { MarkdownEditor } from '@djangocfg/ui-tools/markdown-editor';
|
|
64
|
+
// …same pattern for every tool
|
|
263
65
|
```
|
|
264
66
|
|
|
265
|
-
|
|
67
|
+
| Subpath | Ships |
|
|
68
|
+
|---------|-------|
|
|
69
|
+
| `@djangocfg/ui-tools` | All tools (lazy) |
|
|
70
|
+
| `@djangocfg/ui-tools/<tool-name>` | One tool — `map`, `mermaid`, `chat`, `speech-recognition`, `json-tree`, `code-editor`, `pretty-code`, `openapi-viewer`, `json-form`, `markdown-editor`, `lottie-player`, `audio-player`, `video-player`, `image-viewer`, `cron-scheduler`, `gallery`, `tour`, `tree`, `file-icon`, `upload` |
|
|
71
|
+
| `@djangocfg/ui-tools/styles` | Tailwind source CSS |
|
|
72
|
+
| `@djangocfg/ui-tools/dist.css` | Pre-compiled CSS |
|
|
266
73
|
|
|
267
|
-
|
|
268
|
-
import { VideoPlayer } from '@djangocfg/ui-tools';
|
|
74
|
+
---
|
|
269
75
|
|
|
270
|
-
|
|
271
|
-
src="https://example.com/video.mp4"
|
|
272
|
-
poster="/thumbnail.jpg"
|
|
273
|
-
autoplay={false}
|
|
274
|
-
/>
|
|
275
|
-
```
|
|
76
|
+
## Lazy loading
|
|
276
77
|
|
|
277
|
-
|
|
78
|
+
Every heavy tool ships in two flavors:
|
|
278
79
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
60 fps in WKWebView (Wails / Electron) and Tauri-style WebView2.
|
|
80
|
+
- `Lazy*` — wrapped in `React.lazy` + `Suspense` + skeleton. Use by default.
|
|
81
|
+
- bare tool (`Mermaid`, `JsonTree`, …) — for explicit loading control.
|
|
282
82
|
|
|
283
83
|
```tsx
|
|
284
|
-
import {
|
|
285
|
-
|
|
286
|
-
<LazyAudioPlayer
|
|
287
|
-
src="/audio/track.mp3"
|
|
288
|
-
title="Track Title"
|
|
289
|
-
artist="Artist"
|
|
290
|
-
cover="/cover.jpg"
|
|
291
|
-
/>
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
Highlights:
|
|
295
|
-
|
|
296
|
-
- **Static peaks waveform** by default (decoded once, cached per `src`); zero
|
|
297
|
-
canvas paints during playback — playhead animates via `clip-path` + a single
|
|
298
|
-
CSS variable on the GPU thread.
|
|
299
|
-
- Five waveform modes: `peaks` (default), `live` (AnalyserNode), `bars`
|
|
300
|
-
(CSS-only decoration), `progress` (plain scrubber, no animation), `none`.
|
|
301
|
-
Decode failure quietly degrades `peaks` → `progress`.
|
|
302
|
-
- **Slot composition**: drop `<PlayerProvider>` + import the parts you need
|
|
303
|
-
(`Cover`, `Title`, `Waveform`, `PlayButton`, …) to build any custom layout.
|
|
304
|
-
- **Click on the waveform = seek + play** (configurable via
|
|
305
|
-
`seekStartsPlayback`); drag to scrub.
|
|
306
|
-
- **One active player at a time** by default — `exclusive` prop, coordinated
|
|
307
|
-
same-tab + cross-tab via `BroadcastChannel`.
|
|
308
|
-
- **Persistent volume / mute** in `localStorage`, synced across all uncontrolled
|
|
309
|
-
players (and tabs). Pass `initialVolume` / `muted` to opt out per-player.
|
|
310
|
-
- **Mobile-aware**: auto-switches to `compact` on phone viewports, volume
|
|
311
|
-
popover toggles on tap, hover affordances disabled on touch, iOS Safari
|
|
312
|
-
hides the volume slider (read-only there).
|
|
313
|
-
- **Keyboard**: Space/K toggle, ←/→ seek 5s, ↑/↓ volume, M mute, L loop.
|
|
314
|
-
Active only when focus is in the player.
|
|
315
|
-
- **Selector hooks** — `usePlayerState`, `usePlayerControls`, `usePlayerLevels`,
|
|
316
|
-
`useActivePlayer`, `useLastActivePlayer`, `useIsActivePlayer`,
|
|
317
|
-
`usePlayerPreferences` — for custom toolbars / "now playing" UIs.
|
|
318
|
-
|
|
319
|
-
See [`AudioPlayer/README.md`](src/tools/AudioPlayer/README.md) for the full API
|
|
320
|
-
and the docs at `@dev/@refactoring6-audioplayer/` for architecture / ADRs.
|
|
321
|
-
|
|
322
|
-
## Mermaid Diagrams
|
|
323
|
-
|
|
324
|
-
Render Mermaid diagrams with fullscreen zoom support and type-safe declarative builders.
|
|
325
|
-
|
|
326
|
-
### Basic Usage
|
|
84
|
+
import { LazyChat } from '@djangocfg/ui-tools/chat';
|
|
327
85
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
<LazyMermaid chart={`
|
|
332
|
-
graph TD
|
|
333
|
-
A[Start] --> B{Decision}
|
|
334
|
-
B -->|Yes| C[Action]
|
|
335
|
-
B -->|No| D[End]
|
|
336
|
-
`} />
|
|
86
|
+
<LazyChat transport={transport} /> // loads on mount
|
|
87
|
+
<LazyChat fallback={<MySkeleton />} ... /> // custom loading UI
|
|
337
88
|
```
|
|
338
89
|
|
|
339
|
-
|
|
90
|
+
Skeletons match the tool's final layout so swap-in is jank-free.
|
|
340
91
|
|
|
341
|
-
|
|
342
|
-
|------|------|---------|-------------|
|
|
343
|
-
| `chart` | `string` | - | Mermaid diagram syntax |
|
|
344
|
-
| `className` | `string` | `''` | Additional CSS classes |
|
|
345
|
-
| `isCompact` | `boolean` | `false` | Compact rendering mode |
|
|
346
|
-
| `fullscreen` | `boolean` | `true` | Enable fullscreen button with pinch-zoom |
|
|
92
|
+
---
|
|
347
93
|
|
|
348
|
-
|
|
94
|
+
## Chat — quick start
|
|
349
95
|
|
|
350
|
-
|
|
96
|
+
The most-used tool. Headless reducer + composable UI: streaming SSE transport, pydantic-AI mapper, decomposed parts (`MessageBubble`, `Composer`, `MessageList`, `ToolCalls`, …), role-aware styling. Floating launcher with FAB, popover/side dock, proactive greeting, live-push notifications, audio mute toggle, reset-with-confirm, Linear-style hotkeys.
|
|
351
97
|
|
|
352
98
|
```tsx
|
|
353
|
-
import { LazyMermaid } from '@djangocfg/ui-tools';
|
|
354
99
|
import {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
} from '@djangocfg/ui-tools/mermaid';
|
|
361
|
-
|
|
362
|
-
function MyDiagram() {
|
|
363
|
-
const presets = useStylePresets();
|
|
364
|
-
const boxes = useBoxColors();
|
|
365
|
-
|
|
366
|
-
// Flow diagram with type-safe nodes
|
|
367
|
-
type Nodes = 'start' | 'check' | 'success' | 'finish';
|
|
368
|
-
const flow = FlowDiagram<Nodes>({ direction: 'TB' });
|
|
369
|
-
|
|
370
|
-
flow.node('start').rect('Start');
|
|
371
|
-
flow.node('check').rhombus('Is it working?');
|
|
372
|
-
flow.node('success').rect('Great!');
|
|
373
|
-
flow.node('finish').stadium('End');
|
|
374
|
-
|
|
375
|
-
flow.edge('start').to('check').solid();
|
|
376
|
-
flow.edge('check').to('success').solid('Yes');
|
|
377
|
-
flow.edge('check').to('finish').solid('No');
|
|
378
|
-
flow.edge('success').to('finish').solid();
|
|
379
|
-
|
|
380
|
-
// Apply theme-aware styles
|
|
381
|
-
flow.style.define('success', presets.success);
|
|
382
|
-
flow.style.apply('success', 'success', 'finish');
|
|
383
|
-
|
|
384
|
-
return <LazyMermaid chart={flow.toString()} />;
|
|
385
|
-
}
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### Available Builders
|
|
389
|
-
|
|
390
|
-
| Builder | Description |
|
|
391
|
-
|---------|-------------|
|
|
392
|
-
| `FlowDiagram<Nodes>` | Flowcharts with nodes, edges, subgraphs |
|
|
393
|
-
| `SequenceDiagram` | Sequence diagrams with participants, messages |
|
|
394
|
-
| `JourneyDiagram` | User journey diagrams with sections, tasks |
|
|
395
|
-
|
|
396
|
-
### Theme Hooks
|
|
397
|
-
|
|
398
|
-
| Hook | Description |
|
|
399
|
-
|------|-------------|
|
|
400
|
-
| `useThemePalette()` | Full palette from CSS variables |
|
|
401
|
-
| `useStylePresets()` | Pre-built style configs (success, warning, etc.) |
|
|
402
|
-
| `useBoxColors()` | Colors for sequence diagram boxes |
|
|
403
|
-
|
|
404
|
-
### FlowDiagram API
|
|
405
|
-
|
|
406
|
-
```tsx
|
|
407
|
-
const flow = FlowDiagram<'A' | 'B' | 'C'>({ direction: 'TB' });
|
|
408
|
-
|
|
409
|
-
// Nodes
|
|
410
|
-
flow.node('A').rect('Rectangle');
|
|
411
|
-
flow.node('B').round('Rounded');
|
|
412
|
-
flow.node('C').rhombus('Diamond');
|
|
413
|
-
flow.node('D').stadium('Stadium');
|
|
414
|
-
flow.node('E').cylinder('Database');
|
|
415
|
-
flow.node('F').hexagon('Hexagon');
|
|
416
|
-
|
|
417
|
-
// Edges
|
|
418
|
-
flow.edge('A').to('B').solid();
|
|
419
|
-
flow.edge('A').to('B').solid('with label');
|
|
420
|
-
flow.edge('A').to('B').dotted();
|
|
421
|
-
flow.edge('A').to('B').thick();
|
|
422
|
-
|
|
423
|
-
// Subgraphs
|
|
424
|
-
flow.subgraph('Group Name', (sub) => {
|
|
425
|
-
sub.direction('LR');
|
|
426
|
-
sub.node('X').rect('Inside');
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
// Styles
|
|
430
|
-
flow.style.define('myStyle', { fill: '#f00', stroke: '#000' });
|
|
431
|
-
flow.style.apply('myStyle', 'A', 'B');
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
### SequenceDiagram API
|
|
435
|
-
|
|
436
|
-
**Static API** (type-safe, for known participants):
|
|
437
|
-
|
|
438
|
-
```tsx
|
|
439
|
-
const { d, rect, alt, loop, toString } = SequenceDiagram({
|
|
440
|
-
User: 'actor',
|
|
441
|
-
App: 'participant',
|
|
442
|
-
API: 'participant',
|
|
443
|
-
}, { autoNumber: true });
|
|
444
|
-
|
|
445
|
-
// Messages (type-safe chain)
|
|
446
|
-
d.User.sync.App.msg('Click button');
|
|
447
|
-
d.App.async.API.msg('Fetch data');
|
|
448
|
-
d.API.asyncReply.App.msg('Response');
|
|
449
|
-
|
|
450
|
-
// Blocks
|
|
451
|
-
rect('#rgba(0,100,200,0.2)', () => {
|
|
452
|
-
d.User.sync.App.msg('Login');
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
alt('Success', () => {
|
|
456
|
-
d.App.syncReply.User.msg('Welcome!');
|
|
457
|
-
}).else('Failure', () => {
|
|
458
|
-
d.App.syncReply.User.msg('Error');
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
loop('Every 5s', () => {
|
|
462
|
-
d.App.async.API.msg('Heartbeat');
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
return toString();
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
**Dynamic API** (for runtime participant names):
|
|
100
|
+
ChatLauncher,
|
|
101
|
+
ChatRoot,
|
|
102
|
+
createPydanticAIChatTransport,
|
|
103
|
+
useChatAudio,
|
|
104
|
+
} from '@djangocfg/ui-tools';
|
|
469
105
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
characters.forEach(c => { participants[c] = 'participant'; });
|
|
475
|
-
|
|
476
|
-
const seq = SequenceDiagram(participants, { autoNumber: true });
|
|
477
|
-
|
|
478
|
-
// Dynamic methods - no type assertions needed
|
|
479
|
-
seq.message('Alice', 'Bob', 'Hello!');
|
|
480
|
-
seq.message('Bob', 'Alice', 'Hi!', 'syncReply');
|
|
481
|
-
seq.message('Alice', 'Charlie', 'Ping', 'async');
|
|
482
|
-
|
|
483
|
-
// Notes
|
|
484
|
-
seq.noteOver('Alice', 'Thinking...');
|
|
485
|
-
seq.noteOverSpan('Alice', 'Bob', 'Discussion');
|
|
486
|
-
seq.noteLeft('Charlie', 'Waiting');
|
|
487
|
-
seq.noteRight('Charlie', 'Done');
|
|
488
|
-
|
|
489
|
-
// Blocks work the same
|
|
490
|
-
seq.rect('rgba(100,200,255,0.2)', () => {
|
|
491
|
-
seq.message('Alice', 'Bob', 'Secret message');
|
|
106
|
+
const transport = createPydanticAIChatTransport({
|
|
107
|
+
buildStreamUrl: (sid, msg) => `${API}/chat/sessions/${sid}/stream?message=${encodeURIComponent(msg)}`,
|
|
108
|
+
streamMethod: 'GET',
|
|
109
|
+
buildHeaders: async () => ({ Authorization: `Bearer ${getToken()}` }),
|
|
492
110
|
});
|
|
493
111
|
|
|
494
|
-
|
|
112
|
+
function Chat() {
|
|
113
|
+
const audio = useChatAudio(); // zero setup — uses built-in sounds bundled into the lazy chunk
|
|
114
|
+
return (
|
|
115
|
+
<ChatLauncher
|
|
116
|
+
hotkey={{ key: '/', meta: true }}
|
|
117
|
+
fab={{ variant: 'animated' }} // size='responsive' default — phone/tablet/desktop
|
|
118
|
+
dock={{ title: 'Assistant', height: 600 }}
|
|
119
|
+
greeting="Hi 👋 Need help?"
|
|
120
|
+
audio={audio} // auto-injects mute toggle into header
|
|
121
|
+
>
|
|
122
|
+
<ChatRoot transport={transport} />
|
|
123
|
+
</ChatLauncher>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
495
126
|
```
|
|
496
127
|
|
|
497
|
-
|
|
498
|
-
|----------------|-------------|
|
|
499
|
-
| `message(from, to, text, arrow?)` | Send message (arrow: sync, syncReply, async, asyncReply, solid, dotted, cross) |
|
|
500
|
-
| `noteOver(participant, text)` | Note over one participant |
|
|
501
|
-
| `noteOverSpan(p1, p2, text)` | Note spanning two participants |
|
|
502
|
-
| `noteLeft(participant, text)` | Note left of participant |
|
|
503
|
-
| `noteRight(participant, text)` | Note right of participant |
|
|
128
|
+
**What's wired by default:** desktop side-mode toggle (auto-hides on narrow screens), persisted dock prefs, two-step Escape, click-to-focus composer, mobile fullscreen with `dvh` heights, push-preview bubble for inbound messages while closed, **ChatGPT-style autoscroll** (sticky-to-bottom within 120 px, every user-send re-anchors the viewport), **bundled chat notification sounds** (sent/received/start/error/mention/notification, ~136KB inlined as `data:`-URLs inside the lazy chat chunk — zero host setup). Native hosts (cmdop_go / Tauri) pass `audio={{ silenced: true, onSoundEvent }}` to keep web silent while routing triggers to the backend.
|
|
504
129
|
|
|
505
|
-
|
|
130
|
+
Drop `<VoiceComposerSlot />` from `@djangocfg/ui-tools/speech-recognition` into `composerToolbarEnd` for live mic-to-text — **zero props**, reads / writes the composer through `ComposerHandle` registered in chat context. Drop `<ChatHeaderLanguageButton />` into `dock.headerActions` for a flag-button language picker (66 BCP-47 tags). Both auto-hide on Firefox / in-app browsers / missing `getUserMedia`. See [`SpeechRecognition`](#speech-recognition--quick-start) below.
|
|
506
131
|
|
|
507
|
-
|
|
508
|
-
const journey = JourneyDiagram({ title: 'User Onboarding' });
|
|
509
|
-
|
|
510
|
-
journey.section('Discovery')
|
|
511
|
-
.task('Visit landing page', 5, 'User')
|
|
512
|
-
.task('Read features', 4, 'User');
|
|
132
|
+
Full docs: [`Chat/README.md`](src/tools/Chat/README.md). Stories: `Tools/Chat/{Basic,Bubbles,ToolCalls,Personas,Launcher,Header,Audio & Actions,Voice composer}`.
|
|
513
133
|
|
|
514
|
-
|
|
515
|
-
.task('Click Sign Up', 5, 'User')
|
|
516
|
-
.task('Fill form', 2, 'User')
|
|
517
|
-
.task('Verify email', 4, ['User', 'System']);
|
|
134
|
+
---
|
|
518
135
|
|
|
519
|
-
|
|
520
|
-
```
|
|
136
|
+
<a id="speech-recognition--quick-start"></a>
|
|
521
137
|
|
|
522
|
-
##
|
|
138
|
+
## Speech Recognition — quick start
|
|
523
139
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
```tsx
|
|
527
|
-
import { Editor, DiffEditor } from '@djangocfg/ui-tools';
|
|
528
|
-
// or tree-shakeable: import { Editor } from '@djangocfg/ui-tools/code-editor';
|
|
529
|
-
|
|
530
|
-
// Basic editor
|
|
531
|
-
<Editor
|
|
532
|
-
value="const x = 42;"
|
|
533
|
-
language="typescript"
|
|
534
|
-
onChange={(value) => console.log(value)}
|
|
535
|
-
options={{ fontSize: 14, minimap: false }}
|
|
536
|
-
/>
|
|
537
|
-
|
|
538
|
-
// Diff view
|
|
539
|
-
<DiffEditor
|
|
540
|
-
original="const x = 1;"
|
|
541
|
-
modified="const x = 42;"
|
|
542
|
-
language="typescript"
|
|
543
|
-
/>
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
### Context & Hooks
|
|
140
|
+
Decomposed STT (Speech-to-Text) tool. Headless hook + composable UI + lazy bundle, same shape as `Chat` and `AudioPlayer`. Default backend is the browser Web Speech API (zero deps, zero network); custom engines plug in via a small interface — Deepgram, AssemblyAI, OpenAI Whisper REST, your own gateway, all without vendor SDKs on the critical path.
|
|
547
141
|
|
|
548
142
|
```tsx
|
|
549
143
|
import {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
useLanguage,
|
|
555
|
-
} from '@djangocfg/ui-tools/code-editor';
|
|
556
|
-
|
|
557
|
-
// Multi-file editor with shared state
|
|
558
|
-
<EditorProvider onSave={async (path) => { /* save file */ }}>
|
|
559
|
-
<FileTree />
|
|
560
|
-
<Editor />
|
|
561
|
-
</EditorProvider>
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
## Code Highlighting
|
|
565
|
-
|
|
566
|
-
Read-only syntax highlighting (lighter than Monaco — use when editing not needed):
|
|
567
|
-
|
|
568
|
-
```tsx
|
|
569
|
-
import { LazyPrettyCode } from '@djangocfg/ui-tools';
|
|
570
|
-
|
|
571
|
-
// Default "card" variant — border, background, hover copy toolbar.
|
|
572
|
-
<LazyPrettyCode
|
|
573
|
-
data={`const hello = "world";`}
|
|
574
|
-
language="typescript"
|
|
575
|
-
/>
|
|
576
|
-
|
|
577
|
-
// "plain" variant — chrome-less, no internal scroll. Use when
|
|
578
|
-
// embedding inside another scroll container so the parent surface
|
|
579
|
-
// owns chrome and scroll (e.g. a panel that already has its own
|
|
580
|
-
// ScrollArea and copy button).
|
|
581
|
-
<LazyPrettyCode
|
|
582
|
-
data={htmlResponseBody}
|
|
583
|
-
language="markup"
|
|
584
|
-
variant="plain"
|
|
585
|
-
/>
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
## JSON Form
|
|
589
|
-
|
|
590
|
-
```tsx
|
|
591
|
-
import { JsonSchemaForm } from '@djangocfg/ui-tools';
|
|
592
|
-
|
|
593
|
-
const schema = {
|
|
594
|
-
type: 'object',
|
|
595
|
-
properties: {
|
|
596
|
-
name: { type: 'string', title: 'Name' },
|
|
597
|
-
email: { type: 'string', format: 'email', title: 'Email' },
|
|
598
|
-
},
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
<JsonSchemaForm
|
|
602
|
-
schema={schema}
|
|
603
|
-
onSubmit={(data) => console.log(data)}
|
|
604
|
-
/>
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
## Chat
|
|
608
|
-
|
|
609
|
-
Decomposed, transport-agnostic chat. Streaming-aware, markdown-native, mobile-ready. Sits on top of `MarkdownMessage` (no second markdown stack).
|
|
610
|
-
|
|
611
|
-
```tsx
|
|
612
|
-
import { ChatRoot, createHttpTransport } from '@djangocfg/ui-tools';
|
|
613
|
-
|
|
614
|
-
const transport = createHttpTransport({
|
|
615
|
-
baseUrl: '/api/chat',
|
|
616
|
-
getAuthHeader: () => ({ Authorization: `Bearer ${getToken()}` }),
|
|
617
|
-
});
|
|
144
|
+
DictationButton,
|
|
145
|
+
TranscriptView,
|
|
146
|
+
useSpeechRecognition,
|
|
147
|
+
} from '@djangocfg/ui-tools/speech-recognition';
|
|
618
148
|
|
|
619
|
-
|
|
149
|
+
function Dictate() {
|
|
150
|
+
const rec = useSpeechRecognition(); // Web Speech engine, browser language
|
|
620
151
|
return (
|
|
621
|
-
|
|
622
|
-
<
|
|
623
|
-
|
|
152
|
+
<>
|
|
153
|
+
<DictationButton status={rec.status} onClick={() => rec.toggle()} />
|
|
154
|
+
<TranscriptView transcript={rec.transcript} />
|
|
155
|
+
</>
|
|
624
156
|
);
|
|
625
157
|
}
|
|
626
158
|
```
|
|
627
159
|
|
|
628
|
-
|
|
160
|
+
For the chat composer:
|
|
629
161
|
|
|
630
162
|
```tsx
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
<ChatProvider transport={transport}>
|
|
636
|
-
<MyHeader />
|
|
637
|
-
<MessageList renderEmpty={() => <EmptyState greeting="Custom" />} />
|
|
638
|
-
<Composer composer={composer} />
|
|
639
|
-
</ChatProvider>
|
|
640
|
-
|
|
641
|
-
// 3) Headless — just hooks
|
|
642
|
-
const chat = useChat({ transport });
|
|
643
|
-
const composer = useChatComposer({ onSubmit: chat.sendMessage });
|
|
644
|
-
```
|
|
645
|
-
|
|
646
|
-
Built-in transports:
|
|
647
|
-
- **`createHttpTransport`** — fetch + SSE streaming (default for web)
|
|
648
|
-
- **`createMockTransport`** — scripted in-memory replies for tests/stories
|
|
649
|
-
|
|
650
|
-
Custom hosts (Wails RPC, WebSocket, gRPC) implement the `ChatTransport` interface.
|
|
651
|
-
|
|
652
|
-
What's exported: types (`ChatMessage`, `ChatToolCall`, `ChatAttachment`, `ChatSource`, `ChatTransport`, `ChatStreamEvent`, …), pure core (`reducer`, `createId`, `createTokenBuffer`), hooks (`useChat`, `useChatComposer`, `useChatScroll`, `useChatHistory`, `useChatLayout`), context (`ChatProvider`, `useChatContext`), and components (`ChatRoot`, `MessageList`, `MessageBubble`, `MessageActions`, `Composer`, `Sources`, `ToolCalls`, `Attachments`, `EmptyState`, `ErrorBanner`, `JumpToLatest`, `StreamingIndicator`).
|
|
653
|
-
|
|
654
|
-
Highlights:
|
|
655
|
-
- Token coalescing (~16ms) → ≤1 render per frame during fast streams.
|
|
656
|
-
- Plain-text rendering during stream, full markdown on done.
|
|
657
|
-
- Memoized bubbles by `(id, content, isStreaming, version, toolCalls, sources, attachments)`.
|
|
658
|
-
- `100dvh`, safe-area inset, 16px textarea (no iOS focus-zoom).
|
|
659
|
-
- `role="log"` + `aria-live="polite"`, `aria-busy` on streaming bubbles, `role="alert"` errors.
|
|
660
|
-
- Cancel keeps partial text with `[cancelled]` marker.
|
|
661
|
-
- Tool calls — auto-open while running, auto-close on completion. Pluggable payload renderers via `dispatchToolPayload(matchers, fallback)`.
|
|
662
|
-
- Personas — `config.user` + `config.assistant` for avatar/name, `message.sender` for per-message overrides (multi-user chats).
|
|
663
|
-
- Attachments — `AttachmentsGrid` (thumbs) + `AttachmentsList` (rich renderers); per-type registry `{ image, audio, video, file }` plus `onAttachmentOpen` for host-side lightbox.
|
|
664
|
-
- Audio triggers — optional sounds on send / receive / stream start / error / mention. iOS unlock + cross-tab persisted volume / mute. Off by default.
|
|
665
|
-
- Slots — `header`, `footer`, `banner`, `empty`, `composerToolbarStart/End`, `composerAttachmentTray`, `jumpToLatest` (plus render-prop variants).
|
|
666
|
-
- Helpers — `useChatLightbox`, `collectImageAttachments` for swapping in `<LazyImageViewer>` host-side.
|
|
667
|
-
|
|
668
|
-
Heavy renderers stay opt-in subpath imports — `ui-tools/Chat` doesn't pull `AudioPlayer`/`ImageViewer`/`Map`/`JsonTree` into its dep graph.
|
|
669
|
-
|
|
670
|
-
Full docs in [`src/tools/Chat/README.md`](src/tools/Chat/README.md). Implementation plan in [`@dev/@refactoring7-chat/`](@dev/@refactoring7-chat/).
|
|
671
|
-
|
|
672
|
-
## Markdown Message
|
|
673
|
-
|
|
674
|
-
Read-only markdown renderer tuned for chat / agent transcripts. Built
|
|
675
|
-
on `react-markdown` + `remark-gfm` + `rehype-sanitize`, with a few
|
|
676
|
-
chat-specific affordances on top:
|
|
677
|
-
|
|
678
|
-
- Syntax-highlighted code blocks with a hover-revealed Copy button
|
|
679
|
-
(delegates to `PrettyCode` from this package).
|
|
680
|
-
- Mermaid diagram rendering for ` ```mermaid ` fences.
|
|
681
|
-
- Plain-text fast path: when content has no markdown syntax we skip
|
|
682
|
-
ReactMarkdown entirely and just render the string with
|
|
683
|
-
`whitespace: pre-line`. Cheaper, identical visual.
|
|
684
|
-
- Optional collapsible "Read more..." for long messages.
|
|
685
|
-
- User vs assistant styling modes (`isUser` prop).
|
|
686
|
-
- **`linkRules` API** — declarative handling of custom URL schemes
|
|
687
|
-
(e.g. `cmdop://machine/<uuid>` → render as a chip; `obsidian://`
|
|
688
|
-
→ open in a viewer). One prop replaces the per-consumer
|
|
689
|
-
custom-`a`/sanitize/urlTransform boilerplate.
|
|
690
|
-
|
|
691
|
-
```tsx
|
|
692
|
-
import { MarkdownMessage } from '@djangocfg/ui-tools';
|
|
693
|
-
|
|
694
|
-
<MarkdownMessage
|
|
695
|
-
content="# Hello\n\nThis is **bold** text and `inline code`."
|
|
696
|
-
isCompact={false}
|
|
697
|
-
/>
|
|
698
|
-
|
|
699
|
-
// Chat user message — primary-tinted bubble styling
|
|
700
|
-
<MarkdownMessage content={msg} isUser />
|
|
701
|
-
|
|
702
|
-
// Long content with "Read more..."
|
|
703
|
-
<MarkdownMessage
|
|
704
|
-
content={longText}
|
|
705
|
-
collapsible
|
|
706
|
-
maxLength={300}
|
|
707
|
-
maxLines={5}
|
|
708
|
-
/>
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
### Custom URL schemes — `linkRules`
|
|
712
|
-
|
|
713
|
-
For any chat that emits its own URL schemes — `cmdop://machine/<uuid>`
|
|
714
|
-
mention chips, `obsidian://open?path=…` deep-links, custom file
|
|
715
|
-
viewers — the recommended approach is `linkRules`:
|
|
163
|
+
import {
|
|
164
|
+
VoiceComposerSlot,
|
|
165
|
+
ChatHeaderLanguageButton,
|
|
166
|
+
} from '@djangocfg/ui-tools/speech-recognition';
|
|
716
167
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
const machineMention: LinkRule = {
|
|
721
|
-
name: 'machine-mention',
|
|
722
|
-
protocols: ['cmdop'],
|
|
723
|
-
|
|
724
|
-
// Optional: rewrite the source markdown before render. Useful when
|
|
725
|
-
// your composer adds a decorative `@` outside the link
|
|
726
|
-
// (`@[label](href)`) — the chip itself reads as the mention
|
|
727
|
-
// indicator, so rendering "@<chip>" looks like "@@label".
|
|
728
|
-
preprocess: (source) =>
|
|
729
|
-
source.replace(
|
|
730
|
-
/(^|[^A-Za-z0-9_])@(\[[^\]]+\]\(cmdop:\/\/machine\/[^)\s]+\))/g,
|
|
731
|
-
'$1$2',
|
|
732
|
-
),
|
|
733
|
-
|
|
734
|
-
// Predicate against the resolved href.
|
|
735
|
-
match: (href) => href.startsWith('cmdop://machine/'),
|
|
736
|
-
|
|
737
|
-
// Render whatever you want. `children` is the link's React label.
|
|
738
|
-
render: ({ href, children, isUser }) => {
|
|
739
|
-
const id = href.slice('cmdop://machine/'.length);
|
|
740
|
-
return <MentionChip id={id} isUser={isUser}>{children}</MentionChip>;
|
|
741
|
-
},
|
|
742
|
-
};
|
|
743
|
-
|
|
744
|
-
<MarkdownMessage
|
|
745
|
-
content="Talk to @[Vps-audi](cmdop://machine/abc-123) about deployment."
|
|
746
|
-
linkRules={[machineMention]}
|
|
168
|
+
<ChatRoot
|
|
169
|
+
transport={transport}
|
|
170
|
+
composerToolbarEnd={<VoiceComposerSlot />}
|
|
747
171
|
/>
|
|
748
|
-
```
|
|
749
|
-
|
|
750
|
-
#### Why `linkRules` and not just `customComponents.a`
|
|
751
|
-
|
|
752
|
-
Three concerns have to be aligned for a custom URL scheme to make it
|
|
753
|
-
intact to your renderer, and `customComponents` alone covers only one
|
|
754
|
-
of them:
|
|
755
172
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
| **Sanitize whitelist** | `rehype-sanitize` strips href values for any protocol it doesn't recognise — your renderer receives `href={undefined}`. |
|
|
759
|
-
| **`urlTransform`** | `react-markdown`'s default `urlTransform` runs *before* sanitize and blanks unrecognised schemes the same way — sanitize whitelist is moot if this layer already nuked the href. |
|
|
760
|
-
| **Source preprocess** | Your composer might emit a shape like `@[label](cmdop://...)`; the leading `@` lives outside the link and needs to be stripped before render or the chip ends up next to a literal `@`. |
|
|
761
|
-
|
|
762
|
-
`linkRules` collapses all three into a single declaration:
|
|
763
|
-
- `protocols` is unioned into the sanitize schema.
|
|
764
|
-
- The same protocol opts in the `urlTransform`.
|
|
765
|
-
- `preprocess` runs ahead of render.
|
|
766
|
-
- `match` + `render` replace the per-rule `<a>`.
|
|
767
|
-
|
|
768
|
-
You can still pass `customComponents` and `extraHrefProtocols`
|
|
769
|
-
alongside; rules win on URLs they `match`, everything else falls
|
|
770
|
-
through to your `customComponents.a` (or the built-in chat anchor).
|
|
771
|
-
|
|
772
|
-
### Anatomy
|
|
773
|
-
|
|
774
|
-
The component lives at `src/components/markdown/MarkdownMessage/`:
|
|
775
|
-
|
|
776
|
-
```
|
|
777
|
-
MarkdownMessage/
|
|
778
|
-
├── MarkdownMessage.tsx # composition only
|
|
779
|
-
├── types.ts # MarkdownMessageProps + LinkRule
|
|
780
|
-
├── sanitize.ts # buildSchema + buildUrlTransform
|
|
781
|
-
├── components.tsx # createMarkdownComponents (h/p/ul/a/pre/...)
|
|
782
|
-
├── CodeBlock.tsx # code block with copy button + fallback
|
|
783
|
-
├── CollapseToggle.tsx # "Read more..." button
|
|
784
|
-
├── linkRules.ts # rule application helpers
|
|
785
|
-
├── plainText.ts # hasMarkdownSyntax + extractTextFromChildren
|
|
786
|
-
└── index.ts # public re-exports
|
|
173
|
+
// Plus a flag-button language picker in the dock header:
|
|
174
|
+
<ChatLauncher dock={{ headerActions: <ChatHeaderLanguageButton /> }}>
|
|
787
175
|
```
|
|
788
176
|
|
|
789
|
-
|
|
790
|
-
contract (`MarkdownMessage`, `MarkdownMessageProps`, `LinkRule`,
|
|
791
|
-
`extractTextFromChildren`) re-exported from
|
|
792
|
-
`@djangocfg/ui-tools` directly.
|
|
177
|
+
**What's wired by default:** auto-hide on Firefox / in-app WebViews / missing `getUserMedia` (via `useVoiceSupport`); live interim+final stream into the composer via `ComposerHandle` (works transparently for the built-in textarea Composer and a TipTap MarkdownEditor); typed prefix anchored; focus + cursor-to-end on start and every partial; 90-second countdown + 2.5-second silence auto-stop; Esc cancels (without closing the chat) / Enter finishes (without submitting); persisted prefs (`djangocfg-stt:prefs`); `<SpeechRecognitionProvider>` for sharing engine state across the tree. Language picker shows 66 BCP-47 tags sourced from the Chrome Web Speech demo with country flags. Custom engines through `createHttpEngine` (REST/Whisper), `createWebSocketEngine` (Deepgram-style streaming), or `createExternalEngine` (Wails / Tauri / native sidecar — `onStart` / `onStop` / `subscribe` and you're done).
|
|
793
178
|
|
|
794
|
-
|
|
179
|
+
Full docs: [`SpeechRecognition/README.md`](src/tools/SpeechRecognition/README.md). Stories: `Tools/SpeechRecognition/{Basic, DictationField, PushToTalk, MicMeter, CustomEngine: HTTP, CustomEngine: WebSocket, Language & Device, Errors}` + `Tools/Chat/{Voice composer, Launcher}` (flag-button picker in the dock header). Unit-tested with vitest (`pnpm test`, 21 cases).
|
|
795
180
|
|
|
796
|
-
|
|
797
|
-
|-----------|-------------|
|
|
798
|
-
| `MarkdownMessage` | Read-only markdown renderer with custom-URL-scheme support via `linkRules` (see above) |
|
|
799
|
-
| `Markdown` | Generic markdown renderer with GFM support |
|
|
181
|
+
---
|
|
800
182
|
|
|
801
|
-
##
|
|
183
|
+
## Markdown
|
|
802
184
|
|
|
803
|
-
|
|
804
|
-
|-------|-------------|
|
|
805
|
-
| `useMediaCacheStore` | Media caching for video/audio players |
|
|
806
|
-
|
|
807
|
-
## Cron Scheduler
|
|
808
|
-
|
|
809
|
-
Compact cron expression builder with intuitive UI. Supports Daily, Weekly, Monthly schedules and custom cron expressions.
|
|
810
|
-
|
|
811
|
-
```tsx
|
|
812
|
-
import { CronScheduler } from '@djangocfg/ui-tools';
|
|
813
|
-
|
|
814
|
-
<CronScheduler
|
|
815
|
-
value="0 9 * * 1-5"
|
|
816
|
-
onChange={(cron) => console.log(cron)}
|
|
817
|
-
showPreview
|
|
818
|
-
allowCopy
|
|
819
|
-
/>
|
|
820
|
-
```
|
|
821
|
-
|
|
822
|
-
### Props
|
|
823
|
-
|
|
824
|
-
| Prop | Type | Default | Description |
|
|
825
|
-
|------|------|---------|-------------|
|
|
826
|
-
| `value` | `string` | - | Cron expression (Unix 5-field format) |
|
|
827
|
-
| `onChange` | `(cron: string) => void` | - | Callback when schedule changes |
|
|
828
|
-
| `defaultType` | `'daily' \| 'weekly' \| 'monthly' \| 'custom'` | `'daily'` | Initial schedule type |
|
|
829
|
-
| `showPreview` | `boolean` | `true` | Show human-readable preview |
|
|
830
|
-
| `showCronExpression` | `boolean` | `true` | Show cron expression in preview |
|
|
831
|
-
| `allowCopy` | `boolean` | `false` | Enable copy to clipboard |
|
|
832
|
-
| `timeFormat` | `'12h' \| '24h'` | `'24h'` | Time display format |
|
|
833
|
-
| `disabled` | `boolean` | `false` | Disable all interactions |
|
|
834
|
-
|
|
835
|
-
### Context Hooks
|
|
836
|
-
|
|
837
|
-
For custom compositions, use the context hooks:
|
|
838
|
-
|
|
839
|
-
```tsx
|
|
840
|
-
import {
|
|
841
|
-
CronSchedulerProvider,
|
|
842
|
-
useCronType,
|
|
843
|
-
useCronTime,
|
|
844
|
-
useCronWeekDays,
|
|
845
|
-
useCronMonthDays,
|
|
846
|
-
useCronPreview,
|
|
847
|
-
} from '@djangocfg/ui-tools';
|
|
848
|
-
```
|
|
849
|
-
|
|
850
|
-
### Utilities
|
|
851
|
-
|
|
852
|
-
```tsx
|
|
853
|
-
import {
|
|
854
|
-
buildCron, // State → Cron expression
|
|
855
|
-
parseCron, // Cron → State
|
|
856
|
-
humanizeCron, // Cron → Human description
|
|
857
|
-
isValidCron, // Validate cron syntax
|
|
858
|
-
} from '@djangocfg/ui-tools';
|
|
859
|
-
```
|
|
860
|
-
|
|
861
|
-
## Image Viewer
|
|
862
|
-
|
|
863
|
-
Image viewer with zoom, pan, rotate, flip and gallery navigation.
|
|
185
|
+
Read-only `<MarkdownMessage>` powers every chat bubble. Stays useful standalone for docs, AI replies, system messages.
|
|
864
186
|
|
|
865
187
|
```tsx
|
|
866
|
-
import {
|
|
867
|
-
|
|
868
|
-
// Single image
|
|
869
|
-
<div className="w-full h-[500px]">
|
|
870
|
-
<ImageViewer
|
|
871
|
-
images={[{ file: { name: 'photo.jpg', path: '/images/photo.jpg' }, src: '/images/photo.jpg' }]}
|
|
872
|
-
/>
|
|
873
|
-
</div>
|
|
188
|
+
import { MarkdownMessage } from '@djangocfg/ui-tools';
|
|
874
189
|
|
|
875
|
-
|
|
876
|
-
<div className="w-full h-[500px]">
|
|
877
|
-
<ImageViewer
|
|
878
|
-
images={[
|
|
879
|
-
{ file: { name: 'Photo 1', path: 'p1' }, src: 'https://example.com/1.jpg' },
|
|
880
|
-
{ file: { name: 'Photo 2', path: 'p2' }, src: 'https://example.com/2.jpg' },
|
|
881
|
-
{ file: { name: 'Photo 3', path: 'p3' }, src: 'https://example.com/3.jpg' },
|
|
882
|
-
]}
|
|
883
|
-
initialIndex={0}
|
|
884
|
-
/>
|
|
885
|
-
</div>
|
|
190
|
+
<MarkdownMessage content="# Hello\n\nGFM + emoji 😄 + code:\n\n```ts\nconst x = 1;\n```" />
|
|
886
191
|
```
|
|
887
192
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
| Prop | Type | Default | Description |
|
|
891
|
-
|------|------|---------|-------------|
|
|
892
|
-
| `images` | `ImageItem[]` | required | Array of images to display |
|
|
893
|
-
| `initialIndex` | `number` | `0` | Index of the image to show first |
|
|
894
|
-
| `inDialog` | `boolean` | `false` | Hide expand button (for nested usage) |
|
|
895
|
-
|
|
896
|
-
### Keyboard Shortcuts
|
|
193
|
+
GFM, soft line breaks, smart typography, emoji shortcodes, sanitized HTML, mermaid blocks, syntax-highlighted code, declarative `linkRules` for custom URL schemes (e.g. `cmdop://`, `vehicle://`). Details: [`MarkdownMessage/README.md`](src/components/markdown/MarkdownMessage/README.md).
|
|
897
194
|
|
|
898
|
-
|
|
899
|
-
|-----|--------|
|
|
900
|
-
| `+` / `=` | Zoom in |
|
|
901
|
-
| `-` | Zoom out |
|
|
902
|
-
| `0` | Reset to fit |
|
|
903
|
-
| `R` | Rotate 90° |
|
|
904
|
-
| `←` | Previous image (gallery) |
|
|
905
|
-
| `→` | Next image (gallery) |
|
|
195
|
+
---
|
|
906
196
|
|
|
907
|
-
##
|
|
197
|
+
## Map
|
|
908
198
|
|
|
909
|
-
|
|
199
|
+
MapLibre GL maps with markers, clusters, layers, drawing, geocoding. Subpath: `@djangocfg/ui-tools/map`. Details: [`Map/README.md`](src/tools/Map/README.md).
|
|
910
200
|
|
|
911
201
|
```tsx
|
|
912
|
-
import {
|
|
913
|
-
|
|
914
|
-
// Full mode (default) - with toolbar (Expand All, Copy, Download)
|
|
915
|
-
<LazyJsonTree data={obj} mode="full" />
|
|
916
|
-
|
|
917
|
-
// Compact mode - no toolbar, subtle border
|
|
918
|
-
<LazyJsonTree data={obj} mode="compact" />
|
|
202
|
+
import { MapContainer, MapMarker } from '@djangocfg/ui-tools/map';
|
|
919
203
|
|
|
920
|
-
|
|
921
|
-
<
|
|
204
|
+
<MapContainer style="streets" center={[37.6, 55.7]} zoom={10}>
|
|
205
|
+
<MapMarker position={[37.6, 55.7]} />
|
|
206
|
+
</MapContainer>
|
|
922
207
|
```
|
|
923
208
|
|
|
924
|
-
|
|
925
|
-
|------|---------|--------|----------|
|
|
926
|
-
| `full` | Yes | Yes | Standalone viewer |
|
|
927
|
-
| `compact` | No | Subtle | Cards, panels |
|
|
928
|
-
| `inline` | No | No | Embedded in lists, logs |
|
|
929
|
-
|
|
930
|
-
## Lazy Loading
|
|
931
|
-
|
|
932
|
-
All heavy tools have unified lazy-loaded versions with built-in Suspense fallbacks:
|
|
209
|
+
Optional drawing/geocoding peers: `pnpm add mapbox-gl-draw geocoder-control`.
|
|
933
210
|
|
|
934
|
-
|
|
935
|
-
import {
|
|
936
|
-
LazyMapContainer, // ~800KB
|
|
937
|
-
LazyMermaid, // ~800KB
|
|
938
|
-
LazyPrettyCode, // ~500KB
|
|
939
|
-
LazyOpenapiViewer, // ~400KB
|
|
940
|
-
LazyJsonSchemaForm, // ~300KB
|
|
941
|
-
LazyLottiePlayer, // ~200KB
|
|
942
|
-
LazyAudioPlayer, // ~80KB — WebView-safe v6 player
|
|
943
|
-
LazyVideoPlayer, // ~150KB
|
|
944
|
-
LazyJsonTree, // ~100KB
|
|
945
|
-
LazyImageViewer, // ~50KB
|
|
946
|
-
LazyCronScheduler, // ~15KB
|
|
947
|
-
} from '@djangocfg/ui-tools';
|
|
211
|
+
---
|
|
948
212
|
|
|
949
|
-
|
|
950
|
-
<LazyMermaid chart={diagram} />
|
|
951
|
-
<LazyMapContainer initialViewport={viewport} />
|
|
952
|
-
<LazyVideoPlayer src="/video.mp4" />
|
|
953
|
-
```
|
|
954
|
-
|
|
955
|
-
### Custom Lazy Components
|
|
213
|
+
## Storybook
|
|
956
214
|
|
|
957
|
-
|
|
215
|
+
Stories live next to each tool and are registered in [`src/stories/index.ts`](src/stories/index.ts):
|
|
958
216
|
|
|
959
|
-
```
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
const LazyMyComponent = createLazyComponent(
|
|
963
|
-
() => import('./MyHeavyComponent'),
|
|
964
|
-
{
|
|
965
|
-
displayName: 'LazyMyComponent',
|
|
966
|
-
fallback: <CardLoadingFallback title="Loading..." minHeight={200} />,
|
|
967
|
-
}
|
|
968
|
-
);
|
|
217
|
+
```bash
|
|
218
|
+
pnpm playground
|
|
969
219
|
```
|
|
970
220
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
Built-in fallback components for different use cases:
|
|
221
|
+
Open browser to the URL printed in the console. Categories: `Tools/<ToolName>` plus per-feature subsections for Chat and Map.
|
|
974
222
|
|
|
975
|
-
|
|
976
|
-
|-----------|----------|
|
|
977
|
-
| `LoadingFallback` | Generic spinner with optional text |
|
|
978
|
-
| `CardLoadingFallback` | Card-styled loading with title |
|
|
979
|
-
| `MapLoadingFallback` | Map-specific with location icon |
|
|
980
|
-
| `Spinner` | Simple spinning loader |
|
|
981
|
-
| `LazyWrapper` | Suspense wrapper with configurable fallback |
|
|
223
|
+
---
|
|
982
224
|
|
|
983
225
|
## Requirements
|
|
984
226
|
|
|
985
|
-
- React
|
|
986
|
-
- Tailwind CSS
|
|
987
|
-
-
|
|
988
|
-
- @djangocfg/ui-core (peer dependency)
|
|
989
|
-
|
|
990
|
-
### Optional Dependencies (for Map)
|
|
227
|
+
- React 18+ / React 19
|
|
228
|
+
- Tailwind CSS (or import `@djangocfg/ui-tools/dist.css`)
|
|
229
|
+
- Peer deps for individual tools auto-installed by your package manager.
|
|
991
230
|
|
|
992
|
-
|
|
993
|
-
# For drawing tools
|
|
994
|
-
pnpm add @mapbox/mapbox-gl-draw
|
|
995
|
-
|
|
996
|
-
# For geocoding/search
|
|
997
|
-
pnpm add @maplibre/maplibre-gl-geocoder
|
|
998
|
-
```
|
|
231
|
+
---
|
|
999
232
|
|
|
1000
233
|
## License
|
|
1001
234
|
|
|
1002
|
-
MIT
|
|
235
|
+
MIT — see [LICENSE](./LICENSE).
|