@vendure-io/ui 2.0.0-beta.8 → 2.0.0-beta.9
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/package.json +1 -1
- package/src/components/molecules/code-block.tsx +5 -133
- package/src/lib/highlight.ts +141 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure-io/ui",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.9",
|
|
4
4
|
"description": "React component library for Vendure, built on shadcn/ui and Tailwind v4",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"homepage": "https://github.com/vendurehq/design/tree/main/packages/ui",
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
transformerNotationDiff,
|
|
5
|
-
transformerNotationErrorLevel,
|
|
6
|
-
transformerNotationFocus,
|
|
7
|
-
transformerNotationHighlight,
|
|
8
|
-
transformerNotationWordHighlight,
|
|
9
|
-
} from '@shikijs/transformers';
|
|
10
3
|
import { Button } from '@vendure-io/ui/components/atoms/button';
|
|
11
4
|
import { ScrollArea, ScrollBar } from '@vendure-io/ui/components/atoms/scroll-area';
|
|
12
5
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@vendure-io/ui/components/atoms/tooltip';
|
|
13
6
|
import { useCopyFeedback } from '@vendure-io/ui/components/molecules/copy-feedback-provider';
|
|
14
7
|
import { useCopy } from '@vendure-io/ui/hooks/use-copy';
|
|
8
|
+
import {
|
|
9
|
+
highlightCode,
|
|
10
|
+
normalizeLanguage,
|
|
11
|
+
type SupportedLanguage,
|
|
12
|
+
} from '@vendure-io/ui/lib/highlight';
|
|
15
13
|
import { cn } from '@vendure-io/ui/lib/utils';
|
|
16
14
|
import {
|
|
17
15
|
CheckIcon,
|
|
@@ -33,8 +31,6 @@ import {
|
|
|
33
31
|
useRef,
|
|
34
32
|
useState,
|
|
35
33
|
} from 'react';
|
|
36
|
-
import { createHighlighterCore } from 'shiki/core';
|
|
37
|
-
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
|
|
38
34
|
|
|
39
35
|
// =============================================================================
|
|
40
36
|
// Types
|
|
@@ -87,53 +83,6 @@ const STORAGE_EVENT = 'vendure-ui-package-manager-change';
|
|
|
87
83
|
const COLLAPSE_THRESHOLD = 40; // Lines needed to trigger collapse
|
|
88
84
|
const COLLAPSED_HEIGHT = 300; // px height when collapsed (roughly 15-20 lines)
|
|
89
85
|
|
|
90
|
-
const languageLoaders = {
|
|
91
|
-
bash: () => import('@shikijs/langs/bash').then((module) => module.default),
|
|
92
|
-
css: () => import('@shikijs/langs/css').then((module) => module.default),
|
|
93
|
-
dotenv: () => import('@shikijs/langs/dotenv').then((module) => module.default),
|
|
94
|
-
graphql: () => import('@shikijs/langs/graphql').then((module) => module.default),
|
|
95
|
-
html: () => import('@shikijs/langs/html').then((module) => module.default),
|
|
96
|
-
ini: () => import('@shikijs/langs/ini').then((module) => module.default),
|
|
97
|
-
javascript: () => import('@shikijs/langs/javascript').then((module) => module.default),
|
|
98
|
-
json: () => import('@shikijs/langs/json').then((module) => module.default),
|
|
99
|
-
jsonc: () => import('@shikijs/langs/jsonc').then((module) => module.default),
|
|
100
|
-
jsx: () => import('@shikijs/langs/jsx').then((module) => module.default),
|
|
101
|
-
markdown: () => import('@shikijs/langs/markdown').then((module) => module.default),
|
|
102
|
-
mdx: () => import('@shikijs/langs/mdx').then((module) => module.default),
|
|
103
|
-
python: () => import('@shikijs/langs/python').then((module) => module.default),
|
|
104
|
-
shellscript: () => import('@shikijs/langs/shellscript').then((module) => module.default),
|
|
105
|
-
sql: () => import('@shikijs/langs/sql').then((module) => module.default),
|
|
106
|
-
tsx: () => import('@shikijs/langs/tsx').then((module) => module.default),
|
|
107
|
-
typescript: () => import('@shikijs/langs/typescript').then((module) => module.default),
|
|
108
|
-
yaml: () => import('@shikijs/langs/yaml').then((module) => module.default),
|
|
109
|
-
} as const;
|
|
110
|
-
|
|
111
|
-
type SupportedLanguage = keyof typeof languageLoaders;
|
|
112
|
-
|
|
113
|
-
const themeLoaders = [
|
|
114
|
-
() => import('@shikijs/themes/github-light').then((module) => module.default),
|
|
115
|
-
() => import('@shikijs/themes/github-dark-default').then((module) => module.default),
|
|
116
|
-
] as const;
|
|
117
|
-
|
|
118
|
-
let highlighterPromise: ReturnType<typeof createHighlighterCore> | null = null;
|
|
119
|
-
|
|
120
|
-
function getHighlighter(): ReturnType<typeof createHighlighterCore> {
|
|
121
|
-
highlighterPromise ??= Promise.all([
|
|
122
|
-
Promise.all(themeLoaders.map((loadTheme) => loadTheme())),
|
|
123
|
-
Promise.all(Object.values(languageLoaders).map((loadLanguage) => loadLanguage())),
|
|
124
|
-
]).then(([themes, languages]) =>
|
|
125
|
-
createHighlighterCore({
|
|
126
|
-
themes,
|
|
127
|
-
langs: languages.flat(),
|
|
128
|
-
engine: createJavaScriptRegexEngine(),
|
|
129
|
-
}),
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
return highlighterPromise;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const highlightedCodeCache = new Map<string, Promise<string>>();
|
|
136
|
-
|
|
137
86
|
/**
|
|
138
87
|
* Get the stored package manager preference from localStorage
|
|
139
88
|
*/
|
|
@@ -451,83 +400,6 @@ function processCode(
|
|
|
451
400
|
};
|
|
452
401
|
}
|
|
453
402
|
|
|
454
|
-
// =============================================================================
|
|
455
|
-
// Shiki Highlighting
|
|
456
|
-
// =============================================================================
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Highlight code using Shiki with all transformers.
|
|
460
|
-
* Uses Shiki's native notation for highlighting:
|
|
461
|
-
* - // [!code highlight] - highlight a line (use language-appropriate comment)
|
|
462
|
-
* - // [!code ++] / // [!code --] - diff highlighting
|
|
463
|
-
* - // [!code focus] - focus mode (blur other lines)
|
|
464
|
-
* - // [!code word:myVar] - highlight specific word
|
|
465
|
-
* - // [!code error] / // [!code warning] - error levels
|
|
466
|
-
*/
|
|
467
|
-
async function highlightCode(code: string, language: SupportedLanguage): Promise<string> {
|
|
468
|
-
const cacheKey = `${language}:${code}`;
|
|
469
|
-
const cached = highlightedCodeCache.get(cacheKey);
|
|
470
|
-
if (cached) return cached;
|
|
471
|
-
|
|
472
|
-
const highlighted = getHighlighter().then((highlighter) =>
|
|
473
|
-
highlighter.codeToHtml(code, {
|
|
474
|
-
lang: language,
|
|
475
|
-
themes: {
|
|
476
|
-
light: 'github-light',
|
|
477
|
-
dark: 'github-dark-default',
|
|
478
|
-
},
|
|
479
|
-
transformers: [
|
|
480
|
-
transformerNotationDiff({ matchAlgorithm: 'v3' }),
|
|
481
|
-
transformerNotationHighlight({ matchAlgorithm: 'v3' }),
|
|
482
|
-
transformerNotationWordHighlight({ matchAlgorithm: 'v3' }),
|
|
483
|
-
transformerNotationFocus({ matchAlgorithm: 'v3' }),
|
|
484
|
-
transformerNotationErrorLevel({ matchAlgorithm: 'v3' }),
|
|
485
|
-
],
|
|
486
|
-
}),
|
|
487
|
-
);
|
|
488
|
-
highlightedCodeCache.set(cacheKey, highlighted);
|
|
489
|
-
return highlighted;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Language aliases that map to Shiki's bundled language names
|
|
494
|
-
*/
|
|
495
|
-
const LANGUAGE_ALIASES: Record<string, SupportedLanguage> = {
|
|
496
|
-
env: 'dotenv',
|
|
497
|
-
js: 'javascript',
|
|
498
|
-
ts: 'typescript',
|
|
499
|
-
sh: 'bash',
|
|
500
|
-
shell: 'bash',
|
|
501
|
-
yml: 'yaml',
|
|
502
|
-
py: 'python',
|
|
503
|
-
md: 'markdown',
|
|
504
|
-
plaintext: 'ini',
|
|
505
|
-
text: 'ini',
|
|
506
|
-
chroma: 'ini',
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Normalize language identifier for Shiki/BundledLanguage.
|
|
511
|
-
* Falls back to 'ini' for unsupported languages (minimal highlighting).
|
|
512
|
-
*/
|
|
513
|
-
function normalizeLanguage(lang?: string): SupportedLanguage {
|
|
514
|
-
const normalized = lang?.toLowerCase() || 'ini';
|
|
515
|
-
|
|
516
|
-
// Check for aliases first
|
|
517
|
-
const alias = LANGUAGE_ALIASES[normalized];
|
|
518
|
-
if (alias) {
|
|
519
|
-
return alias;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Check if the language is one of the explicitly bundled grammars.
|
|
523
|
-
if (normalized in languageLoaders) {
|
|
524
|
-
return normalized as SupportedLanguage;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
// Fallback to 'ini' for unsupported languages (has minimal highlighting)
|
|
528
|
-
return 'ini';
|
|
529
|
-
}
|
|
530
|
-
|
|
531
403
|
/**
|
|
532
404
|
* Escape HTML special characters for safe display (fallback)
|
|
533
405
|
*/
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
transformerNotationDiff,
|
|
3
|
+
transformerNotationErrorLevel,
|
|
4
|
+
transformerNotationFocus,
|
|
5
|
+
transformerNotationHighlight,
|
|
6
|
+
transformerNotationWordHighlight,
|
|
7
|
+
} from '@shikijs/transformers';
|
|
8
|
+
import { createHighlighterCore } from 'shiki/core';
|
|
9
|
+
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The design system's Shiki setup: a lazy shared highlighter with the DS themes
|
|
13
|
+
* (github-light / github-dark-default), a fixed grammar set, and the `[!code ...]`
|
|
14
|
+
* notation transformers. Exported so consumers rendering highlighted HTML outside
|
|
15
|
+
* of `CodeBlock` (e.g. custom docs pipelines) reuse the exact same setup instead
|
|
16
|
+
* of duplicating it — and get the lazy per-language chunks instead of the full
|
|
17
|
+
* Shiki bundle.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const languageLoaders = {
|
|
21
|
+
bash: () => import('@shikijs/langs/bash').then((module) => module.default),
|
|
22
|
+
css: () => import('@shikijs/langs/css').then((module) => module.default),
|
|
23
|
+
dotenv: () => import('@shikijs/langs/dotenv').then((module) => module.default),
|
|
24
|
+
graphql: () => import('@shikijs/langs/graphql').then((module) => module.default),
|
|
25
|
+
html: () => import('@shikijs/langs/html').then((module) => module.default),
|
|
26
|
+
ini: () => import('@shikijs/langs/ini').then((module) => module.default),
|
|
27
|
+
javascript: () => import('@shikijs/langs/javascript').then((module) => module.default),
|
|
28
|
+
json: () => import('@shikijs/langs/json').then((module) => module.default),
|
|
29
|
+
jsonc: () => import('@shikijs/langs/jsonc').then((module) => module.default),
|
|
30
|
+
jsx: () => import('@shikijs/langs/jsx').then((module) => module.default),
|
|
31
|
+
markdown: () => import('@shikijs/langs/markdown').then((module) => module.default),
|
|
32
|
+
mdx: () => import('@shikijs/langs/mdx').then((module) => module.default),
|
|
33
|
+
python: () => import('@shikijs/langs/python').then((module) => module.default),
|
|
34
|
+
shellscript: () => import('@shikijs/langs/shellscript').then((module) => module.default),
|
|
35
|
+
sql: () => import('@shikijs/langs/sql').then((module) => module.default),
|
|
36
|
+
tsx: () => import('@shikijs/langs/tsx').then((module) => module.default),
|
|
37
|
+
typescript: () => import('@shikijs/langs/typescript').then((module) => module.default),
|
|
38
|
+
yaml: () => import('@shikijs/langs/yaml').then((module) => module.default),
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
type SupportedLanguage = keyof typeof languageLoaders;
|
|
42
|
+
|
|
43
|
+
const themeLoaders = [
|
|
44
|
+
() => import('@shikijs/themes/github-light').then((module) => module.default),
|
|
45
|
+
() => import('@shikijs/themes/github-dark-default').then((module) => module.default),
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
let highlighterPromise: ReturnType<typeof createHighlighterCore> | null = null;
|
|
49
|
+
|
|
50
|
+
function getHighlighter(): ReturnType<typeof createHighlighterCore> {
|
|
51
|
+
highlighterPromise ??= Promise.all([
|
|
52
|
+
Promise.all(themeLoaders.map((loadTheme) => loadTheme())),
|
|
53
|
+
Promise.all(Object.values(languageLoaders).map((loadLanguage) => loadLanguage())),
|
|
54
|
+
]).then(([themes, languages]) =>
|
|
55
|
+
createHighlighterCore({
|
|
56
|
+
themes,
|
|
57
|
+
langs: languages.flat(),
|
|
58
|
+
engine: createJavaScriptRegexEngine(),
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return highlighterPromise;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const highlightedCodeCache = new Map<string, Promise<string>>();
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Highlight code using Shiki with all transformers.
|
|
69
|
+
* Uses Shiki's native notation for highlighting:
|
|
70
|
+
* - // [!code highlight] - highlight a line (use language-appropriate comment)
|
|
71
|
+
* - // [!code ++] / // [!code --] - diff highlighting
|
|
72
|
+
* - // [!code focus] - focus mode (blur other lines)
|
|
73
|
+
* - // [!code word:myVar] - highlight specific word
|
|
74
|
+
* - // [!code error] / // [!code warning] - error levels
|
|
75
|
+
*/
|
|
76
|
+
async function highlightCode(code: string, language: SupportedLanguage): Promise<string> {
|
|
77
|
+
const cacheKey = `${language}:${code}`;
|
|
78
|
+
const cached = highlightedCodeCache.get(cacheKey);
|
|
79
|
+
if (cached) return cached;
|
|
80
|
+
|
|
81
|
+
const highlighted = getHighlighter().then((highlighter) =>
|
|
82
|
+
highlighter.codeToHtml(code, {
|
|
83
|
+
lang: language,
|
|
84
|
+
themes: {
|
|
85
|
+
light: 'github-light',
|
|
86
|
+
dark: 'github-dark-default',
|
|
87
|
+
},
|
|
88
|
+
transformers: [
|
|
89
|
+
transformerNotationDiff({ matchAlgorithm: 'v3' }),
|
|
90
|
+
transformerNotationHighlight({ matchAlgorithm: 'v3' }),
|
|
91
|
+
transformerNotationWordHighlight({ matchAlgorithm: 'v3' }),
|
|
92
|
+
transformerNotationFocus({ matchAlgorithm: 'v3' }),
|
|
93
|
+
transformerNotationErrorLevel({ matchAlgorithm: 'v3' }),
|
|
94
|
+
],
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
highlightedCodeCache.set(cacheKey, highlighted);
|
|
98
|
+
return highlighted;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Language aliases that map to Shiki's bundled language names
|
|
103
|
+
*/
|
|
104
|
+
const LANGUAGE_ALIASES: Record<string, SupportedLanguage> = {
|
|
105
|
+
env: 'dotenv',
|
|
106
|
+
js: 'javascript',
|
|
107
|
+
ts: 'typescript',
|
|
108
|
+
sh: 'bash',
|
|
109
|
+
shell: 'bash',
|
|
110
|
+
yml: 'yaml',
|
|
111
|
+
py: 'python',
|
|
112
|
+
md: 'markdown',
|
|
113
|
+
plaintext: 'ini',
|
|
114
|
+
text: 'ini',
|
|
115
|
+
chroma: 'ini',
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Normalize language identifier for Shiki/BundledLanguage.
|
|
120
|
+
* Falls back to 'ini' for unsupported languages (minimal highlighting).
|
|
121
|
+
*/
|
|
122
|
+
function normalizeLanguage(lang?: string): SupportedLanguage {
|
|
123
|
+
const normalized = lang?.toLowerCase() || 'ini';
|
|
124
|
+
|
|
125
|
+
// Check for aliases first
|
|
126
|
+
const alias = LANGUAGE_ALIASES[normalized];
|
|
127
|
+
if (alias) {
|
|
128
|
+
return alias;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check if the language is one of the explicitly bundled grammars.
|
|
132
|
+
if (normalized in languageLoaders) {
|
|
133
|
+
return normalized as SupportedLanguage;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Fallback to 'ini' for unsupported languages (has minimal highlighting)
|
|
137
|
+
return 'ini';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export { getHighlighter, highlightCode, normalizeLanguage };
|
|
141
|
+
export type { SupportedLanguage };
|