@djangocfg/widget-code 0.1.18 → 0.1.21
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 +5 -5
- package/package.json +11 -16
- package/src/diff-viewer/hooks/useHighlighter.ts +7 -43
- package/src/diff-viewer/index.ts +0 -1
- package/src/index.ts +2 -2
- package/src/markdown-message/CodeBlock.tsx +3 -10
- package/src/panel-header/CodePanelHeader.tsx +0 -121
- package/src/panel-header/index.ts +0 -1
- package/src/pretty-code/PrettyCode.client.tsx +0 -402
- package/src/pretty-code/README.md +0 -81
- package/src/pretty-code/index.tsx +0 -72
- package/src/pretty-code/lazy.tsx +0 -69
- package/src/pretty-code/pretty-code.stories.tsx +0 -353
- package/src/pretty-code/registerPrismLanguages.ts +0 -116
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Highlight, Language, themes } from 'prism-react-renderer';
|
|
4
|
-
import React, { useMemo, useRef } from 'react';
|
|
5
|
-
|
|
6
|
-
import { useAppT } from '@djangocfg/i18n';
|
|
7
|
-
import { useResolvedTheme } from '@djangocfg/ui-core/hooks';
|
|
8
|
-
import { CodePanelHeader } from '../panel-header';
|
|
9
|
-
|
|
10
|
-
// Surface palette — driven by the ui-core `--code*` semantic tokens so the
|
|
11
|
-
// code panel matches the rest of the theme system (neutral, preset-aware)
|
|
12
|
-
// instead of a hard-coded GitHub blue. The tokens are already tuned per
|
|
13
|
-
// theme (`light.css` / `dark.css`): a near-white gray in light, a near-black
|
|
14
|
-
// gray in dark — so the surface stays a calm neutral in either mode and never
|
|
15
|
-
// reads blue. Syntax colors still come from Prism's vsDark/github palettes.
|
|
16
|
-
const CODE_SURFACE_BG = 'var(--code)';
|
|
17
|
-
const CODE_INLINE_BG = 'var(--code-inline)';
|
|
18
|
-
|
|
19
|
-
// Corner radius comes from the ui-core token scale, never a hard-coded
|
|
20
|
-
// Tailwind step: `--radius-popover` is the container tier (the same one
|
|
21
|
-
// nav popovers and menu surfaces use), so the panel re-rounds with the
|
|
22
|
-
// active preset (`dense`, `windows`, …) instead of pinning 0.5rem forever.
|
|
23
|
-
const CODE_SURFACE_RADIUS = 'var(--radius-popover)';
|
|
24
|
-
|
|
25
|
-
// The panel is BORDERLESS: `--code` already separates it from the page in both
|
|
26
|
-
// themes (96% on a 98% light page, 9% on a 14.5% dark one), so a border only
|
|
27
|
-
// added a second boundary around a shape the fill had already drawn — most
|
|
28
|
-
// visible when the panel nests inside another card, where the two frames
|
|
29
|
-
// competed. Contrast does the separating; `--code-border` stays defined in the
|
|
30
|
-
// theme for hosts that genuinely need an outline.
|
|
31
|
-
|
|
32
|
-
// Load extra Prism grammars (``bash``, ``ruby``, ``java``, ``php``)
|
|
33
|
-
// that aren't in ``prism-react-renderer``'s default bundle. The hook
|
|
34
|
-
// below subscribes to its ready state and re-renders this component
|
|
35
|
-
// once loading completes, so the first tab click gets highlighted
|
|
36
|
-
// correctly even if it happened before the dynamic import resolved.
|
|
37
|
-
import { useEnsurePrismLanguages } from './registerPrismLanguages';
|
|
38
|
-
|
|
39
|
-
interface PrettyCodeProps {
|
|
40
|
-
data: string | object;
|
|
41
|
-
language: Language;
|
|
42
|
-
className?: string;
|
|
43
|
-
/**
|
|
44
|
-
* Force a specific Prism palette + surface. **Defaults to the resolved
|
|
45
|
-
* host theme** (light → light surface + GitHub palette, dark → dark
|
|
46
|
-
* surface + vsDark palette), so the panel matches the rest of the UI and
|
|
47
|
-
* its `--code*` tokens. Pass `mode` explicitly only to pin a palette
|
|
48
|
-
* regardless of theme — e.g. printing a PDF on a light page.
|
|
49
|
-
*/
|
|
50
|
-
mode?: 'dark' | 'light';
|
|
51
|
-
inline?: boolean;
|
|
52
|
-
customBg?: string; // Custom background class
|
|
53
|
-
isCompact?: boolean; // Compact mode for smaller font sizes
|
|
54
|
-
/** @deprecated No-op since the toolbar moved into a persistent
|
|
55
|
-
* `CodePanelHeader` (no scroll-capturing overlay). Kept for API
|
|
56
|
-
* back-compat; will be removed in a future major. */
|
|
57
|
-
scrollIsolation?: boolean;
|
|
58
|
-
/**
|
|
59
|
-
* Line count at which the viewer starts to scroll instead of growing.
|
|
60
|
-
* ``undefined`` (default) = always grows to fit content, no scroll.
|
|
61
|
-
* Set e.g. ``50`` to cap short snippets inline and scroll long ones.
|
|
62
|
-
*/
|
|
63
|
-
maxLines?: number;
|
|
64
|
-
/**
|
|
65
|
-
* When set, an expand (⤢) button appears in the header — but ONLY while
|
|
66
|
-
* the content is actually height-capped (i.e. exceeds `maxLines`). Lets a
|
|
67
|
-
* host open the full code full-screen. No-op when the code fits.
|
|
68
|
-
*/
|
|
69
|
-
onExpand?: () => void;
|
|
70
|
-
/**
|
|
71
|
-
* Visual variant. ``"card"`` (default) ships full chrome (border,
|
|
72
|
-
* background, hover toolbar, optional internal scroll). ``"plain"``
|
|
73
|
-
* is chrome-less — use when embedding inside another scroll
|
|
74
|
-
* container so the surface manages its own chrome and scroll. */
|
|
75
|
-
variant?: 'card' | 'plain';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const PrettyCode = ({ data, language, className, mode, inline = false, customBg, isCompact = false, maxLines, onExpand, variant = 'card' }: PrettyCodeProps) => {
|
|
79
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
80
|
-
const t = useAppT();
|
|
81
|
-
|
|
82
|
-
// Default to the host theme so the surface (`--code*` tokens) and the Prism
|
|
83
|
-
// palette move together; an explicit `mode` prop still pins a palette.
|
|
84
|
-
const resolvedTheme = useResolvedTheme();
|
|
85
|
-
const effectiveMode = mode ?? (resolvedTheme === 'light' ? 'light' : 'dark');
|
|
86
|
-
|
|
87
|
-
// Subscribe to the extra-grammars ready state. When ``bash`` /
|
|
88
|
-
// ``ruby`` / ``java`` / ``php`` finish loading, this hook triggers a
|
|
89
|
-
// re-render so the code block picks up the new grammar.
|
|
90
|
-
useEnsurePrismLanguages();
|
|
91
|
-
|
|
92
|
-
const labels = useMemo(() => ({
|
|
93
|
-
copyCode: t('tools.code.copyCode'),
|
|
94
|
-
noContent: t('tools.code.noContent'),
|
|
95
|
-
}), [t]);
|
|
96
|
-
|
|
97
|
-
// Font size based on compact mode
|
|
98
|
-
const fontSize = isCompact ? '0.75rem' : '0.875rem'; // 12px vs 14px
|
|
99
|
-
|
|
100
|
-
const isDarkMode = effectiveMode !== 'light';
|
|
101
|
-
const prismTheme = isDarkMode ? themes.vsDark : themes.github;
|
|
102
|
-
// The surface now reads the `--code*` tokens in BOTH modes — they already
|
|
103
|
-
// carry the right neutral per theme, so the panel matches the UI and never
|
|
104
|
-
// reads blue. (Previously light mode fell back to `bg-card` and dark mode
|
|
105
|
-
// hard-coded GitHub's #0d1117.)
|
|
106
|
-
const surfaceStyle = { backgroundColor: CODE_SURFACE_BG, borderRadius: CODE_SURFACE_RADIUS };
|
|
107
|
-
const inlineSurfaceStyle = { backgroundColor: CODE_INLINE_BG };
|
|
108
|
-
|
|
109
|
-
// Convert form object to JSON string with proper formatting
|
|
110
|
-
const contentJson = typeof data === 'string' ? data : JSON.stringify(data || {}, null, 2);
|
|
111
|
-
|
|
112
|
-
// Enable scroll only when content exceeds maxLines. Otherwise the block
|
|
113
|
-
// grows to fit — short snippets feel natural, long ones get a cap.
|
|
114
|
-
const lineCount = contentJson ? contentJson.split('\n').length : 0;
|
|
115
|
-
const lineHeightRatio = isCompact ? 1.4 : 1.5;
|
|
116
|
-
const fontSizePx = isCompact ? 12 : 14;
|
|
117
|
-
// Vertical padding of the <pre> (top + bottom, in px) — keep in sync
|
|
118
|
-
// with the padding string below.
|
|
119
|
-
const verticalPadPx = 16 + 12; // 1rem top + 0.75rem bottom (≈)
|
|
120
|
-
const shouldScroll = maxLines !== undefined && lineCount > maxLines;
|
|
121
|
-
const maxHeightPx = maxLines !== undefined
|
|
122
|
-
? maxLines * fontSizePx * lineHeightRatio + verticalPadPx
|
|
123
|
-
: undefined;
|
|
124
|
-
|
|
125
|
-
// Handle empty content
|
|
126
|
-
if (!contentJson || contentJson.trim() === '') {
|
|
127
|
-
const emptyBgClass = customBg || '';
|
|
128
|
-
return (
|
|
129
|
-
<div
|
|
130
|
-
// Same borderless token-rounded surface as the populated panel — the
|
|
131
|
-
// empty state used `rounded-sm`, so a panel visibly changed shape as
|
|
132
|
-
// soon as content arrived.
|
|
133
|
-
className={`relative h-full rounded-[var(--radius-popover)] ${emptyBgClass} ${className || ''}`}
|
|
134
|
-
style={customBg ? undefined : surfaceStyle}
|
|
135
|
-
>
|
|
136
|
-
<div className="h-full overflow-auto p-4">
|
|
137
|
-
<p
|
|
138
|
-
className="text-sm italic text-muted-foreground"
|
|
139
|
-
role="status"
|
|
140
|
-
>
|
|
141
|
-
{labels.noContent}
|
|
142
|
-
</p>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Get display name for language badge
|
|
149
|
-
const getLanguageDisplayName = (lang: string): string => {
|
|
150
|
-
switch (lang.toLowerCase()) {
|
|
151
|
-
case 'bash':
|
|
152
|
-
case 'shell':
|
|
153
|
-
return 'Bash';
|
|
154
|
-
case 'python':
|
|
155
|
-
case 'py':
|
|
156
|
-
return 'Python';
|
|
157
|
-
case 'javascript':
|
|
158
|
-
case 'js':
|
|
159
|
-
return 'JavaScript';
|
|
160
|
-
case 'typescript':
|
|
161
|
-
case 'ts':
|
|
162
|
-
return 'TypeScript';
|
|
163
|
-
case 'json':
|
|
164
|
-
return 'JSON';
|
|
165
|
-
case 'yaml':
|
|
166
|
-
case 'yml':
|
|
167
|
-
return 'YAML';
|
|
168
|
-
case 'html':
|
|
169
|
-
return 'HTML';
|
|
170
|
-
case 'css':
|
|
171
|
-
return 'CSS';
|
|
172
|
-
case 'sql':
|
|
173
|
-
return 'SQL';
|
|
174
|
-
case 'xml':
|
|
175
|
-
return 'XML';
|
|
176
|
-
case 'markdown':
|
|
177
|
-
case 'md':
|
|
178
|
-
return 'Markdown';
|
|
179
|
-
case 'plaintext':
|
|
180
|
-
case 'text':
|
|
181
|
-
return 'Text';
|
|
182
|
-
case 'mermaid':
|
|
183
|
-
return 'Mermaid';
|
|
184
|
-
case 'ruby':
|
|
185
|
-
case 'rb':
|
|
186
|
-
return 'Ruby';
|
|
187
|
-
case 'java':
|
|
188
|
-
return 'Java';
|
|
189
|
-
case 'php':
|
|
190
|
-
return 'PHP';
|
|
191
|
-
case 'go':
|
|
192
|
-
case 'golang':
|
|
193
|
-
return 'Go';
|
|
194
|
-
default:
|
|
195
|
-
return lang.charAt(0).toUpperCase() + lang.slice(1);
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
// Normalize language for Prism - use only basic supported languages
|
|
200
|
-
const normalizedLanguage = (() => {
|
|
201
|
-
const lang = language.toLowerCase();
|
|
202
|
-
|
|
203
|
-
// Try basic languages that are definitely supported
|
|
204
|
-
switch (lang) {
|
|
205
|
-
case 'javascript':
|
|
206
|
-
case 'js':
|
|
207
|
-
return 'javascript';
|
|
208
|
-
case 'typescript':
|
|
209
|
-
case 'ts':
|
|
210
|
-
return 'typescript'; // Try TypeScript first
|
|
211
|
-
case 'python':
|
|
212
|
-
case 'py':
|
|
213
|
-
return 'python';
|
|
214
|
-
case 'json':
|
|
215
|
-
return 'json';
|
|
216
|
-
case 'css':
|
|
217
|
-
return 'css';
|
|
218
|
-
case 'html':
|
|
219
|
-
return 'markup';
|
|
220
|
-
case 'xml':
|
|
221
|
-
return 'markup';
|
|
222
|
-
case 'bash':
|
|
223
|
-
case 'shell':
|
|
224
|
-
case 'sh':
|
|
225
|
-
return 'bash';
|
|
226
|
-
case 'ruby':
|
|
227
|
-
case 'rb':
|
|
228
|
-
return 'ruby';
|
|
229
|
-
case 'java':
|
|
230
|
-
return 'java';
|
|
231
|
-
case 'php':
|
|
232
|
-
return 'php';
|
|
233
|
-
case 'go':
|
|
234
|
-
case 'golang':
|
|
235
|
-
return 'go';
|
|
236
|
-
case 'sql':
|
|
237
|
-
return 'sql';
|
|
238
|
-
case 'yaml':
|
|
239
|
-
case 'yml':
|
|
240
|
-
return 'yaml';
|
|
241
|
-
case 'markdown':
|
|
242
|
-
case 'md':
|
|
243
|
-
return 'markdown';
|
|
244
|
-
case 'mermaid':
|
|
245
|
-
return 'text'; // Mermaid is handled separately in MarkdownMessage
|
|
246
|
-
default:
|
|
247
|
-
// For unknown languages, try to use the original name first
|
|
248
|
-
// If it doesn't work, Prism will fallback to plain text
|
|
249
|
-
return lang || 'text';
|
|
250
|
-
}
|
|
251
|
-
})();
|
|
252
|
-
|
|
253
|
-
const displayLanguage = getLanguageDisplayName(language);
|
|
254
|
-
|
|
255
|
-
// Plain variant — chrome-less render for embedding inside other
|
|
256
|
-
// scroll containers. No border, no background, no hover toolbar, no
|
|
257
|
-
// internal scroll. The caller's ScrollArea/panel owns the chrome
|
|
258
|
-
// and scroll responsibilities.
|
|
259
|
-
if (variant === 'plain') {
|
|
260
|
-
return (
|
|
261
|
-
<Highlight theme={prismTheme} code={contentJson} language={normalizedLanguage as Language}>
|
|
262
|
-
{({ className: prismClassName, style, tokens, getLineProps, getTokenProps }) => {
|
|
263
|
-
const { backgroundColor: _bg, ...restStyle } = style;
|
|
264
|
-
return (
|
|
265
|
-
<pre
|
|
266
|
-
className={`${prismClassName} ${className || ''}`}
|
|
267
|
-
style={{
|
|
268
|
-
...restStyle,
|
|
269
|
-
background: 'transparent',
|
|
270
|
-
margin: 0,
|
|
271
|
-
padding: isCompact ? '0.75rem 1rem' : '1rem',
|
|
272
|
-
fontSize,
|
|
273
|
-
lineHeight: lineHeightRatio,
|
|
274
|
-
fontFamily: 'monospace',
|
|
275
|
-
// ``break-all`` (not ``break-word``) so long unbroken
|
|
276
|
-
// strings without whitespace — typical of escaped
|
|
277
|
-
// JSON bodies in generated code samples — wrap at any
|
|
278
|
-
// character rather than overflowing the container.
|
|
279
|
-
whiteSpace: 'pre-wrap',
|
|
280
|
-
wordBreak: 'break-all',
|
|
281
|
-
overflowWrap: 'anywhere',
|
|
282
|
-
// Hard cap on width — parents (grid cells, panels)
|
|
283
|
-
// should constrain us but some callers render inside
|
|
284
|
-
// ``flex: 1 1 auto`` which lets the pre grow past
|
|
285
|
-
// intended bounds. ``max-width: 100%`` pins to parent.
|
|
286
|
-
maxWidth: '100%',
|
|
287
|
-
}}
|
|
288
|
-
>
|
|
289
|
-
{tokens.map((line, i) => (
|
|
290
|
-
<div key={i} {...getLineProps({ line })}>
|
|
291
|
-
{line.map((token, key) => (
|
|
292
|
-
<span key={key} {...getTokenProps({ token })} />
|
|
293
|
-
))}
|
|
294
|
-
</div>
|
|
295
|
-
))}
|
|
296
|
-
</pre>
|
|
297
|
-
);
|
|
298
|
-
}}
|
|
299
|
-
</Highlight>
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (inline) {
|
|
304
|
-
// Inline chip surface also comes from `--code-inline` via inlineSurfaceStyle.
|
|
305
|
-
const inlineBgClass = customBg || '';
|
|
306
|
-
return (
|
|
307
|
-
<Highlight theme={prismTheme} code={contentJson} language={normalizedLanguage as Language}>
|
|
308
|
-
{({ className: prismClassName, style, tokens, getTokenProps }) => (
|
|
309
|
-
<code
|
|
310
|
-
// `--radius-sm`, not the panel's container radius: an inline chip
|
|
311
|
-
// sits inside a line of prose, so it rounds like a small control
|
|
312
|
-
// rather than like a card.
|
|
313
|
-
className={`${prismClassName} ${inlineBgClass} px-2 py-1 rounded-[var(--radius-sm)] ${isCompact ? 'text-xs' : 'text-sm'} font-mono inline-block ${className || ''}`}
|
|
314
|
-
style={{
|
|
315
|
-
...style,
|
|
316
|
-
...(customBg ? undefined : inlineSurfaceStyle),
|
|
317
|
-
fontSize,
|
|
318
|
-
fontFamily: 'monospace',
|
|
319
|
-
}}
|
|
320
|
-
>
|
|
321
|
-
{tokens.map((line, i) =>
|
|
322
|
-
line.map((token, key) => (
|
|
323
|
-
<span key={`${i}-${key}`} {...getTokenProps({ token })} />
|
|
324
|
-
)),
|
|
325
|
-
)}
|
|
326
|
-
</code>
|
|
327
|
-
)}
|
|
328
|
-
</Highlight>
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Surface comes from the `--code*` tokens via `surfaceStyle` (both modes),
|
|
333
|
-
// unless the host pins its own `customBg` class.
|
|
334
|
-
const bgClass = customBg || '';
|
|
335
|
-
|
|
336
|
-
return (
|
|
337
|
-
<div
|
|
338
|
-
ref={containerRef}
|
|
339
|
-
// Radius lives in the CLASS, not in `surfaceStyle`: a host passing
|
|
340
|
-
// `customBg` drops the inline style entirely (see below), and the panel
|
|
341
|
-
// must stay rounded either way. Borderless by design — see the note on
|
|
342
|
-
// CODE_SURFACE_RADIUS.
|
|
343
|
-
className={`group relative overflow-hidden rounded-[var(--radius-popover)] ${bgClass} ${className || ''}`}
|
|
344
|
-
style={customBg ? undefined : surfaceStyle}
|
|
345
|
-
>
|
|
346
|
-
{/* Persistent header bar (GitHub / ChatGPT style): language label
|
|
347
|
-
left, copy button right, in flow — never floats over the code. */}
|
|
348
|
-
<CodePanelHeader
|
|
349
|
-
language={displayLanguage}
|
|
350
|
-
copyValue={contentJson}
|
|
351
|
-
copyLabel={labels.copyCode}
|
|
352
|
-
dark={isDarkMode}
|
|
353
|
-
// Expand only matters when the code is actually height-capped.
|
|
354
|
-
onExpand={onExpand && shouldScroll ? onExpand : undefined}
|
|
355
|
-
/>
|
|
356
|
-
|
|
357
|
-
<div
|
|
358
|
-
className={shouldScroll ? 'h-full overflow-auto' : ''}
|
|
359
|
-
style={shouldScroll ? { maxHeight: maxHeightPx } : undefined}
|
|
360
|
-
// When the viewer scrolls internally, make it keyboard-reachable
|
|
361
|
-
// so users without a wheel/trackpad can scroll with arrow keys.
|
|
362
|
-
{...(shouldScroll
|
|
363
|
-
? { tabIndex: 0, role: 'region', 'aria-label': displayLanguage }
|
|
364
|
-
: {})}
|
|
365
|
-
>
|
|
366
|
-
<Highlight theme={prismTheme} code={contentJson} language={normalizedLanguage as Language}>
|
|
367
|
-
{({ className: prismClassName, style, tokens, getLineProps, getTokenProps }) => {
|
|
368
|
-
// Remove background from Prism theme - we use our own via CSS
|
|
369
|
-
const { backgroundColor: _bg, ...restStyle } = style;
|
|
370
|
-
return (
|
|
371
|
-
<pre
|
|
372
|
-
className={prismClassName}
|
|
373
|
-
style={{
|
|
374
|
-
...restStyle,
|
|
375
|
-
margin: 0,
|
|
376
|
-
// The header now occupies the top; the code body just needs
|
|
377
|
-
// even padding (no extra top room for an overlay anymore).
|
|
378
|
-
padding: '0.875rem 1rem',
|
|
379
|
-
fontSize,
|
|
380
|
-
lineHeight: lineHeightRatio,
|
|
381
|
-
fontFamily: 'monospace',
|
|
382
|
-
whiteSpace: 'pre-wrap',
|
|
383
|
-
wordBreak: 'break-word',
|
|
384
|
-
overflowWrap: 'break-word',
|
|
385
|
-
}}
|
|
386
|
-
>
|
|
387
|
-
{tokens.map((line, i) => (
|
|
388
|
-
<div key={i} {...getLineProps({ line })}>
|
|
389
|
-
{line.map((token, key) => (
|
|
390
|
-
<span key={key} {...getTokenProps({ token })} />
|
|
391
|
-
))}
|
|
392
|
-
</div>
|
|
393
|
-
))}
|
|
394
|
-
</pre>
|
|
395
|
-
)}}
|
|
396
|
-
</Highlight>
|
|
397
|
-
</div>
|
|
398
|
-
</div>
|
|
399
|
-
);
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
export default PrettyCode;
|
|
@@ -1,81 +0,0 @@
|
|
|
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/widget-code/pretty-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`.
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PrettyCode Component - Dynamic Import Wrapper
|
|
3
|
-
*
|
|
4
|
-
* Lazy loads the heavy Prism library (~500KB) only when needed.
|
|
5
|
-
* This significantly reduces the initial bundle size.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use client';
|
|
9
|
-
|
|
10
|
-
import React, { lazy, Suspense } from 'react';
|
|
11
|
-
import type { Language } from 'prism-react-renderer';
|
|
12
|
-
|
|
13
|
-
import { LoadingFallback } from '@djangocfg/widget-kit/lazy';
|
|
14
|
-
|
|
15
|
-
// Lazy load the client component
|
|
16
|
-
const PrettyCodeClient = lazy(() => import('./PrettyCode.client'));
|
|
17
|
-
|
|
18
|
-
// Shared compact placeholder (16px muted spinner), inside the code-surface
|
|
19
|
-
// border so nothing shifts when PrettyCode mounts. Same look as LazyPrettyCode
|
|
20
|
-
// (lazy.tsx) and the JSON viewer.
|
|
21
|
-
const CodeFallback = () => (
|
|
22
|
-
<LoadingFallback
|
|
23
|
-
minHeight={64}
|
|
24
|
-
text="Loading code…"
|
|
25
|
-
className="rounded-sm border border-border bg-muted dark:bg-code"
|
|
26
|
-
/>
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
export interface PrettyCodeProps {
|
|
30
|
-
data: string | object;
|
|
31
|
-
language: Language;
|
|
32
|
-
className?: string;
|
|
33
|
-
mode?: 'dark' | 'light';
|
|
34
|
-
inline?: boolean;
|
|
35
|
-
customBg?: string;
|
|
36
|
-
isCompact?: boolean;
|
|
37
|
-
/** Block scroll capture until user clicks (default: true) */
|
|
38
|
-
scrollIsolation?: boolean;
|
|
39
|
-
/**
|
|
40
|
-
* Line count above which the block starts scrolling instead of growing.
|
|
41
|
-
* ``undefined`` (default) = no cap, block grows to fit. Set e.g. ``50``
|
|
42
|
-
* to inline short snippets and cap long ones.
|
|
43
|
-
*/
|
|
44
|
-
maxLines?: number;
|
|
45
|
-
/** When set, a header expand (⤢) button appears while the code is
|
|
46
|
-
* height-capped (exceeds `maxLines`). Lets a host open it full-screen. */
|
|
47
|
-
onExpand?: () => void;
|
|
48
|
-
/**
|
|
49
|
-
* Visual variant:
|
|
50
|
-
* - ``"card"`` (default) — full chrome: border, background, hover
|
|
51
|
-
* toolbar (Copy + language tag), optional internal scroll. Suits
|
|
52
|
-
* a standalone code block in documentation prose where the block
|
|
53
|
-
* is its own surface.
|
|
54
|
-
* - ``"plain"`` — chrome-less: no border, no background, no toolbar,
|
|
55
|
-
* no internal scroll. Grows to fit its content. Use when
|
|
56
|
-
* embedding inside another scroll container (e.g. the Response
|
|
57
|
-
* panel) — avoids double-scroll surfaces and lets the parent
|
|
58
|
-
* manage copy/language affordances.
|
|
59
|
-
*/
|
|
60
|
-
variant?: 'card' | 'plain';
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const PrettyCode: React.FC<PrettyCodeProps> = (props) => {
|
|
64
|
-
return (
|
|
65
|
-
<Suspense fallback={<CodeFallback />}>
|
|
66
|
-
<PrettyCodeClient {...props} />
|
|
67
|
-
</Suspense>
|
|
68
|
-
);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export default PrettyCode;
|
|
72
|
-
export type { Language };
|
package/src/pretty-code/lazy.tsx
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Lazy-loaded PrettyCode Component
|
|
5
|
-
*
|
|
6
|
-
* Heavy Prism library (~500KB) is loaded only when component is rendered.
|
|
7
|
-
* Use this for automatic code-splitting with Suspense fallback.
|
|
8
|
-
*
|
|
9
|
-
* For direct imports without lazy loading, use:
|
|
10
|
-
* import PrettyCode from '@djangocfg/widget-code/pretty-code'
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { createLazyComponent, LoadingFallback } from '@djangocfg/widget-kit/lazy';
|
|
14
|
-
import type { Language } from 'prism-react-renderer';
|
|
15
|
-
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// Types
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
|
-
export interface PrettyCodeProps {
|
|
21
|
-
data: string | object;
|
|
22
|
-
language: Language;
|
|
23
|
-
className?: string;
|
|
24
|
-
mode?: 'dark' | 'light';
|
|
25
|
-
inline?: boolean;
|
|
26
|
-
customBg?: string;
|
|
27
|
-
isCompact?: boolean;
|
|
28
|
-
scrollIsolation?: boolean;
|
|
29
|
-
maxLines?: number;
|
|
30
|
-
/** Header expand (⤢) callback, shown while the code is height-capped. */
|
|
31
|
-
onExpand?: () => void;
|
|
32
|
-
/** ``'card'`` (default) ships the full chrome. ``'plain'`` is
|
|
33
|
-
* chrome-less — for embedding inside another scroll container. */
|
|
34
|
-
variant?: 'card' | 'plain';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export type { Language };
|
|
38
|
-
|
|
39
|
-
// ============================================================================
|
|
40
|
-
// Lazy Component
|
|
41
|
-
// ============================================================================
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* LazyPrettyCode - Lazy-loaded code syntax highlighter
|
|
45
|
-
*
|
|
46
|
-
* Automatically shows loading state while Prism loads (~500KB). Uses the shared
|
|
47
|
-
* compact `LoadingFallback` (16px muted spinner) so the code viewer and the JSON
|
|
48
|
-
* viewer show the SAME quiet placeholder while their chunks load — kept inside a
|
|
49
|
-
* subtle code-surface border so nothing shifts when PrettyCode mounts.
|
|
50
|
-
*/
|
|
51
|
-
export const LazyPrettyCode = createLazyComponent<PrettyCodeProps>(
|
|
52
|
-
() => import('./PrettyCode.client'),
|
|
53
|
-
{
|
|
54
|
-
displayName: 'LazyPrettyCode',
|
|
55
|
-
fallback: (
|
|
56
|
-
<LoadingFallback
|
|
57
|
-
minHeight={64}
|
|
58
|
-
text="Loading code…"
|
|
59
|
-
className="rounded-sm border border-border bg-muted dark:bg-code"
|
|
60
|
-
/>
|
|
61
|
-
),
|
|
62
|
-
}
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
// `PrettyCode` is the historical named export — same component as
|
|
66
|
-
// `LazyPrettyCode`, kept for backwards compatibility with callers that
|
|
67
|
-
// imported it from the old root barrel.
|
|
68
|
-
export { LazyPrettyCode as PrettyCode };
|
|
69
|
-
export { default } from './index';
|