@djangocfg/ui-tools 2.1.402 → 2.1.407

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +16 -1
  2. package/package.json +11 -9
  3. package/src/tools/AudioPlayer/lazy.tsx +13 -27
  4. package/src/tools/ImageViewer/components/ImageViewer.tsx +10 -2
  5. package/src/tools/JsonForm/JsonSchemaForm.tsx +3 -1
  6. package/src/tools/JsonForm/README.md +12 -2
  7. package/src/tools/JsonForm/widgets/TextareaWidget.tsx +25 -0
  8. package/src/tools/JsonForm/widgets/index.ts +1 -0
  9. package/src/tools/JsonTree/README.md +12 -0
  10. package/src/tools/PrettyCode/README.md +81 -0
  11. package/src/tools/VideoPlayer/README.md +87 -230
  12. package/src/tools/VideoPlayer/VideoPlayer.tsx +82 -0
  13. package/src/tools/VideoPlayer/canvas/canvas-dispatcher.tsx +34 -0
  14. package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +38 -0
  15. package/src/tools/VideoPlayer/canvas/iframe-canvas.tsx +33 -0
  16. package/src/tools/VideoPlayer/canvas/index.ts +12 -0
  17. package/src/tools/VideoPlayer/canvas/jsx.d.ts +54 -0
  18. package/src/tools/VideoPlayer/canvas/native-canvas.tsx +38 -0
  19. package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +39 -0
  20. package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +77 -0
  21. package/src/tools/VideoPlayer/index.ts +51 -65
  22. package/src/tools/VideoPlayer/lazy.tsx +11 -54
  23. package/src/tools/VideoPlayer/parts/controls-bar.tsx +35 -0
  24. package/src/tools/VideoPlayer/parts/fullscreen.tsx +19 -0
  25. package/src/tools/VideoPlayer/parts/index.ts +15 -0
  26. package/src/tools/VideoPlayer/parts/pip.tsx +19 -0
  27. package/src/tools/VideoPlayer/parts/play-button.tsx +19 -0
  28. package/src/tools/VideoPlayer/parts/playback-rate.tsx +31 -0
  29. package/src/tools/VideoPlayer/parts/poster.tsx +3 -0
  30. package/src/tools/VideoPlayer/parts/seek-bar.tsx +26 -0
  31. package/src/tools/VideoPlayer/parts/volume.tsx +32 -0
  32. package/src/tools/VideoPlayer/styles/video-player.css +141 -0
  33. package/src/tools/VideoPlayer/types.ts +82 -0
  34. package/src/tools/VideoPlayer/utils/parse-embed-url.ts +70 -0
  35. package/src/tools/VideoPlayer/utils/vimeo-id.ts +24 -0
  36. package/src/tools/VideoPlayer/utils/youtube-id.ts +64 -0
  37. package/src/tools/index.ts +35 -28
  38. package/src/tools/VideoPlayer/components/VideoControls.tsx +0 -138
  39. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +0 -172
  40. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +0 -201
  41. package/src/tools/VideoPlayer/components/index.ts +0 -14
  42. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +0 -52
  43. package/src/tools/VideoPlayer/context/index.ts +0 -8
  44. package/src/tools/VideoPlayer/hooks/index.ts +0 -12
  45. package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +0 -71
  46. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +0 -117
  47. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +0 -284
  48. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +0 -505
  49. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +0 -397
  50. package/src/tools/VideoPlayer/providers/index.ts +0 -8
  51. package/src/tools/VideoPlayer/types/index.ts +0 -38
  52. package/src/tools/VideoPlayer/types/player.ts +0 -116
  53. package/src/tools/VideoPlayer/types/provider.ts +0 -93
  54. package/src/tools/VideoPlayer/types/sources.ts +0 -97
  55. package/src/tools/VideoPlayer/utils/debug.ts +0 -14
  56. package/src/tools/VideoPlayer/utils/fileSource.ts +0 -78
  57. package/src/tools/VideoPlayer/utils/index.ts +0 -12
  58. package/src/tools/VideoPlayer/utils/resolvers.ts +0 -75
package/README.md CHANGED
@@ -32,7 +32,7 @@ Sixteen tools, each one lazy-loaded so it doesn't ship until used. Bundle size i
32
32
  | `LottiePlayer` | ~200KB | Lottie animation player |
33
33
  | `Chat` | ~150KB | Streaming chat (SSE + tool calls + attachments). [README](src/tools/Chat/README.md) |
34
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 |
35
+ | `VideoPlayer` | ~12KB core | media-chrome player — YouTube / Vimeo / HLS / MP4 in one composable shell. [README](src/tools/VideoPlayer/README.md) |
36
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
37
  | `JsonTree` | ~100KB | JSON visualization (full/compact/inline modes) |
38
38
  | `AudioPlayer` | ~80KB | WebView-safe waveform player |
@@ -78,10 +78,23 @@ Subpaths come in three flavors:
78
78
  | `@djangocfg/ui-tools/markdown-editor` | `LazyMarkdownEditor`, `mentionPresets`, types | TipTap + ProseMirror (~200 KB) only loads via the lazy wrapper. |
79
79
  | `@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. |
80
80
  | `@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. |
81
+ | `@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) |
81
82
  | `@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` |
83
+ | `@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. |
82
84
  | `@djangocfg/ui-tools/styles` | Tailwind source CSS | |
83
85
  | `@djangocfg/ui-tools/dist.css` | Pre-compiled CSS | |
84
86
 
87
+ ### Code & data inspectors are always-dark by design
88
+
89
+ `PrettyCode` and `JsonTree` render on a fixed dark surface (`#0d1117`)
90
+ regardless of the host UI theme. Same convention as GitHub, VSCode,
91
+ ChatGPT, Chrome DevTools. Syntax highlighting / typed-value coloring
92
+ ship their own contrast model — mixing them with light UI surfaces
93
+ flattens everything into low-contrast pastels.
94
+
95
+ The chrome (border, padding, toolbars) still uses semantic UI tokens.
96
+ Only the *content surface* is fixed. See per-tool READMEs for details.
97
+
85
98
  ---
86
99
 
87
100
  ## Lazy loading
@@ -146,6 +159,8 @@ function Chat() {
146
159
 
147
160
  **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.
148
161
 
162
+ Need a fully custom input row (e.g. mention autocomplete via `MarkdownEditor`)? Pass `renderComposer={({ composer }) => <YourComposer composer={composer} />}` to `<ChatRoot>` — it replaces the default `<Composer>` while keeping autoscroll, JumpToLatest, and history behaviour. Story: `UI Tools / Chat / Mentions / Machine Mentions`.
163
+
149
164
  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. 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.
150
165
 
151
166
  Full docs: [`Chat/README.md`](src/tools/Chat/README.md). Stories: `Tools/Chat/{Basic,Bubbles,ToolCalls,Personas,Launcher,Header,Audio & Actions,Voice composer}`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-tools",
3
- "version": "2.1.402",
3
+ "version": "2.1.407",
4
4
  "description": "Heavy React tools with lazy loading - for Electron, Vite, CRA, Next.js apps",
5
5
  "keywords": [
6
6
  "ui-tools",
@@ -159,8 +159,8 @@
159
159
  "test:watch": "vitest"
160
160
  },
161
161
  "peerDependencies": {
162
- "@djangocfg/i18n": "^2.1.402",
163
- "@djangocfg/ui-core": "^2.1.402",
162
+ "@djangocfg/i18n": "^2.1.407",
163
+ "@djangocfg/ui-core": "^2.1.407",
164
164
  "consola": "^3.4.2",
165
165
  "lodash-es": "^4.18.1",
166
166
  "lucide-react": "^0.545.0",
@@ -183,9 +183,10 @@
183
183
  "@tiptap/react": "^3.20.1",
184
184
  "@tiptap/starter-kit": "^3.20.1",
185
185
  "@tiptap/suggestion": "^3.20.1",
186
- "@vidstack/react": "next",
187
186
  "@wavesurfer/react": "^1.0.12",
187
+ "hls-video-element": "^1.5.11",
188
188
  "maplibre-gl": "^4.7.1",
189
+ "media-chrome": "^4.19.0",
189
190
  "media-icons": "next",
190
191
  "mermaid": "^11.12.0",
191
192
  "monaco-editor": "^0.55.1",
@@ -205,8 +206,9 @@
205
206
  "remark-emoji": "^5.0.2",
206
207
  "remark-gfm": "4.0.1",
207
208
  "remark-smartypants": "^3.0.2",
208
- "vidstack": "next",
209
- "wavesurfer.js": "^7.12.1"
209
+ "vimeo-video-element": "^1.7.2",
210
+ "wavesurfer.js": "^7.12.1",
211
+ "youtube-video-element": "^1.9.0"
210
212
  },
211
213
  "optionalDependencies": {
212
214
  "@mapbox/mapbox-gl-draw": "^1.4.3",
@@ -214,9 +216,9 @@
214
216
  "material-file-icons": "^2.4.0"
215
217
  },
216
218
  "devDependencies": {
217
- "@djangocfg/i18n": "^2.1.402",
218
- "@djangocfg/typescript-config": "^2.1.402",
219
- "@djangocfg/ui-core": "^2.1.402",
219
+ "@djangocfg/i18n": "^2.1.407",
220
+ "@djangocfg/typescript-config": "^2.1.407",
221
+ "@djangocfg/ui-core": "^2.1.407",
220
222
  "@types/lodash-es": "^4.17.12",
221
223
  "@types/mapbox__mapbox-gl-draw": "^1.4.8",
222
224
  "@types/node": "^24.7.2",
@@ -3,39 +3,25 @@
3
3
  /**
4
4
  * `@djangocfg/ui-tools/audio-player` subpath entrypoint.
5
5
  *
6
- * `LazyPlayer` is the only heavy export it dynamically imports the full
7
- * `Player` tree (PlayerShell + Layout + Cover + Waveform + Controls + audio
8
- * decoding helpers). Everything else listed here is light:
9
- * - types are erased,
10
- * - the Zustand store, context hooks, and selectors carry no UI,
11
- * - the slot components (`Cover`, `Title`, `PlayButton`, `Waveform`, …)
12
- * are presentational React components that read from PlayerContext
13
- * they only become meaningful inside a `<PlayerProvider>` (which only
14
- * exists inside `LazyPlayer` once loaded).
6
+ * `LazyPlayer` is exported as a direct (synchronous) alias of `Player`. We
7
+ * intentionally avoid `React.lazy` + `import('./Player')` here: under bundlers
8
+ * that pre-bundle subpath entries (Vite optimizeDeps in Next.js/Vite/SB), the
9
+ * dynamic import creates a second chunk that re-instantiates the React
10
+ * Contexts (AudioRefCtx/ControlsCtx/MetaCtx/StateCtx/LevelsCtx). The slot
11
+ * components and selector hooks re-exported below would then read from a
12
+ * different context instance than `<PlayerProvider>` writes to, which made
13
+ * `usePlayerAudio` throw "must be used inside <PlayerProvider>".
15
14
  *
16
- * Consumers building a fully custom layout: render `<LazyPlayer>` once to
17
- * get the provider, then arrange slot components inside via render-children
18
- * pattern, or use the bare `Player` re-exported from the root barrel.
15
+ * Heavy audio-decoding work (peaks, AudioContext) already happens lazily at
16
+ * runtime via effects inside `PlayerProvider`/`PlayerShell` there is no
17
+ * benefit to splitting the React shell behind a second chunk.
19
18
  */
20
19
 
21
- import { createLazyComponent } from '../../components';
22
- import type { PlayerProps } from './types';
23
-
24
20
  // ============================================================================
25
- // Lazy heavy component
21
+ // Player component (synchronous; previously lazy — see note above)
26
22
  // ============================================================================
27
23
 
28
- export const LazyPlayer = createLazyComponent<PlayerProps>(
29
- () => import('./Player').then((mod) => ({ default: mod.Player })),
30
- {
31
- displayName: 'LazyAudioPlayer',
32
- fallback: (
33
- <div className="rounded-lg border border-border/60 bg-card px-4 py-6 text-sm text-muted-foreground">
34
- Loading audio player…
35
- </div>
36
- ),
37
- },
38
- );
24
+ export { Player, Player as LazyPlayer } from './Player';
39
25
 
40
26
  // ============================================================================
41
27
  // Light surface — types, store, context, slot components, hooks
@@ -196,13 +196,21 @@ export function ImageViewer({
196
196
  wrapperClass="!w-full !h-full cursor-grab active:cursor-grabbing"
197
197
  contentClass="!w-full !h-full flex items-center justify-center"
198
198
  >
199
- <div className="relative">
199
+ {/*
200
+ Fill the TransformComponent content box so the children's
201
+ `max-w-full / max-h-full` resolve against the actual viewport
202
+ instead of the image's natural box. Without `w-full h-full`
203
+ this wrapper shrink-fits the image, which collapses the
204
+ max-* constraints and renders the image at intrinsic size —
205
+ visible as cropping / half-height in tall containers.
206
+ */}
207
+ <div className="relative w-full h-full flex items-center justify-center">
200
208
  {useProgressiveLoading && lqip && !isFullyLoaded && (
201
209
  <img
202
210
  src={lqip}
203
211
  alt=""
204
212
  aria-hidden="true"
205
- className="absolute inset-0 max-w-full max-h-full object-contain select-none"
213
+ className="absolute max-w-full max-h-full object-contain select-none"
206
214
  style={{ transform: transformStyle, filter: 'blur(20px)', transition: 'opacity 0.3s ease-out', opacity: isFullyLoaded ? 0 : 1 }}
207
215
  draggable={false}
208
216
  />
@@ -17,7 +17,7 @@ import {
17
17
  import { JsonFormContext, JsonSchemaFormProps } from './types';
18
18
  import { normalizeFormData, validateSchema } from './utils';
19
19
  import {
20
- CheckboxWidget, ColorWidget, NumberWidget, SelectWidget, SliderWidget, SwitchWidget, TextWidget
20
+ CheckboxWidget, ColorWidget, NumberWidget, SelectWidget, SliderWidget, SwitchWidget, TextareaWidget, TextWidget
21
21
  } from './widgets';
22
22
 
23
23
  /**
@@ -104,6 +104,7 @@ export function JsonSchemaForm<T = any>(props: JsonSchemaFormProps<T>) {
104
104
  const widgets: RegistryWidgetsType = useMemo(() => ({
105
105
  // Standard widget names (PascalCase) - used by RJSF internally
106
106
  TextWidget,
107
+ TextareaWidget,
107
108
  NumberWidget,
108
109
  CheckboxWidget,
109
110
  SelectWidget,
@@ -112,6 +113,7 @@ export function JsonSchemaForm<T = any>(props: JsonSchemaFormProps<T>) {
112
113
  SliderWidget,
113
114
  // Lowercase aliases - for uiSchema 'ui:widget' references
114
115
  text: TextWidget,
116
+ textarea: TextareaWidget,
115
117
  number: NumberWidget,
116
118
  checkbox: CheckboxWidget,
117
119
  select: SelectWidget,
@@ -3,10 +3,20 @@
3
3
  Schema-driven forms on top of [react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form) (RJSF) with `@djangocfg/ui` widgets, AJV8 validation, and a few custom extensions for compact playground-style UIs.
4
4
 
5
5
  ```tsx
6
- import { JsonSchemaForm } from '@djangocfg/ui-tools/json-form';
7
- // or, for code-splitting:
6
+ // Lazy entry what production code should import. Ships only the
7
+ // component reference + a Suspense fallback; the ~300KB RJSF bundle
8
+ // loads on first mount.
8
9
  import { LazyJsonSchemaForm } from '@djangocfg/ui-tools/json-form';
9
10
 
11
+ // Full entry — eager bundle. Use only for storybook / internal
12
+ // tooling that needs templates, widgets, or `evaluateDisabledWhen`
13
+ // at module scope. Pulls RJSF synchronously, no code-splitting.
14
+ import {
15
+ JsonSchemaForm,
16
+ ObjectFieldTemplate,
17
+ evaluateDisabledWhen,
18
+ } from '@djangocfg/ui-tools/json-form/full';
19
+
10
20
  const schema = {
11
21
  type: 'object',
12
22
  properties: {
@@ -0,0 +1,25 @@
1
+ "use client"
2
+
3
+ import React from 'react';
4
+
5
+ import type { WidgetProps } from '@rjsf/utils';
6
+
7
+ import { TextWidget } from './TextWidget';
8
+
9
+ /**
10
+ * Multiline text widget for JSON Schema Form. Thin wrapper around
11
+ * `TextWidget` that pins `options.widget = 'textarea'` so the
12
+ * underlying renderer reaches the `<textarea>` branch.
13
+ *
14
+ * Registered under the `textarea` ui:widget alias — schemas can opt
15
+ * into multiline editing without setting `ui:options.widget`:
16
+ *
17
+ * uiSchema = { description: { 'ui:widget': 'textarea' } }
18
+ *
19
+ * Honors all the same `ui:options` as `TextWidget` — most usefully
20
+ * `rows` (default `3`).
21
+ */
22
+ export function TextareaWidget(props: WidgetProps) {
23
+ const options = { ...(props.options ?? {}), widget: 'textarea' };
24
+ return <TextWidget {...props} options={options} />;
25
+ }
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  export { TextWidget } from './TextWidget';
9
+ export { TextareaWidget } from './TextareaWidget';
9
10
  export { NumberWidget } from './NumberWidget';
10
11
  export { CheckboxWidget } from './CheckboxWidget';
11
12
  export { SelectWidget } from './SelectWidget';
@@ -2,6 +2,18 @@
2
2
 
3
3
  Interactive JSON tree viewer with expand/collapse, copy, and download.
4
4
 
5
+ ## Why dark by default
6
+
7
+ JsonTree renders on a fixed dark surface (`#0d1117`) regardless of
8
+ the host UI theme — same convention as Chrome DevTools, Insomnia,
9
+ Bruno, Postman. JSON inspectors carry their own data-typed palette
10
+ (blue keys, green strings, orange numbers, red null) tuned for dark
11
+ backgrounds; mixing it with light UI tokens flattens values into
12
+ unreadable pastels.
13
+
14
+ The palette is intentionally **not** a semantic token. See PrettyCode
15
+ README for the same rationale.
16
+
5
17
  ## Structure
6
18
 
7
19
  ```
@@ -0,0 +1,81 @@
1
+ # PrettyCode
2
+
3
+ Syntax-highlighted code block. Prism-powered, dark-by-default surface.
4
+
5
+ ## Why dark by default
6
+
7
+ PrettyCode renders on a fixed dark surface (`#0d1117` + `vsDark` palette)
8
+ regardless of the host theme — the same convention as GitHub, VSCode,
9
+ ChatGPT, Slack. Syntax highlighting ships its own contrast model
10
+ (keyword / string / number / comment) that flattens to low-contrast
11
+ pastels when forced onto a light UI surface.
12
+
13
+ The surface is intentionally **not** a semantic token. Semantic tokens
14
+ (`--primary`, `--accent`, `--card`) describe UI affordances; code
15
+ display is data, not UI.
16
+
17
+ ## Structure
18
+
19
+ ```
20
+ PrettyCode/
21
+ ├── PrettyCode.client.tsx # Main component (Highlight + dark surface)
22
+ ├── registerPrismLanguages.ts # Lazy-load extra grammars (bash, ruby, java, php)
23
+ ├── index.tsx # Public re-export
24
+ └── lazy.tsx # Lazy-loaded export
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```tsx
30
+ import { PrettyCode } from '@djangocfg/ui-tools/code';
31
+
32
+ <PrettyCode data={sourceCode} language="typescript" />
33
+
34
+ // Inline snippet (used in markdown rendering)
35
+ <PrettyCode data={`const x = 1`} language="ts" inline />
36
+
37
+ // Capped at 50 visible lines, scrolls if longer
38
+ <PrettyCode data={longBlob} language="json" maxLines={50} />
39
+
40
+ // Chrome-less variant — embed inside another scroll container
41
+ <PrettyCode data={src} language="bash" variant="plain" />
42
+ ```
43
+
44
+ ## Props
45
+
46
+ | Prop | Type | Default | Description |
47
+ |------|------|---------|-------------|
48
+ | `data` | `string \| object` | — | Code string. Objects are `JSON.stringify`d. |
49
+ | `language` | `Language` | — | Prism language id. Unknown → plain text. |
50
+ | `mode` | `'dark' \| 'light'` | `'dark'` | Palette override. Default is always dark (see "Why dark"). |
51
+ | `inline` | `boolean` | `false` | Render as inline `<code>` (no card / toolbar). |
52
+ | `variant` | `'card' \| 'plain'` | `'card'` | `plain` strips border, background, and toolbar. |
53
+ | `isCompact` | `boolean` | `false` | 12px font instead of 14px. |
54
+ | `maxLines` | `number` | `undefined` | When set, block scrolls past this many lines. |
55
+ | `customBg` | `string` | — | Tailwind class to override the dark surface. |
56
+ | `scrollIsolation` | `boolean` | `true` | Block scroll capture until clicked (long snippets). |
57
+ | `className` | `string` | — | Extra classes on the wrapper. |
58
+
59
+ ## When to override `mode`
60
+
61
+ Almost never. The dark default is correct for ~all cases. Pass
62
+ `mode="light"` only for deliberate light-on-light renders (e.g. PDF
63
+ export targeting a printed page). The surface falls back to
64
+ `bg-card` semantic token in that mode.
65
+
66
+ ## Supported languages
67
+
68
+ Out of the box: `javascript`, `typescript`, `python`, `json`, `css`,
69
+ `markup` (html/xml), `sql`, `yaml`, `markdown`, `go`.
70
+
71
+ Lazy-loaded on first use: `bash`/`shell`, `ruby`, `java`, `php`.
72
+ Lazy load is tracked through `useEnsurePrismLanguages()` — the
73
+ component re-renders once the grammar resolves.
74
+
75
+ Unknown languages fall back to plain text (no highlighting, no error).
76
+
77
+ ## Storybook
78
+
79
+ `UI Tools / Code / PrettyCode` — `Playground`, `TypeScript`, `Json`,
80
+ `Python`, `Bash`, `Sql`, `Yaml`, `LongFileWithScroll`, `Inline`,
81
+ `Compact`, `LightMode`, `PlainVariant`.