@djangocfg/ui-tools 2.1.419 → 2.1.420

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 CHANGED
@@ -6,264 +6,91 @@
6
6
 
7
7
  # @djangocfg/ui-tools
8
8
 
9
- Heavy React tools with lazy loading. No Next.js dependency — works with Electron, Vite, CRA, any React environment.
9
+ Heavy, lazy-loaded React components for admin, devtools, and dashboards. Pairs with [`@djangocfg/ui-core`](../ui-core) (primitives + theme tokens).
10
10
 
11
- Part of [DjangoCFG](https://djangocfg.com).
11
+ Every tool is `React.lazy` by default — import what you use, pay only its bundle cost.
12
12
 
13
- ```bash
14
- pnpm add @djangocfg/ui-tools
15
- ```
16
-
17
- ---
18
-
19
- ## What's inside
20
-
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.
22
-
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 |
32
- | `LottiePlayer` | ~200KB | Lottie animation player |
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` | ~12KB core | media-chrome player — YouTube / Vimeo / HLS / MP4 in one composable shell. [README](src/tools/VideoPlayer/README.md) |
36
- | `MarkdownMessage` | ~120KB | Read-only chat-tuned markdown. **SSR-safe** — use as a Client Component, the result is server-rendered. [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 |
42
- | `Masonry` | ~8KB | Virtualised window-scroll masonry grid with column balancing. [README](src/tools/data/Masonry/README.md) |
43
-
44
- Plus utility primitives: `Tree`, `Tour` (with spotlight overlay), `FileIcon`, `UploadDropzone`.
13
+ ## Install
45
14
 
46
- ---
47
-
48
- ## Imports
49
-
50
- **There is no root barrel** — `import … from '@djangocfg/ui-tools'` is intentionally **not** supported. Always import from a tool subpath.
51
-
52
- Why: a root barrel plus per-tool subpaths would inevitably ship the same modules twice (the bundled `dist/` copy via the root, plus the raw `src/` copy via the subpath). Different physical modules mean different `React.createContext()` instances — `ChatProvider`, `MapProvider`, `TreeProvider` all silently break across the boundary.
53
-
54
- The TypeScript / bundler error you'll see is `Cannot find module '@djangocfg/ui-tools'`. That's intentional; fix it by picking a subpath:
55
-
56
- ```tsx
57
- import { LazyJsonTree } from '@djangocfg/ui-tools/json-tree';
58
- import { Gallery } from '@djangocfg/ui-tools/gallery';
59
- import { LazyMapContainer, MapMarker } from '@djangocfg/ui-tools/map';
60
- import { MarkdownMessage } from '@djangocfg/ui-tools/markdown-message';
61
- import { LazyEditor, useMonaco } from '@djangocfg/ui-tools/code-editor';
62
- import { LazyChat, createPydanticAIChatTransport } from '@djangocfg/ui-tools/chat';
63
- import { LazyMarkdownEditor, mentionPresets } from '@djangocfg/ui-tools/markdown-editor';
64
- import { LazyPlayer, PlayerProvider } from '@djangocfg/ui-tools/audio-player';
65
- // …same pattern for every tool
15
+ ```bash
16
+ pnpm add @djangocfg/ui-tools @djangocfg/ui-core
66
17
  ```
67
18
 
68
- Subpaths come in three flavors:
19
+ Wrap your app in `<UiProviders>` from `@djangocfg/ui-core` once at the root (gives Tooltip/Dialog/Toast context to every tool).
69
20
 
70
- - **Lazy + light surface.** The heavy component is exported only as a `Lazy*` wrapper; types, hooks, transports, slot components, and pure helpers are exported synchronously. This is the standard pattern.
71
- - **Lazy primitives co-exist with light primitives.** `./map` exports `LazyMapContainer` (heavy MapLibre GL) plus `MapMarker` / `MapPopup` etc. — the markers are thin `react-map-gl` wrappers that don't import the heavy lib at module scope.
72
- - **Synchronous, SSR-safe.** `./markdown-message` is intentionally not lazy — the component is `'use client'` but produces plain HTML, so Next renders it on the server.
21
+ ## Catalogue
73
22
 
74
- | Subpath | Ships | Notes |
75
- |---------|-------|-------|
76
- | `@djangocfg/ui-tools/chat` | `LazyChat`, `LazyChatLauncher`, plus the raw components (`ChatRoot`, `ChatLauncher`, `MessageBubble`, `Composer`, `MessageBlocks`, header buttons, …), the composer kit (`ComposerMenuButton`, `ComposerBanner`, `ComposerToolPill`, `ComposerModelPicker`, `ComposerRichTextarea`, …), transports (`createPydanticAIChatTransport`, `createHttpTransport`, …), hooks (`useChat`, `useChatComposer`, …), reducer/core/types/style tokens | Use the `Lazy*` wrappers when possible. The raw components are re-exported synchronously for callers that compose custom chat shells — importing them pulls the dependency graph eagerly. |
77
- | `@djangocfg/ui-tools/code-editor` | `LazyEditor`, `LazyDiffEditor`, `useMonaco`, `useEditor`, `useLanguage`, `useEditorTheme`, `EditorProvider`, types | Monaco (~550 KB) loads only when an editor mounts. `useMonaco` does its own dynamic import. |
78
- | `@djangocfg/ui-tools/audio-player` | `LazyPlayer`, `PlayerProvider`, selector hooks, slot components (`Cover`, `Title`, `PlayButton`, `Waveform`, …), peaks-cache helpers, store, types | Slot components are presentational — safe synchronous re-exports. They only do anything inside a `<LazyPlayer>` provider. |
79
- | `@djangocfg/ui-tools/markdown-editor` | `LazyMarkdownEditor`, `mentionPresets`, types | TipTap + ProseMirror (~200 KB) only loads via the lazy wrapper. |
80
- | `@djangocfg/ui-tools/map` | `LazyMapContainer`, `LazyMapView`, plus light primitives (`MapMarker`, `MapPopup`, `MapCluster`, `MapSource`, `MapLayer`, `MapControls`, `MapProvider`, types) | The heavy MapLibre GL chunk (~800 KB) only loads when `LazyMapContainer` actually mounts. Markers and popups are thin `react-map-gl` wrappers — exported synchronously. |
81
- | `@djangocfg/ui-tools/markdown-message` | `MarkdownMessage`, `ChatMessageRow`, `ActionRow`, `extractTextFromChildren`, types | **SSR-safe.** The component itself is `'use client'`, but rendering produces plain HTML — Next.js will pre-render it on the server when imported from a Client Component. Use this when you want the markdown renderer without dragging in the full chat. |
82
- | `@djangocfg/ui-tools/video-player` | `VideoPlayer` (+ `LazyVideoPlayer` alias), `parseEmbedUrl`, composable parts (`PlayButton`, `SeekBar`, `Volume`, `ControlsBar`, …), per-engine canvases, types | media-chrome shell — YouTube / Vimeo / HLS / MP4 / iframe behind one API. Provider engines (`youtube-video-element`, `hls-video-element`, …) are imported only by the matching canvas, so unused engines tree-shake. [README](src/tools/VideoPlayer/README.md) |
83
- | `@djangocfg/ui-tools/<tool-name>` | One tool | `mermaid`, `speech-recognition`, `json-tree`, `pretty-code`, `openapi-viewer`, `json-form`, `lottie-player`, `video-player`, `image-viewer`, `cron-scheduler`, `gallery`, `tour`, `tree`, `file-icon`, `upload` |
84
- | `@djangocfg/ui-tools/json-form/full` | `JsonSchemaForm`, `ObjectFieldTemplate`, `evaluateDisabledWhen`, all widgets / templates / utils | Eager bundle. Use only for storybook / internal tooling that needs the template + util APIs at module scope. Production code should import `LazyJsonSchemaForm` from `/json-form` instead. |
85
- | `@djangocfg/ui-tools/styles` | Tailwind source CSS | |
86
- | `@djangocfg/ui-tools/dist.css` | Pre-compiled CSS | |
23
+ | Group | Tools |
24
+ |---|---|
25
+ | `chat/` | ChatLauncher, MessageList, Composer, SuggestedPrompts |
26
+ | `data/` | DataGrid · DataTable · JsonTree · Kanban · Listbox · Masonry · Timeline · Tree |
27
+ | `dev/code/` | PrettyCode · DiffViewer · MarkdownMessage |
28
+ | `dev/api/` | OpenapiViewer · ApiRefTable · RequestViewer |
29
+ | `dev/ops/` | LogViewer · EnvTable |
30
+ | `dev/` (top) | Mermaid · Map |
31
+ | `forms/` | CodeEditor (Monaco) · JsonEditor · JsonForm · MarkdownEditor · NotionEditor · FileUpload · Uploader |
32
+ | `input/` | Combobox · CronScheduler (+ CronPreview) · Scroller · Sortable · SpeechRecognition |
33
+ | `media/` | AudioPlayer · VideoPlayer · ImageViewer · LottiePlayer · Gallery |
34
+ | `overlay/` | ResponsiveDialog · ScrollSpy · SelectionToolbar · Tour |
35
+ | `visual/charts/` | Gauge · ActivityGraph · CommitGraph · Sparkline |
36
+ | `visual/indicators/` | StatusIndicator · Fps · Rating |
37
+ | `visual/design/` | ColorPicker · ColorPalette · FileIcon |
38
+ | `visual/` (top) | Marquee · QRCode |
87
39
 
88
- ### Code & data inspectors are always-dark by design
40
+ Sub-grouping is internal public imports stay flat.
89
41
 
90
- `PrettyCode` and `JsonTree` render on a fixed dark surface (`#0d1117`)
91
- regardless of the host UI theme. Same convention as GitHub, VSCode,
92
- ChatGPT, Chrome DevTools. Syntax highlighting / typed-value coloring
93
- ship their own contrast model — mixing them with light UI surfaces
94
- flattens everything into low-contrast pastels.
95
-
96
- The chrome (border, padding, toolbars) still uses semantic UI tokens.
97
- Only the *content surface* is fixed. See per-tool READMEs for details.
98
-
99
- ---
100
-
101
- ## Lazy loading
102
-
103
- Every heavy tool ships in two flavors:
104
-
105
- - `Lazy*` — wrapped in `React.lazy` + `Suspense` + skeleton. Use by default.
106
- - bare tool (`Mermaid`, `JsonTree`, …) — for explicit loading control.
42
+ ## Usage
107
43
 
108
44
  ```tsx
109
- import { LazyChat } from '@djangocfg/ui-tools/chat';
45
+ import { JsonTree, LogViewer, DiffViewer, CronScheduler } from '@djangocfg/ui-tools';
110
46
 
111
- <LazyChat transport={transport} /> // loads on mount
112
- <LazyChat fallback={<MySkeleton />} ... /> // custom loading UI
47
+ <JsonTree data={response} compactHeader />
48
+ <LogViewer entries={logs} />
49
+ <DiffViewer oldCode={a} newCode={b} language="ts" view="split" />
50
+ <CronScheduler value={cron} onChange={setCron} />
113
51
  ```
114
52
 
115
- Skeletons match the tool's final layout so swap-in is jank-free.
116
-
117
- ---
118
-
119
- ## Chat — quick start
120
-
121
- 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.
53
+ Subpath imports avoid loading siblings:
122
54
 
123
55
  ```tsx
124
- import {
125
- ChatLauncher,
126
- ChatRoot,
127
- createPydanticAIChatTransport,
128
- } from '@djangocfg/ui-tools/chat';
129
-
130
- const transport = createPydanticAIChatTransport({
131
- buildStreamUrl: (sid, msg) => `${API}/chat/sessions/${sid}/stream?message=${encodeURIComponent(msg)}`,
132
- streamMethod: 'GET',
133
- buildHeaders: async () => ({ Authorization: `Bearer ${getToken()}` }),
134
- });
135
-
136
- function Chat() {
137
- // ChatLauncher mounts <ChatProvider> internally — pass transport / audio /
138
- // config here, then render <ChatRoot /> as a child without props.
139
- return (
140
- <ChatLauncher
141
- transport={transport}
142
- audio={{}} // ChatAudioConfig (sounds map). `{}` uses bundled defaults.
143
- hotkey={{ key: '/', meta: true }}
144
- fab={{ variant: 'animated' }} // size='responsive' default — phone/tablet/desktop
145
- dock={{ title: 'Assistant', height: 600 }}
146
- greeting="Hi 👋 Need help?"
147
- headerSlots={{ // declarative header buttons, rendered inside the provider
148
- languagePicker: true,
149
- modeToggle: { persistAs: 'my.chat.dock' },
150
- reset: {
151
- onReset: async () => { await api.clearChat(); return true; },
152
- },
153
- }}
154
- >
155
- <ChatRoot />
156
- </ChatLauncher>
157
- );
158
- }
56
+ import { JsonTree } from '@djangocfg/ui-tools/json-tree';
57
+ import { LogViewer } from '@djangocfg/ui-tools/log-viewer';
159
58
  ```
160
59
 
161
- **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.
162
-
163
- Need a fully custom input row (e.g. mention autocomplete via `MarkdownEditor`)? Pass `composer={{ render: ({ composer }) => <YourComposer composer={composer} /> }}` to `<ChatRoot>` — it replaces the default `<Composer>` while keeping autoscroll, JumpToLatest, and history behaviour. Story: `UI Tools / Chat / Composer / Mentions`.
164
-
165
- Drop `<VoiceComposerSlot />` from `@djangocfg/ui-tools/speech-recognition` into the composer's `slots.blockStart` (`composer={{ slots: { blockStart: <VoiceComposerSlot /> } }}`) for live mic-to-text — **zero props**, reads / writes the composer through `ComposerHandle` registered in chat context. Set `headerSlots={{ languagePicker: true }}` on `<ChatLauncher>` 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.
60
+ ## Theming
166
61
 
167
- **Page-context.** The assistant can see the page the user is on. The `page-snapshot` engine (`src/lib/page-snapshot`) walks the live DOM into a token-efficient, redacted snapshot; wire `getDynamicMetadata` on `<ChatRoot>` / `<ChatProvider>` (typically from `usePageSnapshot().getChatMetadata`) and it rides the request `metadata`. When the assistant replies with `point` directives, `<HighlightOverlay>` (`src/tools/Chat/highlight`) spotlights the referenced elements. Story: `UI Tools/Chat/Highlight`.
62
+ All tools render through `@djangocfg/ui-core` semantic tokens (`bg-card`, `text-foreground`, `border-border`, status surfaces) they adapt automatically to light/dark and to the theme presets in ui-core.
168
63
 
169
- Full docs: [`Chat/README.md`](src/tools/Chat/README.md). Stories are grouped under `UI Tools/Chat` into `Getting Started`, `Messages`, `Composer`, `Launcher`, `Transports`, `Highlight` — plus `Overview` and a one-screen `Showcase`.
64
+ Two intentional exceptions, both opt-in via prop:
170
65
 
171
- ---
66
+ - **`PrettyCode`** — fixed dark surface regardless of UI theme. Code blocks ship their own contrast model (GitHub/VSCode/ChatGPT convention); mixing with light UI produces low-contrast pastel renders. Override via `mode="light"`.
67
+ - **`DiffViewer`** — adaptive; uses `themes.github` on light, `themes.vsDark` on dark via `useResolvedTheme()`.
172
68
 
173
- <a id="speech-recognition--quick-start"></a>
69
+ Canvas/SVG components (charts, viz) sample theme colors via `useThemeColor`/`alpha` from `@djangocfg/ui-core/styles/palette` — never `color-mix`/`oklch` strings (Canvas2D rejects them).
174
70
 
175
- ## Speech Recognition — quick start
176
-
177
- 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.
178
-
179
- ```tsx
180
- import {
181
- DictationButton,
182
- TranscriptView,
183
- useSpeechRecognition,
184
- } from '@djangocfg/ui-tools/speech-recognition';
185
-
186
- function Dictate() {
187
- const rec = useSpeechRecognition(); // Web Speech engine, browser language
188
- return (
189
- <>
190
- <DictationButton status={rec.status} onClick={() => rec.toggle()} />
191
- <TranscriptView transcript={rec.transcript} />
192
- </>
193
- );
194
- }
195
- ```
196
-
197
- For the chat composer:
198
-
199
- ```tsx
200
- import { VoiceComposerSlot } from '@djangocfg/ui-tools/speech-recognition';
201
-
202
- <ChatRoot
203
- transport={transport}
204
- composer={{ slots: { blockStart: <VoiceComposerSlot /> } }}
205
- />
206
-
207
- // Plus a flag-button language picker in the dock header:
208
- <ChatLauncher headerSlots={{ languagePicker: true }} ... >
209
- ```
210
-
211
- **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).
212
-
213
- Full docs: [`SpeechRecognition/README.md`](src/tools/SpeechRecognition/README.md). Stories: `UI Tools/Speech/{Dictation, ComposerSlot, PushToTalk, MicMeter, Engines, Languages, Errors}` + `UI Tools/Chat/Composer/Speech & Attachments` and `UI Tools/Chat/Launcher/Playground` (flag-button picker in the dock header). Unit-tested with vitest (`pnpm test`, 21 cases).
214
-
215
- ---
216
-
217
- ## Markdown
218
-
219
- Read-only `<MarkdownMessage>` powers every chat bubble. Stays useful standalone for docs, AI replies, system messages.
220
-
221
- ```tsx
222
- import { MarkdownMessage } from '@djangocfg/ui-tools/markdown-message';
223
-
224
- <MarkdownMessage content="# Hello\n\nGFM + emoji 😄 + code:\n\n```ts\nconst x = 1;\n```" />
225
- ```
226
-
227
- 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/tools/MarkdownMessage/README.md).
228
-
229
- ---
230
-
231
- ## Map
232
-
233
- MapLibre GL maps with markers, clusters, layers, drawing, geocoding. Subpath: `@djangocfg/ui-tools/map`. Details: [`Map/README.md`](src/tools/Map/README.md).
234
-
235
- ```tsx
236
- import { MapContainer, MapMarker } from '@djangocfg/ui-tools/map';
71
+ ## Lazy loading
237
72
 
238
- <MapContainer style="streets" center={[37.6, 55.7]} zoom={10}>
239
- <MapMarker position={[37.6, 55.7]} />
240
- </MapContainer>
241
- ```
73
+ Every tool's default export is `React.lazy`-wrapped; initial bundle stays small. Heaviest:
242
74
 
243
- Optional drawing/geocoding peers: `pnpm add mapbox-gl-draw geocoder-control`.
75
+ Mermaid ~800KB · Monaco CodeEditor ~550KB · PrettyCode (shiki) ~500KB · OpenapiViewer ~400KB · JsonForm ~300KB · AudioPlayer (WaveSurfer) ~200KB · LottiePlayer ~200KB · NotionEditor (TipTap) ~200KB · VideoPlayer (media-chrome) ~150KB · JsonTree ~100KB · ImageViewer ~50KB · CronScheduler ~15KB.
244
76
 
245
- ---
77
+ Works in Next.js, Vite, Wails, CRA — no framework lock-in.
246
78
 
247
- ## Storybook
79
+ ## Build discipline
248
80
 
249
- Stories live next to each tool and are registered in [`src/stories/index.ts`](src/stories/index.ts):
81
+ After any `src/` change, **run `pnpm build`** before considering the change done — consumers pick up `dist/` via `pnpm sync:cfg`; a stale `dist/` ships old code silently. See [`CLAUDE.md`](./CLAUDE.md) for details.
250
82
 
251
83
  ```bash
252
- pnpm playground
84
+ pnpm build # rebuild dist/ (tsup)
85
+ pnpm check # tsc --noEmit
253
86
  ```
254
87
 
255
- Open browser to the URL printed in the console. Categories: `Tools/<ToolName>` plus per-feature subsections for Chat and Map.
256
-
257
- ---
258
-
259
88
  ## Requirements
260
89
 
261
- - React 18+ / React 19
262
- - Tailwind CSS (or import `@djangocfg/ui-tools/dist.css`)
263
- - Peer deps for individual tools auto-installed by your package manager.
264
-
265
- ---
90
+ - React 18 or 19
91
+ - `@djangocfg/ui-core` (peer)
92
+ - Tailwind CSS v4 (host imports `@djangocfg/ui-core/styles`)
266
93
 
267
94
  ## License
268
95
 
269
- MIT — see [LICENSE](./LICENSE).
96
+ MIT — © djangocfg.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-tools",
3
- "version": "2.1.419",
3
+ "version": "2.1.420",
4
4
  "description": "Heavy React tools with lazy loading - for Electron, Vite, CRA, Next.js apps",
5
5
  "keywords": [
6
6
  "ui-tools",
@@ -329,8 +329,8 @@
329
329
  "test:watch": "vitest"
330
330
  },
331
331
  "peerDependencies": {
332
- "@djangocfg/i18n": "^2.1.419",
333
- "@djangocfg/ui-core": "^2.1.419",
332
+ "@djangocfg/i18n": "^2.1.420",
333
+ "@djangocfg/ui-core": "^2.1.420",
334
334
  "consola": "^3.4.2",
335
335
  "lodash-es": "^4.18.1",
336
336
  "lucide-react": "^0.545.0",
@@ -405,9 +405,9 @@
405
405
  "@maplibre/maplibre-gl-geocoder": "^1.7.0"
406
406
  },
407
407
  "devDependencies": {
408
- "@djangocfg/i18n": "^2.1.419",
409
- "@djangocfg/typescript-config": "^2.1.419",
410
- "@djangocfg/ui-core": "^2.1.419",
408
+ "@djangocfg/i18n": "^2.1.420",
409
+ "@djangocfg/typescript-config": "^2.1.420",
410
+ "@djangocfg/ui-core": "^2.1.420",
411
411
  "@types/lodash-es": "^4.17.12",
412
412
  "@types/mapbox__mapbox-gl-draw": "^1.4.8",
413
413
  "@types/node": "^25.2.3",
@@ -0,0 +1,48 @@
1
+ # DataGrid
2
+
3
+ Headless data grid with built-in sort / filter / pagination / selection and column resizing. Composable: `<DataGrid>` is the all-in-one entry point, while `DataGridProvider` + `DataGridHeader` / `DataGridRow` / `DataGridCell` / `DataGridPagination` let you swap individual parts. Pure React, no `@tanstack/react-table` dependency.
4
+
5
+ ```tsx
6
+ import { DataGrid } from '@djangocfg/ui-tools/data-grid';
7
+
8
+ <DataGrid
9
+ data={users}
10
+ columns={[
11
+ { key: 'name', header: 'Name', sortable: true, filterable: true },
12
+ { key: 'email', header: 'Email', sortable: true },
13
+ { key: 'role', header: 'Role', align: 'right' },
14
+ ]}
15
+ getRowId={(u) => u.id}
16
+ selectionMode="multiple"
17
+ onSelectionChange={setSelected}
18
+ />
19
+ ```
20
+
21
+ ## Props
22
+
23
+ | Prop | Type | Default | Description |
24
+ |---|---|---|---|
25
+ | `data` | `T[]` | — | Row data. |
26
+ | `columns` | `DataGridColumn<T>[]` | — | Column defs (`key`, `header`, `cell?`, `sortable?`, `filterable?`, `width?`, `align?`, `filterFn?`). |
27
+ | `getRowId` | `(row: T) => string \| number` | — | Stable row id. |
28
+ | `selectionMode` | `'none' \| 'single' \| 'multiple'` | `'none'` | Selection behaviour. |
29
+ | `initialSelectedIds` / `onSelectionChange` | `RowId[]` / `(ids) => void` | — | Uncontrolled selection. |
30
+ | `sort` / `onSortChange` / `initialSort` | `DataGridSortState` | — | Controlled or uncontrolled sort. |
31
+ | `filters` / `onFilterChange` / `initialFilters` | `DataGridFilterState` | — | Per-column text filters. |
32
+ | `pagination` / `onPaginationChange` / `initialPagination` | `DataGridPaginationState` | — | Controlled or uncontrolled pagination. |
33
+ | `pageSizeOptions` | `number[]` | `[10, 25, 50, 100]` | Page-size choices. |
34
+ | `resizable` | `boolean` | `false` | Enable column resizing. |
35
+ | `stickyHeader` | `boolean` | `true` | Sticky header row. |
36
+ | `loading` | `boolean` | `false` | Loading state. |
37
+ | `emptyMessage` | `ReactNode` | — | Rendered when `data` is empty. |
38
+ | `getRowClassName` | `(row, i) => string` | — | Per-row class hook. |
39
+ | `onRowClick` / `onRowDoubleClick` | `(row) => void` | — | Row event handlers. |
40
+
41
+ ## Notes
42
+
43
+ - No virtualization — for >10k rows pair with pagination or use a virtualized list above the grid.
44
+ - All state slots support both controlled (`sort` + `onSortChange`) and uncontrolled (`initialSort`) usage.
45
+
46
+ ---
47
+
48
+ Adapted from jalcoui (MIT).
@@ -0,0 +1,42 @@
1
+ # DataTable
2
+
3
+ Lighter sibling of `DataGrid`. Same sort / filter / pagination / selection model, but no column resizing, no sticky header, no `width`/`minWidth`/`maxWidth` per column — use when you want a plain semantic `<table>` for read-heavy lists. For resizable / sticky-header grids reach for `DataGrid` instead.
4
+
5
+ ```tsx
6
+ import { DataTable } from '@djangocfg/ui-tools/data-table';
7
+
8
+ <DataTable
9
+ data={invoices}
10
+ columns={[
11
+ { key: 'number', header: '#', sortable: true },
12
+ { key: 'client', header: 'Client', filterable: true },
13
+ { key: 'total', header: 'Total', align: 'right' },
14
+ ]}
15
+ getRowId={(inv) => inv.id}
16
+ onRowClick={openInvoice}
17
+ />
18
+ ```
19
+
20
+ ## Props
21
+
22
+ | Prop | Type | Default | Description |
23
+ |---|---|---|---|
24
+ | `data` | `T[]` | — | Row data. |
25
+ | `columns` | `DataTableColumn<T>[]` | — | Column defs (`key`, `header`, `cell?`, `sortable?`, `filterable?`, `align?`, `filterFn?`). |
26
+ | `getRowId` | `(row: T) => string \| number` | — | Stable row id. |
27
+ | `selectionMode` | `'none' \| 'single' \| 'multiple'` | `'none'` | Selection behaviour. |
28
+ | `initialSelectedIds` / `onSelectionChange` | `RowId[]` / `(ids) => void` | — | Uncontrolled selection. |
29
+ | `sort` / `onSortChange` / `initialSort` | `DataTableSortState` | — | Controlled or uncontrolled sort. |
30
+ | `filters` / `onFilterChange` / `initialFilters` | `DataTableFilterState` | — | Per-column text filters. |
31
+ | `pagination` / `onPaginationChange` / `initialPagination` | `DataTablePaginationState` | — | Controlled or uncontrolled pagination. |
32
+ | `pageSizeOptions` | `number[]` | `[10, 25, 50, 100]` | Page-size choices. |
33
+ | `loading` | `boolean` | `false` | Loading state. |
34
+ | `emptyMessage` | `ReactNode` | — | Rendered when `data` is empty. |
35
+ | `getRowClassName` | `(row, i) => string` | — | Per-row class hook. |
36
+ | `onRowClick` / `onRowDoubleClick` | `(row) => void` | — | Row event handlers. |
37
+
38
+ Composable parts (`DataTableProvider`, `DataTableHeader`, `DataTableRow`, `DataTablePagination`) are exported for custom compositions.
39
+
40
+ ---
41
+
42
+ Adapted from jalcoui (MIT).
@@ -0,0 +1,46 @@
1
+ # Mermaid
2
+
3
+ Mermaid diagram renderer with a typed declarative builder layer on top of the raw `chart` string. Supports the diagram types covered by the builders (`FlowDiagram`, `SequenceDiagram`, `JourneyDiagram`) plus anything else Mermaid parses (gantt, class, state, ER, pie, …) via the `chart` prop. Click-to-fullscreen via the floating toolbar.
4
+
5
+ ```tsx
6
+ import Mermaid from '@djangocfg/ui-tools/mermaid';
7
+
8
+ <Mermaid chart={`
9
+ flowchart LR
10
+ A[Client] --> B[API] --> C[(DB)]
11
+ `} />
12
+ ```
13
+
14
+ Builder API:
15
+
16
+ ```tsx
17
+ import Mermaid, { FlowDiagram } from '@djangocfg/ui-tools/mermaid';
18
+
19
+ const chart = new FlowDiagram({ direction: 'LR' })
20
+ .node('a', 'Client')
21
+ .node('b', 'API')
22
+ .edge('a', 'b')
23
+ .build();
24
+
25
+ <Mermaid chart={chart} />
26
+ ```
27
+
28
+ ## Props
29
+
30
+ | Prop | Type | Default | Description |
31
+ |---|---|---|---|
32
+ | `chart` | `string` | — | Mermaid source. |
33
+ | `isCompact` | `boolean` | `false` | Tighter padding for embedded contexts. |
34
+ | `fullscreen` | `boolean` | `true` | Click-to-fullscreen via the floating toolbar. |
35
+ | `scrollIsolation` | `boolean` | `false` | Lock page wheel while hovering. Off by default — Mermaid SVGs don't scroll, the overlay just steals wheel events. |
36
+ | `debounceMs` | `number` | `300` | Re-render debounce after `chart` changes. Lower for static diagrams, higher while streaming. |
37
+ | `className` | `string` | — | — |
38
+
39
+ ## Notes
40
+
41
+ - **~800KB bundle** — entry point is already `lazy()`-wrapped with a `Suspense` spinner, so the cost is only paid when a diagram is actually mounted.
42
+ - Theming pulls from `useThemePalette` / `useStylePresets` / `useBoxColors` (exported alongside the builders) — Mermaid colors follow the active app theme; do not hard-code hex.
43
+
44
+ ---
45
+
46
+ Adapted from jalcoui (MIT).
@@ -0,0 +1,61 @@
1
+ # CronScheduler
2
+
3
+ Cron expression builder + read-only preview. Standard Unix 5-field format (`minute hour day-of-month month day-of-week`). Two surfaces:
4
+
5
+ - `<CronScheduler>` — interactive builder (daily / weekly / monthly / custom), popover or `inline`.
6
+ - `<CronPreview>` — standalone read-only summary with human description and next run times.
7
+
8
+ ```tsx
9
+ import { CronScheduler, CronPreview } from '@djangocfg/ui-tools/cron-scheduler';
10
+
11
+ // Edit
12
+ <CronScheduler value={cron} onChange={setCron} showCronExpression />
13
+
14
+ // Display
15
+ <CronPreview value="0 9 * * 1-5" nextRuns={5} />
16
+ ```
17
+
18
+ ## `<CronScheduler>` props
19
+
20
+ | Prop | Type | Default | Description |
21
+ |---|---|---|---|
22
+ | `value` | `string` | — | Current cron expression. |
23
+ | `onChange` | `(cron: string) => void` | — | Fires on every valid state change. |
24
+ | `defaultType` | `'daily' \| 'weekly' \| 'monthly' \| 'custom'` | `'daily'` | Initial mode. |
25
+ | `showCronExpression` | `boolean` | `false` | Render the raw expression under the trigger. |
26
+ | `allowCopy` | `boolean` | `false` | Copy-to-clipboard button. |
27
+ | `timeFormat` | `'12h' \| '24h'` | `'24h'` | Time picker format. |
28
+ | `inline` | `boolean` | `false` | Expand the editor in place instead of a popover. |
29
+ | `size` | `'default' \| 'sm'` | `'default'` | Compact fields (`sm`) for narrow side panels / modals. |
30
+ | `placeholder` | `string` | — | Trigger label when no schedule is set (popover mode). |
31
+ | `disabled` | `boolean` | `false` | Disable all interactions. |
32
+
33
+ ## `<CronPreview>` props
34
+
35
+ | Prop | Type | Default | Description |
36
+ |---|---|---|---|
37
+ | `value` | `string` | — | Cron expression to preview. Invalid values render a destructive-toned error card. |
38
+ | `title` | `string` | — | Optional heading above the summary. |
39
+ | `nextRuns` | `number` | `5` | Upcoming runs to list. `0` hides the list. |
40
+ | `showExpression` | `boolean` | `true` | Show the raw cron string alongside the summary. |
41
+ | `referenceDate` | `Date` | `new Date()` | Anchor for `getNextRuns`. |
42
+
43
+ ## Utilities
44
+
45
+ Exported alongside the components for custom UIs:
46
+
47
+ - `buildCron(state)` / `parseCron(expr)` — state ↔ expression
48
+ - `isValidCron(expr)` — boolean validator
49
+ - `humanizeCron(expr)` — human-readable summary string
50
+ - `getNextRuns(expr, n, from?)` / `formatNextRun(date)` — upcoming run times
51
+
52
+ Advanced compositions can use `CronSchedulerProvider` + the slice hooks (`useCronType`, `useCronTime`, `useCronWeekDays`, `useCronMonthDays`, `useCronCustom`, `useCronPreview`, `useCronSize`) and field components (`ScheduleTypeSelector`, `TimeSelector`, `DayChips`, `MonthDayGrid`, `CustomInput`, `SchedulePreview`).
53
+
54
+ ## Notes
55
+
56
+ - Builder is lazy-loaded (~15KB). `CronPreview` is not lazy — it ships with the entry module.
57
+ - `SchedulePreview` (inside the editor) and `CronPreview` (standalone) are distinct: the former reads from `CronSchedulerProvider`, the latter takes a value prop and stands alone.
58
+
59
+ ---
60
+
61
+ Adapted from jalcoui (MIT).
@@ -0,0 +1,45 @@
1
+ # Gauge
2
+
3
+ Accessible SVG meter (`role="meter"`) with composable parts: `Gauge` (root + context), `GaugeIndicator` (svg), `GaugeTrack`, `GaugeRange`, `GaugeValueText`, `GaugeLabel`. `GaugeCombined` is the all-in-one shorthand. Track / range colors come from Tailwind semantic tokens (`text-muted-foreground/20`, `text-primary`) — no Canvas, no `useThemePalette` hooks needed.
4
+
5
+ ```tsx
6
+ import {
7
+ Gauge,
8
+ GaugeIndicator,
9
+ GaugeTrack,
10
+ GaugeRange,
11
+ GaugeValueText,
12
+ } from '@djangocfg/ui-tools/gauge';
13
+
14
+ <Gauge value={72} size={140} thickness={10} startAngle={-120} endAngle={120}>
15
+ <GaugeIndicator>
16
+ <GaugeTrack />
17
+ <GaugeRange />
18
+ </GaugeIndicator>
19
+ <GaugeValueText />
20
+ </Gauge>
21
+ ```
22
+
23
+ ## `<Gauge>` props
24
+
25
+ | Prop | Type | Default | Description |
26
+ |---|---|---|---|
27
+ | `value` | `number \| null` | `null` | Current value. `null` / `undefined` → indeterminate state. Out-of-range values are clamped. |
28
+ | `min` | `number` | `0` | Lower bound. |
29
+ | `max` | `number` | `100` | Upper bound. Must be `> min`; otherwise auto-bumped. |
30
+ | `size` | `number` | `120` | Outer size in px. |
31
+ | `thickness` | `number` | `8` | Arc stroke width. Must be `< size`. |
32
+ | `startAngle` | `number` | `0` | Arc start in degrees (12 o'clock = 0). |
33
+ | `endAngle` | `number` | `360` | Arc end in degrees. `|end - start| >= 360` renders a full ring. |
34
+ | `getValueText` | `(value, min, max) => string` | percentage `0–100` | Override the text rendered inside `GaugeValueText` and announced via `aria-valuetext`. |
35
+ | `asChild` | `boolean` | `false` | Render-as via Radix `Slot`. |
36
+
37
+ ## Notes
38
+
39
+ - All sub-parts (`GaugeIndicator`, `GaugeTrack`, `GaugeRange`, `GaugeValueText`, `GaugeLabel`) **must** be rendered under a `<Gauge>` — they throw without the context.
40
+ - Override colors by passing `className` to `GaugeTrack` / `GaugeRange` (they use `currentColor`, so any `text-*` token works).
41
+ - State is exposed via `data-state="indeterminate" | "loading" | "complete"` on every part for custom styling.
42
+
43
+ ---
44
+
45
+ Adapted from jalcoui (MIT).