@djangocfg/widget-code 0.1.1

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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/package.json +119 -0
  4. package/src/diff-viewer/DiffViewer.tsx +168 -0
  5. package/src/diff-viewer/README.md +33 -0
  6. package/src/diff-viewer/components/CopyButton.tsx +49 -0
  7. package/src/diff-viewer/components/DiffLineContent.tsx +48 -0
  8. package/src/diff-viewer/components/SplitView.tsx +270 -0
  9. package/src/diff-viewer/components/UnifiedView.tsx +189 -0
  10. package/src/diff-viewer/diff-viewer.stories.tsx +168 -0
  11. package/src/diff-viewer/hooks/useDiff.ts +47 -0
  12. package/src/diff-viewer/hooks/useHighlighter.ts +54 -0
  13. package/src/diff-viewer/index.ts +22 -0
  14. package/src/diff-viewer/lazy.tsx +25 -0
  15. package/src/diff-viewer/types.ts +125 -0
  16. package/src/diff-viewer/utils/computeDiff.ts +171 -0
  17. package/src/editor/README.md +187 -0
  18. package/src/editor/components/DiffEditor.tsx +158 -0
  19. package/src/editor/components/Editor.tsx +293 -0
  20. package/src/editor/components/index.ts +2 -0
  21. package/src/editor/context/EditorProvider.tsx +212 -0
  22. package/src/editor/context/index.ts +1 -0
  23. package/src/editor/editor.stories.tsx +397 -0
  24. package/src/editor/hooks/index.ts +4 -0
  25. package/src/editor/hooks/useEditor.ts +36 -0
  26. package/src/editor/hooks/useEditorTheme.ts +91 -0
  27. package/src/editor/hooks/useLanguage.ts +29 -0
  28. package/src/editor/hooks/useMonaco.ts +79 -0
  29. package/src/editor/index.ts +16 -0
  30. package/src/editor/lazy.tsx +76 -0
  31. package/src/editor/lib/index.ts +2 -0
  32. package/src/editor/lib/languages.ts +227 -0
  33. package/src/editor/lib/themes.ts +42 -0
  34. package/src/editor/types/index.ts +137 -0
  35. package/src/editor/workers/index.ts +1 -0
  36. package/src/editor/workers/setup.ts +58 -0
  37. package/src/index.ts +10 -0
  38. package/src/json-editor/JsonEditor.tsx +115 -0
  39. package/src/json-editor/index.ts +1 -0
  40. package/src/json-editor/lazy.tsx +24 -0
  41. package/src/markdown-message/ActionRow.tsx +53 -0
  42. package/src/markdown-message/ChatMessageRow.tsx +103 -0
  43. package/src/markdown-message/CodeBlock.tsx +137 -0
  44. package/src/markdown-message/CollapseToggle.tsx +69 -0
  45. package/src/markdown-message/MarkdownMessage.tsx +376 -0
  46. package/src/markdown-message/README.md +221 -0
  47. package/src/markdown-message/builtinRules.tsx +92 -0
  48. package/src/markdown-message/components.tsx +224 -0
  49. package/src/markdown-message/index.ts +22 -0
  50. package/src/markdown-message/linkRules.ts +83 -0
  51. package/src/markdown-message/plainText.ts +83 -0
  52. package/src/markdown-message/sanitize.ts +88 -0
  53. package/src/markdown-message/streamingBlocks.ts +168 -0
  54. package/src/markdown-message/types.ts +168 -0
  55. package/src/panel-header/CodePanelHeader.tsx +121 -0
  56. package/src/panel-header/index.ts +1 -0
  57. package/src/pretty-code/PrettyCode.client.tsx +402 -0
  58. package/src/pretty-code/README.md +81 -0
  59. package/src/pretty-code/index.tsx +72 -0
  60. package/src/pretty-code/lazy.tsx +69 -0
  61. package/src/pretty-code/pretty-code.stories.tsx +353 -0
  62. package/src/pretty-code/registerPrismLanguages.ts +116 -0
  63. package/src/role-tokens.ts +39 -0
  64. package/src/use-collapsible-content.ts +236 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Reforms.ai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # @djangocfg/widget-code
2
+
3
+ Code surfaces: an editor, a diff, a highlighted listing, a JSON editor, and the
4
+ markdown renderer that embeds them.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ pnpm add @djangocfg/widget-code @djangocfg/ui-core @djangocfg/i18n
10
+ ```
11
+
12
+ | Subpath | What | Weight |
13
+ |---|---|---|
14
+ | `./editor` | Monaco, single file or diff | ~550 KB, lazy |
15
+ | `./json-editor` | an editable JSON tree | `json-edit-react` |
16
+ | `./pretty-code` | a read-only listing with a panel header | Prism |
17
+ | `./diff-viewer` | unified or split diff, virtualised | Prism + virtuoso |
18
+ | `./diff-viewer/full` | the same, eagerly | |
19
+ | `./markdown-message` | markdown with embedded code, diagrams and chips | |
20
+ | `./role-tokens` | the `ANCHOR` / `TOGGLE` class contracts | |
21
+
22
+ ## No root barrel worth importing
23
+
24
+ Monaco alone is ~550 KB, and Prism and `json-edit-react` sit behind their own
25
+ subpaths. The root exports the role tokens and nothing else, on purpose.
26
+
27
+ ## Read-only code elsewhere
28
+
29
+ `@djangocfg/widget-media/code` renders a listing with **shiki**, and a console
30
+ should pick one highlighter rather than match two themes by hand. Prefer it for
31
+ a new surface; this package's Prism stack stays because `DiffViewer` and
32
+ `MarkdownMessage` are built on it.
33
+
34
+ ## Verify
35
+
36
+ ```bash
37
+ pnpm --filter @djangocfg/widget-code check-types test
38
+ ```
package/package.json ADDED
@@ -0,0 +1,119 @@
1
+ {
2
+ "name": "@djangocfg/widget-code",
3
+ "version": "0.1.1",
4
+ "description": "Code surfaces: a Monaco editor, a diff viewer, a Prism listing, a JSON editor and the markdown renderer that embeds them",
5
+ "keywords": [
6
+ "code-editor",
7
+ "monaco",
8
+ "diff",
9
+ "syntax-highlighting",
10
+ "markdown",
11
+ "react",
12
+ "typescript"
13
+ ],
14
+ "author": {
15
+ "name": "DjangoCFG",
16
+ "url": "https://djangocfg.com"
17
+ },
18
+ "homepage": "https://djangocfg.com",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/markolofsen/django-cfg.git",
22
+ "directory": "widgets/code"
23
+ },
24
+ "license": "MIT",
25
+ "type": "module",
26
+ "main": "./src/index.ts",
27
+ "types": "./src/index.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./src/index.ts",
31
+ "default": "./src/index.ts"
32
+ },
33
+ "./editor": {
34
+ "types": "./src/editor/lazy.tsx",
35
+ "default": "./src/editor/lazy.tsx"
36
+ },
37
+ "./json-editor": {
38
+ "types": "./src/json-editor/lazy.tsx",
39
+ "default": "./src/json-editor/lazy.tsx"
40
+ },
41
+ "./pretty-code": {
42
+ "types": "./src/pretty-code/lazy.tsx",
43
+ "default": "./src/pretty-code/lazy.tsx"
44
+ },
45
+ "./diff-viewer": {
46
+ "types": "./src/diff-viewer/lazy.tsx",
47
+ "default": "./src/diff-viewer/lazy.tsx"
48
+ },
49
+ "./diff-viewer/full": {
50
+ "types": "./src/diff-viewer/index.ts",
51
+ "default": "./src/diff-viewer/index.ts"
52
+ },
53
+ "./markdown-message": {
54
+ "types": "./src/markdown-message/index.ts",
55
+ "default": "./src/markdown-message/index.ts"
56
+ },
57
+ "./role-tokens": {
58
+ "types": "./src/role-tokens.ts",
59
+ "default": "./src/role-tokens.ts"
60
+ }
61
+ },
62
+ "files": [
63
+ "src",
64
+ "!src/**/*.test.ts",
65
+ "!src/**/*.test.tsx",
66
+ "README.md",
67
+ "LICENSE"
68
+ ],
69
+ "scripts": {
70
+ "check": "tsc --noEmit",
71
+ "check-types": "tsc --noEmit",
72
+ "test": "vitest run",
73
+ "test:watch": "vitest"
74
+ },
75
+ "dependencies": {
76
+ "@djangocfg/widget-data": "^0.1.1",
77
+ "@djangocfg/widget-diagram": "^0.1.1",
78
+ "@djangocfg/widget-editor": "^0.1.1",
79
+ "@djangocfg/widget-kit": "^0.1.1",
80
+ "json-edit-react": "^1.30.2",
81
+ "monaco-editor": "^0.55.1",
82
+ "prism-react-renderer": "^2.4.1",
83
+ "prismjs": "^1.30.0",
84
+ "react-markdown": "10.1.0",
85
+ "react-virtuoso": "^4.18.10",
86
+ "rehype-external-links": "^3.0.0",
87
+ "rehype-raw": "^7.0.0",
88
+ "rehype-sanitize": "^6.0.0",
89
+ "remark-breaks": "^4.0.0",
90
+ "remark-emoji": "^5.0.2",
91
+ "remark-gfm": "4.0.1",
92
+ "remark-smartypants": "^3.0.2"
93
+ },
94
+ "peerDependencies": {
95
+ "@djangocfg/i18n": "^2.1.542",
96
+ "@djangocfg/ui-core": "^2.1.542",
97
+ "lucide-react": "^0.545.0",
98
+ "react": "^19.0.0",
99
+ "react-dom": "^19.0.0"
100
+ },
101
+ "devDependencies": {
102
+ "@djangocfg/i18n": "^2.1.542",
103
+ "@djangocfg/typescript-config": "^2.1.542",
104
+ "@djangocfg/ui-core": "^2.1.542",
105
+ "@storybook/react-vite": "^10.5.0",
106
+ "@types/node": "^25.9.5",
107
+ "@types/react": "19.2.15",
108
+ "@types/react-dom": "19.2.3",
109
+ "lucide-react": "0.545.0",
110
+ "react": "19.2.7",
111
+ "react-dom": "19.2.7",
112
+ "storybook": "^10.5.0",
113
+ "typescript": "^5.9.3",
114
+ "vitest": "^4.1.10"
115
+ },
116
+ "publishConfig": {
117
+ "access": "public"
118
+ }
119
+ }
@@ -0,0 +1,168 @@
1
+ // Adapted from jalcoui (MIT) — github.com/jal-co/ui
2
+ //
3
+ // Line-by-line code diff viewer. Supports unified (single column) and
4
+ // split (side-by-side) layouts. Syntax highlighting reuses the same
5
+ // `prism-react-renderer` bundle that the `PrettyCode` tool already
6
+ // pulls in — no second highlighter is loaded.
7
+ //
8
+ // Requires <UiProviders> at the host root (no nested providers here).
9
+ // See CONTRACT.md §5.
10
+ //
11
+ // Unlike `PrettyCode` (intentionally always-dark), DiffViewer adapts to
12
+ // the host theme. Diffs are commonly embedded in documentation pages
13
+ // where forcing a dark surface on a light page reads as a bug. The
14
+ // Prism theme is picked per host theme (`themes.vsDark` for dark,
15
+ // `themes.github` for light) and surface chrome uses semantic tokens.
16
+
17
+ 'use client';
18
+
19
+ import * as React from 'react';
20
+ import { themes, type PrismTheme } from 'prism-react-renderer';
21
+ import { cn } from '@djangocfg/ui-core/lib';
22
+ import { useResolvedTheme } from '@djangocfg/ui-core/hooks';
23
+ import { CopyButton } from './components/CopyButton';
24
+ import { SplitView } from './components/SplitView';
25
+ import { UnifiedView } from './components/UnifiedView';
26
+ import { useDiff } from './hooks/useDiff';
27
+ import { useDiffLanguage } from './hooks/useHighlighter';
28
+ import {
29
+ DIFF_TONE_CLASSES,
30
+ type DiffInput,
31
+ type DiffViewerProps,
32
+ } from './types';
33
+
34
+ export function DiffViewer({
35
+ layout = 'unified',
36
+ language,
37
+ oldTitle,
38
+ newTitle,
39
+ copyable = true,
40
+ context = 3,
41
+ hostScrolls = false,
42
+ className,
43
+ ...allProps
44
+ }: DiffViewerProps) {
45
+ // Split the diff-input keys off the DOM rest props so we don't spread
46
+ // `oldCode` / `newCode` / `patch` onto the wrapping <div>.
47
+ const { oldCode, newCode, patch, ...divProps } =
48
+ allProps as DiffViewerProps & Record<string, unknown>;
49
+ const input: DiffInput =
50
+ patch !== undefined
51
+ ? ({ patch: patch as string } as DiffInput)
52
+ : ({
53
+ oldCode: (oldCode as string) ?? '',
54
+ newCode: (newCode as string) ?? '',
55
+ } as DiffInput);
56
+
57
+ const { lines, numWidth, stats } = useDiff({ input, context });
58
+
59
+ // Longest content line in ch — the shared minimum width of the content
60
+ // column in BOTH layouts. A single global value (like `numWidth`) keeps
61
+ // every row the same width, so the whole block scrolls as one unit inside
62
+ // the outer `overflow-x-auto` wrapper below, and split-view columns stay
63
+ // aligned across rows. Tabs render 8ch wide under `whitespace-pre`, so
64
+ // they count as 8.
65
+ const maxContentCh = React.useMemo(() => {
66
+ let max = 0;
67
+ for (const l of lines) {
68
+ const tabs = (l.content.match(/\t/g) ?? []).length;
69
+ // CJK / fullwidth glyphs are one UTF-16 unit but render two columns
70
+ // wide in monospace — count them twice. (Emoji are already two units.)
71
+ const wide = (l.content.match(/[\u2E80-\u9FFF\uF900-\uFAFF\uFF01-\uFF60]/g) ?? []).length;
72
+ const len = l.content.length + tabs * 7 + wide;
73
+ if (len > max) max = len;
74
+ }
75
+ return max;
76
+ }, [lines]);
77
+
78
+ // Pass `null` when no language → cheap plain-text render path.
79
+ const prismLang = useDiffLanguage(language);
80
+ const resolvedLanguage = language ? prismLang : null;
81
+
82
+ // Pick a Prism palette to match the host theme. Resolved once at the
83
+ // viewer root, then forwarded to every line — keeps the hook out of
84
+ // per-line render paths (could be thousands of rows on big diffs).
85
+ const resolved = useResolvedTheme();
86
+ const prismTheme: PrismTheme =
87
+ resolved === 'dark' ? themes.vsDark : themes.github;
88
+
89
+ // Full text used for the copy button — concatenates current lines as
90
+ // displayed, including removed ones. Mirrors jalcoui's behavior.
91
+ const fullCode = React.useMemo(
92
+ () => lines.map((l) => l.content).join('\n'),
93
+ [lines],
94
+ );
95
+
96
+ const showHeader = Boolean(oldTitle || newTitle);
97
+ const addedTone = DIFF_TONE_CLASSES.added.stat;
98
+ const removedTone = DIFF_TONE_CLASSES.removed.stat;
99
+
100
+ return (
101
+ <div
102
+ data-slot="diff-viewer"
103
+ data-layout={layout}
104
+ className={cn(
105
+ 'relative overflow-hidden rounded-xl border border-border/60 bg-card text-foreground shadow-sm',
106
+ className,
107
+ )}
108
+ {...(divProps as React.ComponentProps<'div'>)}
109
+ >
110
+ {showHeader && (
111
+ <div className="flex items-center justify-between gap-3 border-b border-border/60 bg-muted/40 px-4 py-2.5">
112
+ <div className="flex items-center gap-3 text-sm">
113
+ {oldTitle && (
114
+ <span className="text-muted-foreground">{oldTitle}</span>
115
+ )}
116
+ {oldTitle && newTitle && (
117
+ <span className="text-muted-foreground/60" aria-hidden="true">
118
+ →
119
+ </span>
120
+ )}
121
+ {newTitle && (
122
+ <span className="font-medium text-foreground">{newTitle}</span>
123
+ )}
124
+ </div>
125
+ <div className="flex items-center gap-3">
126
+ <div className="flex items-center gap-2 text-xs tabular-nums">
127
+ {stats.added > 0 && (
128
+ <span className={addedTone}>+{stats.added}</span>
129
+ )}
130
+ {stats.removed > 0 && (
131
+ <span className={removedTone}>-{stats.removed}</span>
132
+ )}
133
+ </div>
134
+ {copyable && <CopyButton value={fullCode} />}
135
+ </div>
136
+ </div>
137
+ )}
138
+
139
+ {!showHeader && copyable && (
140
+ <div className="absolute right-2 top-2 z-10">
141
+ <CopyButton value={fullCode} />
142
+ </div>
143
+ )}
144
+
145
+ <div className="overflow-x-auto">
146
+ {layout === 'split' ? (
147
+ <SplitView
148
+ lines={lines}
149
+ numWidth={numWidth}
150
+ maxContentCh={maxContentCh}
151
+ language={resolvedLanguage}
152
+ prismTheme={prismTheme}
153
+ hostScrolls={hostScrolls}
154
+ />
155
+ ) : (
156
+ <UnifiedView
157
+ lines={lines}
158
+ numWidth={numWidth}
159
+ maxContentCh={maxContentCh}
160
+ language={resolvedLanguage}
161
+ prismTheme={prismTheme}
162
+ hostScrolls={hostScrolls}
163
+ />
164
+ )}
165
+ </div>
166
+ </div>
167
+ );
168
+ }
@@ -0,0 +1,33 @@
1
+ # DiffViewer
2
+
3
+ Line-by-line diff viewer with unified or split layout. Reuses the `prism-react-renderer` instance from `PrettyCode` for optional syntax highlighting — no second highlighter is loaded.
4
+
5
+ Pass either `oldCode` + `newCode` (computed diff) or a pre-built `patch` string (unified format).
6
+
7
+ ```tsx
8
+ import { DiffViewer } from '@djangocfg/widget-code/diff-viewer';
9
+
10
+ <DiffViewer
11
+ oldCode="const x = 1;"
12
+ newCode="const x = 2;\nconst y = 3;"
13
+ language="ts"
14
+ layout="unified"
15
+ />
16
+ ```
17
+
18
+ ## Props
19
+
20
+ | Prop | Type | Default | Description |
21
+ |---|---|---|---|
22
+ | `oldCode` / `newCode` | `string` | — | Source pair to diff. Mutually exclusive with `patch`. |
23
+ | `patch` | `string` | — | Pre-computed unified diff. Mutually exclusive with `oldCode`/`newCode`. |
24
+ | `layout` | `'unified' \| 'split'` | `'unified'` | Single column vs side-by-side. |
25
+ | `language` | `Language \| string` | — | Prism language key for highlighting. Omit for plain monospace. |
26
+ | `oldTitle` | `string` | — | LHS file header label. |
27
+ | `newTitle` | `string` | — | RHS file header label. |
28
+
29
+ Storybook: `apps/storybook/stories/ui-tools/dev/DiffViewer.stories.tsx`
30
+
31
+ ---
32
+
33
+ Adapted from jalcoui (MIT).
@@ -0,0 +1,49 @@
1
+ // Adapted from jalcoui (MIT) — github.com/jal-co/ui
2
+
3
+ 'use client';
4
+
5
+ import * as React from 'react';
6
+ import { Check, Copy } from 'lucide-react';
7
+ import { cn } from '@djangocfg/ui-core/lib';
8
+
9
+ interface CopyButtonProps extends Omit<React.ComponentProps<'button'>, 'value'> {
10
+ value: string;
11
+ }
12
+
13
+ /**
14
+ * Tiny copy-to-clipboard button used in the DiffViewer header. Local
15
+ * component (not a hoist of `CopyAction` from `FloatingToolbar`) so the
16
+ * viewer can render without any provider context.
17
+ *
18
+ * Uses semantic tokens — the diff viewer adapts to the host theme,
19
+ * unlike `PrettyCode` which forces dark.
20
+ */
21
+ export function CopyButton({ value, className, ...props }: CopyButtonProps) {
22
+ const [copied, setCopied] = React.useState(false);
23
+
24
+ const handleCopy = React.useCallback(() => {
25
+ void navigator.clipboard.writeText(value).then(() => {
26
+ setCopied(true);
27
+ window.setTimeout(() => setCopied(false), 1500);
28
+ });
29
+ }, [value]);
30
+
31
+ return (
32
+ <button
33
+ type="button"
34
+ onClick={handleCopy}
35
+ data-slot="diff-viewer-copy-button"
36
+ className={cn(
37
+ 'inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-xs transition-colors',
38
+ 'text-muted-foreground hover:text-foreground hover:bg-muted',
39
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50',
40
+ className,
41
+ )}
42
+ aria-label={copied ? 'Copied' : 'Copy code'}
43
+ {...props}
44
+ >
45
+ {copied ? <Check className="size-3.5" /> : <Copy className="size-3.5" />}
46
+ {copied ? 'Copied' : 'Copy'}
47
+ </button>
48
+ );
49
+ }
@@ -0,0 +1,48 @@
1
+ // Adapted from jalcoui (MIT) — github.com/jal-co/ui
2
+
3
+ 'use client';
4
+
5
+ import * as React from 'react';
6
+ import { Highlight, type Language, type PrismTheme } from 'prism-react-renderer';
7
+
8
+ interface DiffLineContentProps {
9
+ content: string;
10
+ language: Language | null;
11
+ /**
12
+ * Prism palette resolved by the parent `DiffViewer` against the host
13
+ * theme. Forwarded here so we don't run a theme-detection hook on
14
+ * every line (a big diff can be thousands of rows).
15
+ */
16
+ prismTheme: PrismTheme;
17
+ }
18
+
19
+ /**
20
+ * Single-line content cell. When `language` is non-null the line is
21
+ * tokenized by `prism-react-renderer` (the same instance PrettyCode
22
+ * uses). When null we render plain text — cheap path for `language`-less
23
+ * diffs and very large lists.
24
+ *
25
+ * Each line tokenizes independently. For diffs > a few thousand lines
26
+ * this can get warm; in practice the LCS already drops to context lines
27
+ * before we get there.
28
+ */
29
+ export const DiffLineContent = React.memo(function DiffLineContent({
30
+ content,
31
+ language,
32
+ prismTheme,
33
+ }: DiffLineContentProps) {
34
+ if (!language) {
35
+ return <>{content || ' '}</>;
36
+ }
37
+ return (
38
+ <Highlight theme={prismTheme} code={content || ' '} language={language}>
39
+ {({ tokens, getTokenProps }) => (
40
+ <>
41
+ {tokens[0]?.map((token, key) => (
42
+ <span key={key} {...getTokenProps({ token })} />
43
+ ))}
44
+ </>
45
+ )}
46
+ </Highlight>
47
+ );
48
+ });