@wallarm-org/design-system 0.22.0 → 0.22.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.
- package/dist/components/CodeSnippet/CodeSnippetActions.js +1 -1
- package/dist/components/CodeSnippet/CodeSnippetCode.js +21 -16
- package/dist/components/CodeSnippet/CodeSnippetContent.js +10 -10
- package/dist/components/CodeSnippet/CodeSnippetContext.d.ts +8 -0
- package/dist/components/CodeSnippet/CodeSnippetLineNumbers.js +10 -7
- package/dist/components/CodeSnippet/CodeSnippetRoot.d.ts +4 -1
- package/dist/components/CodeSnippet/CodeSnippetRoot.js +54 -3
- package/dist/components/CodeSnippet/InlineCodeSnippet.js +1 -1
- package/dist/components/CodeSnippet/index.d.ts +2 -0
- package/dist/components/CodeSnippet/index.js +2 -1
- package/dist/components/CodeSnippet/internal/CodeSnippetHighlights.js +7 -8
- package/dist/components/CodeSnippet/internal/ColorStickColumn.d.ts +2 -2
- package/dist/components/CodeSnippet/internal/ColorStickColumn.js +8 -8
- package/dist/components/CodeSnippet/internal/FoldColumn.d.ts +3 -0
- package/dist/components/CodeSnippet/internal/FoldColumn.js +42 -0
- package/dist/components/CodeSnippet/internal/FoldSummaryLine.d.ts +8 -0
- package/dist/components/CodeSnippet/internal/FoldSummaryLine.js +20 -0
- package/dist/components/CodeSnippet/internal/FoldToggle.d.ts +7 -0
- package/dist/components/CodeSnippet/internal/FoldToggle.js +19 -0
- package/dist/components/CodeSnippet/internal/PrefixColumn.d.ts +2 -2
- package/dist/components/CodeSnippet/internal/PrefixColumn.js +8 -8
- package/dist/components/CodeSnippet/internal/ShowMoreButton.js +2 -2
- package/dist/components/CodeSnippet/lib/foldUtils.d.ts +28 -0
- package/dist/components/CodeSnippet/lib/foldUtils.js +80 -0
- package/dist/components/CodeSnippet/lib/httpFolds.d.ts +27 -0
- package/dist/components/CodeSnippet/lib/httpFolds.js +36 -0
- package/dist/hooks/useCopyTooltip.js +5 -1
- package/dist/metadata/components.json +52 -26
- package/dist/theme/index.css +1 -0
- package/dist/theme/semantic.css +32 -25
- package/dist/theme/utilities/code-snippet-bg.css +8 -0
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ const CodeSnippetActions = ({ className, ref, ...props })=>{
|
|
|
7
7
|
ref: ref,
|
|
8
8
|
"data-slot": "code-snippet-actions",
|
|
9
9
|
"data-testid": testId,
|
|
10
|
-
className: cn('flex items-center gap-4 ml-auto px-6 rounded-tr-6', className),
|
|
10
|
+
className: cn('flex items-center gap-4 ml-auto px-6 rounded-tr-6 code-snippet-bg', className),
|
|
11
11
|
...props
|
|
12
12
|
});
|
|
13
13
|
};
|
|
@@ -1,57 +1,62 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useTestId } from "../../utils/testId.js";
|
|
3
|
-
import { MIN_HIDDEN_LINES_THRESHOLD } from "./CodeSnippetContext.js";
|
|
4
3
|
import { useCodeSnippet } from "./hooks/index.js";
|
|
5
4
|
import { CodeContent, CodeLine, TokenizedCodeLine } from "./internal/index.js";
|
|
5
|
+
import { FoldSummaryLine } from "./internal/FoldSummaryLine.js";
|
|
6
6
|
import { SIZE_LINE_HEIGHT_CLASSES } from "./lib/lineStyles.js";
|
|
7
7
|
import { splitTextByRanges } from "./lib/lineUtils.js";
|
|
8
8
|
const CodeSnippetCode = ({ className, ...props })=>{
|
|
9
9
|
const testId = useTestId('code');
|
|
10
|
-
const { code, tokens, isLoading, wrapLines,
|
|
10
|
+
const { code, tokens, isLoading, wrapLines, lines, size, inlineGutter, showLineNumbers, visibleDisplayItems, toggleFold } = useCodeSnippet();
|
|
11
11
|
const lineHeightClass = SIZE_LINE_HEIGHT_CLASSES[size];
|
|
12
|
-
const
|
|
13
|
-
|
|
12
|
+
const renderFoldSummary = (item)=>/*#__PURE__*/ jsx(FoldSummaryLine, {
|
|
13
|
+
fold: item.fold,
|
|
14
|
+
lineCount: item.lineCount,
|
|
15
|
+
lineHeightClass: lineHeightClass,
|
|
16
|
+
onToggle: ()=>toggleFold(item.fold.id)
|
|
17
|
+
}, `fold-${item.fold.id}`);
|
|
14
18
|
if (isLoading || !tokens) {
|
|
15
19
|
const codeLines = code.split('\n');
|
|
16
|
-
const visibleLines = shouldClip ? codeLines.slice(0, maxLines) : codeLines;
|
|
17
20
|
return /*#__PURE__*/ jsx(CodeContent, {
|
|
18
21
|
wrapLines: wrapLines,
|
|
19
22
|
className: className,
|
|
20
23
|
...props,
|
|
21
24
|
"data-testid": testId,
|
|
22
|
-
children:
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
+
children: visibleDisplayItems.map((item)=>{
|
|
26
|
+
if ('fold-summary' === item.type) return renderFoldSummary(item);
|
|
27
|
+
const line = codeLines[item.index] ?? '';
|
|
28
|
+
const lineConfig = lines.get(item.lineNumber);
|
|
25
29
|
const ranges = lineConfig?.ranges;
|
|
26
30
|
const hasRanges = ranges && ranges.length > 0;
|
|
27
31
|
return /*#__PURE__*/ jsx(CodeLine, {
|
|
28
32
|
lineConfig: lineConfig,
|
|
29
33
|
lineHeightClass: lineHeightClass,
|
|
30
34
|
showInlineGutter: inlineGutter,
|
|
31
|
-
lineNumber: showLineNumbers ? lineNumber : void 0,
|
|
35
|
+
lineNumber: showLineNumbers ? item.lineNumber : void 0,
|
|
32
36
|
children: hasRanges ? splitTextByRanges(line, ranges, lineConfig?.color).map((segment, i)=>/*#__PURE__*/ jsx("span", {
|
|
33
37
|
className: segment.rangeColor,
|
|
34
38
|
children: segment.content
|
|
35
39
|
}, i)) : line
|
|
36
|
-
}, lineNumber);
|
|
40
|
+
}, item.lineNumber);
|
|
37
41
|
})
|
|
38
42
|
});
|
|
39
43
|
}
|
|
40
|
-
const visibleTokens = shouldClip ? tokens.slice(0, maxLines) : tokens;
|
|
41
44
|
return /*#__PURE__*/ jsx(CodeContent, {
|
|
42
45
|
wrapLines: wrapLines,
|
|
43
46
|
className: className,
|
|
44
47
|
...props,
|
|
45
48
|
"data-testid": testId,
|
|
46
|
-
children:
|
|
47
|
-
|
|
49
|
+
children: visibleDisplayItems.map((item)=>{
|
|
50
|
+
if ('fold-summary' === item.type) return renderFoldSummary(item);
|
|
51
|
+
const lineTokens = tokens[item.index];
|
|
52
|
+
if (!lineTokens) return null;
|
|
48
53
|
return /*#__PURE__*/ jsx(TokenizedCodeLine, {
|
|
49
54
|
tokens: lineTokens,
|
|
50
|
-
lineConfig: lines.get(lineNumber),
|
|
55
|
+
lineConfig: lines.get(item.lineNumber),
|
|
51
56
|
lineHeightClass: lineHeightClass,
|
|
52
57
|
showInlineGutter: inlineGutter,
|
|
53
|
-
lineNumber: showLineNumbers ? lineNumber : void 0
|
|
54
|
-
}, lineNumber);
|
|
58
|
+
lineNumber: showLineNumbers ? item.lineNumber : void 0
|
|
59
|
+
}, item.lineNumber);
|
|
55
60
|
})
|
|
56
61
|
});
|
|
57
62
|
};
|
|
@@ -7,21 +7,22 @@ import { CodeSnippetContext } from "./CodeSnippetContext.js";
|
|
|
7
7
|
import { useCodeSnippet } from "./hooks/index.js";
|
|
8
8
|
import { CodeSnippetHighlights } from "./internal/CodeSnippetHighlights.js";
|
|
9
9
|
import { ColorStickColumn } from "./internal/ColorStickColumn.js";
|
|
10
|
+
import { FoldColumn } from "./internal/FoldColumn.js";
|
|
10
11
|
import { PrefixColumn } from "./internal/PrefixColumn.js";
|
|
11
12
|
import { SIZE_LINE_HEIGHT_CLASSES } from "./lib/lineStyles.js";
|
|
12
13
|
const CodeSnippetContent = ({ className, children, nativeScroll = false, ...props })=>{
|
|
13
14
|
const testId = useTestId('content');
|
|
14
15
|
const context = useCodeSnippet();
|
|
15
|
-
const { wrapLines, lines,
|
|
16
|
+
const { wrapLines, lines, visibleDisplayItems, folds, size } = context;
|
|
17
|
+
const hasFolds = folds.length > 0;
|
|
16
18
|
const hasHighlights = Array.from(lines.values()).some((config)=>null != config.color);
|
|
17
19
|
const hasAnyPrefix = Array.from(lines.values()).some((config)=>null != config.prefix);
|
|
18
|
-
const lineCount = totalLines;
|
|
19
20
|
const lineHeightClass = SIZE_LINE_HEIGHT_CLASSES[size];
|
|
20
21
|
const childrenArray = Children.toArray(children);
|
|
21
22
|
const lineNumbersElement = childrenArray.find((child)=>/*#__PURE__*/ isValidElement(child) && child.type?.displayName === 'CodeSnippetLineNumbers');
|
|
22
23
|
const otherChildren = childrenArray.filter((child)=>!/*#__PURE__*/ isValidElement(child) || child.type?.displayName !== 'CodeSnippetLineNumbers');
|
|
23
24
|
const hasLineNumbers = Boolean(lineNumbersElement);
|
|
24
|
-
const needsGutterElements = hasHighlights || hasLineNumbers || hasAnyPrefix;
|
|
25
|
+
const needsGutterElements = hasHighlights || hasLineNumbers || hasAnyPrefix || hasFolds;
|
|
25
26
|
const useInlineGutter = wrapLines && needsGutterElements;
|
|
26
27
|
const hasGutter = !wrapLines && needsGutterElements;
|
|
27
28
|
const overriddenContext = useMemo(()=>({
|
|
@@ -39,18 +40,17 @@ const CodeSnippetContent = ({ className, children, nativeScroll = false, ...prop
|
|
|
39
40
|
!wrapLines && hasHighlights && /*#__PURE__*/ jsx(CodeSnippetHighlights, {}),
|
|
40
41
|
hasGutter && /*#__PURE__*/ jsxs("div", {
|
|
41
42
|
"data-slot": "code-snippet-gutter",
|
|
42
|
-
className: "sticky left-0 z-20 flex shrink-0 py-8 -my-8 mr-8
|
|
43
|
+
className: "sticky left-0 z-20 flex shrink-0 py-8 -my-8 mr-8 code-snippet-bg [contain:paint]",
|
|
43
44
|
children: [
|
|
44
45
|
hasHighlights && /*#__PURE__*/ jsx(ColorStickColumn, {
|
|
45
|
-
|
|
46
|
-
startingLineNumber: startingLineNumber,
|
|
46
|
+
visibleDisplayItems: visibleDisplayItems,
|
|
47
47
|
lines: lines,
|
|
48
48
|
lineHeightClass: lineHeightClass
|
|
49
49
|
}),
|
|
50
50
|
lineNumbersElement,
|
|
51
|
+
hasFolds && /*#__PURE__*/ jsx(FoldColumn, {}),
|
|
51
52
|
hasAnyPrefix && /*#__PURE__*/ jsx(PrefixColumn, {
|
|
52
|
-
|
|
53
|
-
startingLineNumber: startingLineNumber,
|
|
53
|
+
visibleDisplayItems: visibleDisplayItems,
|
|
54
54
|
lines: lines,
|
|
55
55
|
lineHeightClass: lineHeightClass
|
|
56
56
|
})
|
|
@@ -68,14 +68,14 @@ const CodeSnippetContent = ({ className, children, nativeScroll = false, ...prop
|
|
|
68
68
|
if (nativeScroll) return /*#__PURE__*/ jsx("div", {
|
|
69
69
|
"data-slot": "code-snippet-content",
|
|
70
70
|
"data-testid": testId,
|
|
71
|
-
className: cn('min-h-0', wrapLines ? 'overflow-y-auto overflow-x-hidden' : 'overflow-auto', className),
|
|
71
|
+
className: cn('min-h-0 overscroll-none', wrapLines ? 'overflow-y-auto overflow-x-hidden' : 'overflow-auto', className),
|
|
72
72
|
...props,
|
|
73
73
|
children: innerContent
|
|
74
74
|
});
|
|
75
75
|
return /*#__PURE__*/ jsx("div", {
|
|
76
76
|
"data-slot": "code-snippet-content",
|
|
77
77
|
"data-testid": testId,
|
|
78
|
-
className: cn('min-h-0', className),
|
|
78
|
+
className: cn('min-h-0 [&_[data-part=viewport]]:overscroll-none', className),
|
|
79
79
|
...props,
|
|
80
80
|
children: /*#__PURE__*/ jsxs(ScrollArea, {
|
|
81
81
|
children: [
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode } from 'react';
|
|
2
2
|
import type { SyntaxAdapter, Token } from './adapters/types';
|
|
3
|
+
import type { DisplayItem, FoldRegion } from './lib/foldUtils';
|
|
3
4
|
export type CodeSnippetSize = 'sm' | 'md' | 'lg';
|
|
4
5
|
export type LineColor = 'danger' | 'warning' | 'info' | 'success' | 'brand' | 'ai' | 'neutral';
|
|
5
6
|
export type LineTextStyle = 'regular' | 'medium' | 'italic';
|
|
@@ -39,6 +40,13 @@ export type CodeSnippetContextValue<TLanguage extends string = string> = {
|
|
|
39
40
|
showLineNumbers: boolean;
|
|
40
41
|
lines: Map<number, LineConfig>;
|
|
41
42
|
totalLines: number;
|
|
43
|
+
/** All display items (post-fold, pre-clip). Used for ShowMore counting. */
|
|
44
|
+
displayItems: DisplayItem[];
|
|
45
|
+
/** Display items clipped to maxLines. All rendering components should iterate this. */
|
|
46
|
+
visibleDisplayItems: DisplayItem[];
|
|
47
|
+
folds: FoldRegion[];
|
|
48
|
+
collapsedFolds: Set<string>;
|
|
49
|
+
toggleFold: (foldId: string) => void;
|
|
42
50
|
isExpanded: boolean;
|
|
43
51
|
maxLines: number;
|
|
44
52
|
isFullscreen: boolean;
|
|
@@ -5,7 +5,7 @@ import { useCodeSnippet } from "./hooks/index.js";
|
|
|
5
5
|
import { LINE_COLOR_STYLES, SIZE_LINE_HEIGHT_CLASSES } from "./lib/lineStyles.js";
|
|
6
6
|
const CodeSnippetLineNumbers = ({ className, ...props })=>{
|
|
7
7
|
const testId = useTestId('line-numbers');
|
|
8
|
-
const { tokens,
|
|
8
|
+
const { tokens, visibleDisplayItems, lines, size } = useCodeSnippet();
|
|
9
9
|
const lineHeightClass = SIZE_LINE_HEIGHT_CLASSES[size];
|
|
10
10
|
if (!tokens) return null;
|
|
11
11
|
return /*#__PURE__*/ jsx("div", {
|
|
@@ -13,14 +13,17 @@ const CodeSnippetLineNumbers = ({ className, ...props })=>{
|
|
|
13
13
|
"data-testid": testId,
|
|
14
14
|
className: cn('flex flex-col text-text-secondary select-none text-right', className),
|
|
15
15
|
...props,
|
|
16
|
-
children:
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
children: visibleDisplayItems.map((item)=>{
|
|
17
|
+
if ('fold-summary' === item.type) return /*#__PURE__*/ jsx("span", {
|
|
18
|
+
className: cn(lineHeightClass, 'px-8'),
|
|
19
|
+
children: item.fold.startLine
|
|
20
|
+
}, `fold-${item.fold.id}`);
|
|
21
|
+
const lineConfig = lines.get(item.lineNumber);
|
|
19
22
|
const colorStyles = lineConfig?.color ? LINE_COLOR_STYLES[lineConfig.color] : void 0;
|
|
20
23
|
return /*#__PURE__*/ jsx("span", {
|
|
21
|
-
className: cn(lineHeightClass, 'px-8
|
|
22
|
-
children: lineNumber
|
|
23
|
-
}, lineNumber);
|
|
24
|
+
className: cn(lineHeightClass, 'px-8', colorStyles?.text, colorStyles?.bg),
|
|
25
|
+
children: item.lineNumber
|
|
26
|
+
}, item.lineNumber);
|
|
24
27
|
})
|
|
25
28
|
});
|
|
26
29
|
};
|
|
@@ -2,6 +2,7 @@ import type { HTMLAttributes, ReactNode, Ref } from 'react';
|
|
|
2
2
|
import { type VariantProps } from 'class-variance-authority';
|
|
3
3
|
import { type TestableProps } from '../../utils/testId';
|
|
4
4
|
import { type LineConfig } from './CodeSnippetContext';
|
|
5
|
+
import { type FoldRegion } from './lib/foldUtils';
|
|
5
6
|
declare const codeSnippetRootVariants: (props?: ({
|
|
6
7
|
size?: "sm" | "md" | "lg" | null | undefined;
|
|
7
8
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -21,13 +22,15 @@ export type CodeSnippetRootProps<TLanguage extends string = string> = CodeSnippe
|
|
|
21
22
|
wrapLines?: boolean;
|
|
22
23
|
/** Max lines before collapsing (for ShowMore) */
|
|
23
24
|
maxLines?: number;
|
|
25
|
+
/** Foldable regions that can be collapsed/expanded */
|
|
26
|
+
folds?: FoldRegion[];
|
|
24
27
|
/** Callback when code is copied */
|
|
25
28
|
onCopy?: (code: string) => void;
|
|
26
29
|
/** Child components */
|
|
27
30
|
children?: ReactNode;
|
|
28
31
|
};
|
|
29
32
|
export declare const CodeSnippetRoot: {
|
|
30
|
-
<TLanguage extends string = string>({ code, language, size, lines, startingLineNumber, wrapLines: initialWrapLines, maxLines, onCopy, className, children, "data-testid": testId, ...props }: CodeSnippetRootProps<TLanguage>): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
<TLanguage extends string = string>({ code, language, size, lines, startingLineNumber, wrapLines: initialWrapLines, maxLines, folds: foldsProp, onCopy, className, children, "data-testid": testId, ...props }: CodeSnippetRootProps<TLanguage>): import("react/jsx-runtime").JSX.Element;
|
|
31
34
|
displayName: string;
|
|
32
35
|
};
|
|
33
36
|
export {};
|
|
@@ -5,10 +5,11 @@ import { cva } from "class-variance-authority";
|
|
|
5
5
|
import { cn } from "../../utils/cn.js";
|
|
6
6
|
import { TestIdProvider } from "../../utils/testId.js";
|
|
7
7
|
import { plainAdapter } from "./adapters/plain.js";
|
|
8
|
-
import { CodeSnippetContext } from "./CodeSnippetContext.js";
|
|
8
|
+
import { CodeSnippetContext, MIN_HIDDEN_LINES_THRESHOLD } from "./CodeSnippetContext.js";
|
|
9
9
|
import { useAdapter } from "./hooks/index.js";
|
|
10
10
|
import { ShowMoreButton } from "./internal/ShowMoreButton.js";
|
|
11
|
-
|
|
11
|
+
import { buildDisplayItems, validateFolds } from "./lib/foldUtils.js";
|
|
12
|
+
const codeSnippetRootVariants = cva("relative code-snippet-bg rounded-6 font-mono text-syntax-no-syntax overflow-hidden flex flex-col [&::selection]:bg-[var(--color-syntax-highlight-selected-highlight)] [&::selection]:text-[var(--color-syntax-highlight-selected-code)] [&_*::selection]:bg-[var(--color-syntax-highlight-selected-highlight)] [&_*::selection]:text-[var(--color-syntax-highlight-selected-code)] [&>[data-slot=code-snippet-actions]]:absolute [&>[data-slot=code-snippet-actions]]:right-0 [&>[data-slot=code-snippet-actions]]:top-0 [&>[data-slot=code-snippet-actions]]:z-30 [&>[data-slot=code-snippet-actions]]:p-6 [&>[data-slot=code-snippet-actions]]:rounded-br-6 [&>[data-slot=code-snippet-actions]]:rounded-tl-6", {
|
|
12
13
|
variants: {
|
|
13
14
|
size: {
|
|
14
15
|
sm: 'text-xs leading-sm',
|
|
@@ -20,7 +21,8 @@ const codeSnippetRootVariants = cva("relative bg-component-code-snippet-bg round
|
|
|
20
21
|
size: 'sm'
|
|
21
22
|
}
|
|
22
23
|
});
|
|
23
|
-
const
|
|
24
|
+
const EMPTY_LINES = {};
|
|
25
|
+
const CodeSnippetRoot = ({ code, language = 'text', size = 'sm', lines = EMPTY_LINES, startingLineNumber = 1, wrapLines: initialWrapLines = false, maxLines = 0, folds: foldsProp, onCopy, className, children, 'data-testid': testId, ...props })=>{
|
|
24
26
|
const adapterContext = useAdapter();
|
|
25
27
|
const [wrapLines, setWrapLines] = useState(initialWrapLines);
|
|
26
28
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
@@ -79,6 +81,45 @@ const CodeSnippetRoot = ({ code, language = 'text', size = 'sm', lines = {}, sta
|
|
|
79
81
|
onCopy
|
|
80
82
|
]);
|
|
81
83
|
const totalLines = tokens?.length ?? code.split('\n').length;
|
|
84
|
+
const validatedFolds = useMemo(()=>foldsProp ? validateFolds(foldsProp, totalLines, startingLineNumber) : [], [
|
|
85
|
+
foldsProp,
|
|
86
|
+
totalLines,
|
|
87
|
+
startingLineNumber
|
|
88
|
+
]);
|
|
89
|
+
const computeCollapsedFolds = useCallback((folds)=>{
|
|
90
|
+
if (!folds) return new Set();
|
|
91
|
+
return new Set(folds.filter((f)=>false !== f.defaultCollapsed).map((f)=>f.id));
|
|
92
|
+
}, []);
|
|
93
|
+
const [collapsedFolds, setCollapsedFolds] = useState(()=>computeCollapsedFolds(foldsProp));
|
|
94
|
+
useEffect(()=>{
|
|
95
|
+
setCollapsedFolds(computeCollapsedFolds(foldsProp));
|
|
96
|
+
}, [
|
|
97
|
+
foldsProp,
|
|
98
|
+
computeCollapsedFolds
|
|
99
|
+
]);
|
|
100
|
+
const toggleFold = useCallback((foldId)=>{
|
|
101
|
+
setCollapsedFolds((prev)=>{
|
|
102
|
+
const next = new Set(prev);
|
|
103
|
+
if (next.has(foldId)) next.delete(foldId);
|
|
104
|
+
else next.add(foldId);
|
|
105
|
+
return next;
|
|
106
|
+
});
|
|
107
|
+
}, []);
|
|
108
|
+
const displayItems = useMemo(()=>buildDisplayItems(totalLines, validatedFolds, collapsedFolds, startingLineNumber), [
|
|
109
|
+
totalLines,
|
|
110
|
+
validatedFolds,
|
|
111
|
+
collapsedFolds,
|
|
112
|
+
startingLineNumber
|
|
113
|
+
]);
|
|
114
|
+
const visibleDisplayItems = useMemo(()=>{
|
|
115
|
+
const hiddenRows = displayItems.length - maxLines;
|
|
116
|
+
const shouldClip = maxLines > 0 && !isExpanded && hiddenRows >= MIN_HIDDEN_LINES_THRESHOLD;
|
|
117
|
+
return shouldClip ? displayItems.slice(0, maxLines) : displayItems;
|
|
118
|
+
}, [
|
|
119
|
+
displayItems,
|
|
120
|
+
maxLines,
|
|
121
|
+
isExpanded
|
|
122
|
+
]);
|
|
82
123
|
const contextValue = useMemo(()=>({
|
|
83
124
|
code,
|
|
84
125
|
language,
|
|
@@ -94,6 +135,11 @@ const CodeSnippetRoot = ({ code, language = 'text', size = 'sm', lines = {}, sta
|
|
|
94
135
|
v
|
|
95
136
|
])),
|
|
96
137
|
totalLines,
|
|
138
|
+
displayItems,
|
|
139
|
+
visibleDisplayItems,
|
|
140
|
+
folds: validatedFolds,
|
|
141
|
+
collapsedFolds,
|
|
142
|
+
toggleFold,
|
|
97
143
|
isExpanded,
|
|
98
144
|
maxLines,
|
|
99
145
|
isFullscreen,
|
|
@@ -112,6 +158,11 @@ const CodeSnippetRoot = ({ code, language = 'text', size = 'sm', lines = {}, sta
|
|
|
112
158
|
startingLineNumber,
|
|
113
159
|
lines,
|
|
114
160
|
totalLines,
|
|
161
|
+
displayItems,
|
|
162
|
+
visibleDisplayItems,
|
|
163
|
+
validatedFolds,
|
|
164
|
+
collapsedFolds,
|
|
165
|
+
toggleFold,
|
|
115
166
|
isExpanded,
|
|
116
167
|
maxLines,
|
|
117
168
|
isFullscreen,
|
|
@@ -4,7 +4,7 @@ import { cva } from "class-variance-authority";
|
|
|
4
4
|
import { useCopyTooltip } from "../../hooks/index.js";
|
|
5
5
|
import { cn } from "../../utils/cn.js";
|
|
6
6
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../Tooltip/index.js";
|
|
7
|
-
const inlineCodeSnippetVariants = cva("inline-flex items-center
|
|
7
|
+
const inlineCodeSnippetVariants = cva("inline-flex items-center code-snippet-bg rounded-6 font-mono font-normal text-syntax-no-syntax", {
|
|
8
8
|
variants: {
|
|
9
9
|
size: {
|
|
10
10
|
sm: 'text-xs px-4 py-2',
|
|
@@ -16,3 +16,5 @@ export { CodeSnippetTitle, type CodeSnippetTitleProps } from './CodeSnippetTitle
|
|
|
16
16
|
export { CodeSnippetWrapButton, type CodeSnippetWrapButtonProps } from './CodeSnippetWrapButton';
|
|
17
17
|
export { useAdapter, useCodeSnippet } from './hooks';
|
|
18
18
|
export { InlineCodeSnippet, type InlineCodeSnippetProps } from './InlineCodeSnippet';
|
|
19
|
+
export { type FoldRegion } from './lib/foldUtils';
|
|
20
|
+
export { getHttpFolds, HTTP_FOLD_ID, type HttpFoldOptions, type HttpFoldSectionOptions, } from './lib/httpFolds';
|
|
@@ -14,4 +14,5 @@ import { CodeSnippetTitle } from "./CodeSnippetTitle.js";
|
|
|
14
14
|
import { CodeSnippetWrapButton } from "./CodeSnippetWrapButton.js";
|
|
15
15
|
import { useAdapter, useCodeSnippet } from "./hooks/index.js";
|
|
16
16
|
import { InlineCodeSnippet } from "./InlineCodeSnippet.js";
|
|
17
|
-
|
|
17
|
+
import { HTTP_FOLD_ID, getHttpFolds } from "./lib/httpFolds.js";
|
|
18
|
+
export { CodeSnippetActions, CodeSnippetAdapterProvider, CodeSnippetCode, CodeSnippetContent, CodeSnippetCopyButton, CodeSnippetFullscreenButton, CodeSnippetHeader, CodeSnippetLineNumbers, CodeSnippetRoot, CodeSnippetTab, CodeSnippetTabs, CodeSnippetTitle, CodeSnippetWrapButton, HTTP_FOLD_ID, InlineCodeSnippet, getHttpFolds, loadHighlightJsAdapter, loadPrismAdapter, loadShikiAdapter, plainAdapter, useAdapter, useCodeSnippet };
|
|
@@ -3,22 +3,21 @@ import { cn } from "../../../utils/cn.js";
|
|
|
3
3
|
import { useCodeSnippet } from "../hooks/index.js";
|
|
4
4
|
import { LINE_COLOR_STYLES, SIZE_LINE_HEIGHT_CLASSES } from "../lib/lineStyles.js";
|
|
5
5
|
const CodeSnippetHighlights = ()=>{
|
|
6
|
-
const {
|
|
7
|
-
const lineCount = totalLines;
|
|
6
|
+
const { visibleDisplayItems, lines, size } = useCodeSnippet();
|
|
8
7
|
const lineHeightClass = SIZE_LINE_HEIGHT_CLASSES[size];
|
|
9
8
|
return /*#__PURE__*/ jsx("div", {
|
|
10
9
|
"data-slot": "code-snippet-highlights",
|
|
11
10
|
className: "absolute inset-0 z-0 flex flex-col py-8 pointer-events-none",
|
|
12
11
|
"aria-hidden": "true",
|
|
13
|
-
children:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const lineConfig = lines.get(lineNumber);
|
|
12
|
+
children: visibleDisplayItems.map((item)=>{
|
|
13
|
+
if ('fold-summary' === item.type) return /*#__PURE__*/ jsx("div", {
|
|
14
|
+
className: lineHeightClass
|
|
15
|
+
}, `fold-${item.fold.id}`);
|
|
16
|
+
const lineConfig = lines.get(item.lineNumber);
|
|
18
17
|
const colorStyles = lineConfig?.color ? LINE_COLOR_STYLES[lineConfig.color] : null;
|
|
19
18
|
return /*#__PURE__*/ jsx("div", {
|
|
20
19
|
className: cn(lineHeightClass, colorStyles?.bg)
|
|
21
|
-
}, lineNumber);
|
|
20
|
+
}, item.lineNumber);
|
|
22
21
|
})
|
|
23
22
|
});
|
|
24
23
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { FC } from 'react';
|
|
2
2
|
import type { LineConfig } from '../CodeSnippetContext';
|
|
3
|
+
import type { DisplayItem } from '../lib/foldUtils';
|
|
3
4
|
/** Color stick column component - renders color indicators for each line */
|
|
4
5
|
export declare const ColorStickColumn: FC<{
|
|
5
|
-
|
|
6
|
-
startingLineNumber: number;
|
|
6
|
+
visibleDisplayItems: DisplayItem[];
|
|
7
7
|
lines: Map<number, LineConfig>;
|
|
8
8
|
lineHeightClass: string;
|
|
9
9
|
}>;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../../../utils/cn.js";
|
|
3
3
|
import { LINE_COLOR_STYLES } from "../lib/lineStyles.js";
|
|
4
|
-
const ColorStickColumn = ({
|
|
4
|
+
const ColorStickColumn = ({ visibleDisplayItems, lines, lineHeightClass })=>/*#__PURE__*/ jsx("div", {
|
|
5
5
|
className: "flex flex-col",
|
|
6
6
|
"data-slot": "code-snippet-color-stick",
|
|
7
|
-
children:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const lineConfig = lines.get(lineNumber);
|
|
7
|
+
children: visibleDisplayItems.map((item)=>{
|
|
8
|
+
if ('fold-summary' === item.type) return /*#__PURE__*/ jsx("span", {
|
|
9
|
+
className: cn(lineHeightClass, 'border-l-2 pl-12 border-transparent')
|
|
10
|
+
}, `fold-${item.fold.id}`);
|
|
11
|
+
const lineConfig = lines.get(item.lineNumber);
|
|
12
12
|
const colorStyles = lineConfig?.color ? LINE_COLOR_STYLES[lineConfig.color] : void 0;
|
|
13
13
|
return /*#__PURE__*/ jsx("span", {
|
|
14
|
-
className: cn(lineHeightClass, 'border-l-2 pl-12',
|
|
15
|
-
}, lineNumber);
|
|
14
|
+
className: cn(lineHeightClass, 'border-l-2 pl-12', colorStyles?.border ?? 'border-transparent', colorStyles?.bg)
|
|
15
|
+
}, item.lineNumber);
|
|
16
16
|
})
|
|
17
17
|
});
|
|
18
18
|
export { ColorStickColumn };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { cn } from "../../../utils/cn.js";
|
|
4
|
+
import { useCodeSnippet } from "../hooks/index.js";
|
|
5
|
+
import { LINE_COLOR_STYLES, SIZE_LINE_HEIGHT_CLASSES } from "../lib/lineStyles.js";
|
|
6
|
+
import { FoldToggle } from "./FoldToggle.js";
|
|
7
|
+
const FoldColumn = ()=>{
|
|
8
|
+
const { visibleDisplayItems, folds, collapsedFolds, toggleFold, size, lines } = useCodeSnippet();
|
|
9
|
+
const lineHeightClass = SIZE_LINE_HEIGHT_CLASSES[size];
|
|
10
|
+
const foldByStartLine = useMemo(()=>new Map(folds.map((f)=>[
|
|
11
|
+
f.startLine,
|
|
12
|
+
f
|
|
13
|
+
])), [
|
|
14
|
+
folds
|
|
15
|
+
]);
|
|
16
|
+
return /*#__PURE__*/ jsx("div", {
|
|
17
|
+
className: "flex flex-col select-none",
|
|
18
|
+
"data-slot": "code-snippet-fold",
|
|
19
|
+
children: visibleDisplayItems.map((item)=>{
|
|
20
|
+
if ('fold-summary' === item.type) return /*#__PURE__*/ jsx("span", {
|
|
21
|
+
className: cn(lineHeightClass, 'flex h-lh items-center justify-center px-4'),
|
|
22
|
+
children: /*#__PURE__*/ jsx(FoldToggle, {
|
|
23
|
+
fold: item.fold,
|
|
24
|
+
isCollapsed: true,
|
|
25
|
+
onToggle: ()=>toggleFold(item.fold.id)
|
|
26
|
+
})
|
|
27
|
+
}, `fold-${item.fold.id}`);
|
|
28
|
+
const fold = foldByStartLine.get(item.lineNumber);
|
|
29
|
+
const lineConfig = lines.get(item.lineNumber);
|
|
30
|
+
const colorStyles = lineConfig?.color ? LINE_COLOR_STYLES[lineConfig.color] : void 0;
|
|
31
|
+
return /*#__PURE__*/ jsx("span", {
|
|
32
|
+
className: cn(lineHeightClass, 'flex h-lh items-center justify-center px-4', colorStyles?.bg),
|
|
33
|
+
children: fold && !collapsedFolds.has(fold.id) ? /*#__PURE__*/ jsx(FoldToggle, {
|
|
34
|
+
fold: fold,
|
|
35
|
+
isCollapsed: false,
|
|
36
|
+
onToggle: ()=>toggleFold(fold.id)
|
|
37
|
+
}) : null
|
|
38
|
+
}, item.lineNumber);
|
|
39
|
+
})
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
export { FoldColumn };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../../utils/cn.js";
|
|
3
|
+
import { getFoldSummaryLabel } from "../lib/foldUtils.js";
|
|
4
|
+
const FoldSummaryLine = ({ fold, lineCount, lineHeightClass, onToggle })=>{
|
|
5
|
+
const label = getFoldSummaryLabel(fold, lineCount);
|
|
6
|
+
const ariaLabel = `Collapsed region: ${label}, ${lineCount} lines`;
|
|
7
|
+
return /*#__PURE__*/ jsx("button", {
|
|
8
|
+
type: "button",
|
|
9
|
+
className: cn(lineHeightClass, 'flex w-full items-center gap-4 px-4 text-text-secondary hover:text-text-primary hover:bg-bg-secondary-hover transition-colors cursor-pointer select-none text-left'),
|
|
10
|
+
"aria-expanded": false,
|
|
11
|
+
"aria-label": ariaLabel,
|
|
12
|
+
onClick: onToggle,
|
|
13
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
14
|
+
className: "inline-flex items-center rounded-2 border border-border-secondary px-6 text-xs leading-sm",
|
|
15
|
+
children: label
|
|
16
|
+
})
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
FoldSummaryLine.displayName = 'FoldSummaryLine';
|
|
20
|
+
export { FoldSummaryLine };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronDown } from "../../../icons/ChevronDown.js";
|
|
3
|
+
import { ChevronRight } from "../../../icons/ChevronRight.js";
|
|
4
|
+
import { getFoldSummaryLabel } from "../lib/foldUtils.js";
|
|
5
|
+
const FoldToggle = ({ fold, isCollapsed, onToggle })=>{
|
|
6
|
+
const lineCount = fold.endLine - fold.startLine + 1;
|
|
7
|
+
const label = getFoldSummaryLabel(fold, lineCount);
|
|
8
|
+
const ariaLabel = isCollapsed ? `Expand ${label}` : `Collapse ${label}`;
|
|
9
|
+
return /*#__PURE__*/ jsx("button", {
|
|
10
|
+
type: "button",
|
|
11
|
+
className: "flex items-center justify-center w-16 h-16 rounded-2 text-text-secondary hover:text-text-primary hover:bg-bg-secondary-hover transition-colors cursor-pointer",
|
|
12
|
+
"aria-expanded": !isCollapsed,
|
|
13
|
+
"aria-label": ariaLabel,
|
|
14
|
+
onClick: onToggle,
|
|
15
|
+
children: isCollapsed ? /*#__PURE__*/ jsx(ChevronRight, {}) : /*#__PURE__*/ jsx(ChevronDown, {})
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
FoldToggle.displayName = 'FoldToggle';
|
|
19
|
+
export { FoldToggle };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { FC } from 'react';
|
|
2
2
|
import type { LineConfig } from '../CodeSnippetContext';
|
|
3
|
+
import type { DisplayItem } from '../lib/foldUtils';
|
|
3
4
|
/** Prefix column component - renders all line prefixes in a separate column */
|
|
4
5
|
export declare const PrefixColumn: FC<{
|
|
5
|
-
|
|
6
|
-
startingLineNumber: number;
|
|
6
|
+
visibleDisplayItems: DisplayItem[];
|
|
7
7
|
lines: Map<number, LineConfig>;
|
|
8
8
|
lineHeightClass: string;
|
|
9
9
|
}>;
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../../../utils/cn.js";
|
|
3
3
|
import { LINE_COLOR_STYLES } from "../lib/lineStyles.js";
|
|
4
|
-
const PrefixColumn = ({
|
|
4
|
+
const PrefixColumn = ({ visibleDisplayItems, lines, lineHeightClass })=>/*#__PURE__*/ jsx("div", {
|
|
5
5
|
className: "flex flex-col select-none",
|
|
6
6
|
"data-slot": "code-snippet-prefix",
|
|
7
|
-
children:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const lineConfig = lines.get(lineNumber);
|
|
7
|
+
children: visibleDisplayItems.map((item)=>{
|
|
8
|
+
if ('fold-summary' === item.type) return /*#__PURE__*/ jsx("span", {
|
|
9
|
+
className: cn(lineHeightClass, 'flex h-lh items-center justify-center px-8')
|
|
10
|
+
}, `fold-${item.fold.id}`);
|
|
11
|
+
const lineConfig = lines.get(item.lineNumber);
|
|
12
12
|
const colorStyles = lineConfig?.color ? LINE_COLOR_STYLES[lineConfig.color] : void 0;
|
|
13
13
|
return /*#__PURE__*/ jsx("span", {
|
|
14
|
-
className: cn(lineHeightClass, 'flex h-lh items-center justify-center px-8
|
|
14
|
+
className: cn(lineHeightClass, 'flex h-lh items-center justify-center px-8', colorStyles?.text, colorStyles?.bg),
|
|
15
15
|
children: lineConfig?.prefix
|
|
16
|
-
}, lineNumber);
|
|
16
|
+
}, item.lineNumber);
|
|
17
17
|
})
|
|
18
18
|
});
|
|
19
19
|
export { PrefixColumn };
|
|
@@ -5,8 +5,8 @@ import { Button } from "../../Button/index.js";
|
|
|
5
5
|
import { MIN_HIDDEN_LINES_THRESHOLD } from "../CodeSnippetContext.js";
|
|
6
6
|
import { useCodeSnippet } from "../hooks/index.js";
|
|
7
7
|
const ShowMoreButton = ()=>{
|
|
8
|
-
const {
|
|
9
|
-
const hiddenLines =
|
|
8
|
+
const { displayItems, maxLines, isExpanded, setIsExpanded } = useCodeSnippet();
|
|
9
|
+
const hiddenLines = displayItems.length - maxLines;
|
|
10
10
|
if (maxLines <= 0 || hiddenLines < MIN_HIDDEN_LINES_THRESHOLD) return null;
|
|
11
11
|
return /*#__PURE__*/ jsx("div", {
|
|
12
12
|
"data-slot": "code-snippet-show-more",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type FoldRegion = {
|
|
2
|
+
/** Unique identifier for this fold region */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Inclusive start line number (1-based) */
|
|
5
|
+
startLine: number;
|
|
6
|
+
/** Inclusive end line number */
|
|
7
|
+
endLine: number;
|
|
8
|
+
/** Optional label for the collapsed summary. When omitted, auto-generated from line count. */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Whether the fold starts collapsed. Default: true */
|
|
11
|
+
defaultCollapsed?: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type DisplayItem = {
|
|
14
|
+
type: 'line';
|
|
15
|
+
index: number;
|
|
16
|
+
lineNumber: number;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'fold-summary';
|
|
19
|
+
fold: FoldRegion;
|
|
20
|
+
lineCount: number;
|
|
21
|
+
};
|
|
22
|
+
export declare function getFoldSummaryLabel(fold: FoldRegion, lineCount: number): string;
|
|
23
|
+
/**
|
|
24
|
+
* Validates fold regions and returns a clean, sorted list.
|
|
25
|
+
* Dev: logs warnings for invalid folds. Prod: silently filters them out.
|
|
26
|
+
*/
|
|
27
|
+
export declare function validateFolds(folds: FoldRegion[], totalLines: number, startingLineNumber?: number): FoldRegion[];
|
|
28
|
+
export declare function buildDisplayItems(totalLines: number, folds: FoldRegion[], collapsedFolds: Set<string>, startingLineNumber: number): DisplayItem[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
function getFoldSummaryLabel(fold, lineCount) {
|
|
2
|
+
return fold.label ?? `${lineCount} lines`;
|
|
3
|
+
}
|
|
4
|
+
function validateFolds(folds, totalLines, startingLineNumber = 1) {
|
|
5
|
+
const isDev = 'production' !== process.env.NODE_ENV;
|
|
6
|
+
const valid = [];
|
|
7
|
+
const seenIds = new Set();
|
|
8
|
+
const lastLine = startingLineNumber + totalLines - 1;
|
|
9
|
+
const sorted = [
|
|
10
|
+
...folds
|
|
11
|
+
].sort((a, b)=>a.startLine - b.startLine);
|
|
12
|
+
for (const fold of sorted){
|
|
13
|
+
if (fold.startLine > fold.endLine) {
|
|
14
|
+
if (isDev) console.warn(`[CodeSnippet] Fold "${fold.id}": startLine (${fold.startLine}) > endLine (${fold.endLine}). Skipping.`);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (fold.startLine < startingLineNumber || fold.endLine > lastLine) {
|
|
18
|
+
if (isDev) console.warn(`[CodeSnippet] Fold "${fold.id}": out of bounds (lines ${fold.startLine}-${fold.endLine}, total ${totalLines}). Skipping.`);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (seenIds.has(fold.id)) {
|
|
22
|
+
if (isDev) console.warn(`[CodeSnippet] Fold "${fold.id}": duplicate id. Skipping.`);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const overlaps = valid.some((existing)=>fold.startLine <= existing.endLine && fold.endLine >= existing.startLine);
|
|
26
|
+
if (overlaps) {
|
|
27
|
+
if (isDev) console.warn(`[CodeSnippet] Fold "${fold.id}": overlaps with an existing fold. Skipping.`);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
seenIds.add(fold.id);
|
|
31
|
+
valid.push(fold);
|
|
32
|
+
}
|
|
33
|
+
return valid;
|
|
34
|
+
}
|
|
35
|
+
function buildDisplayItems(totalLines, folds, collapsedFolds, startingLineNumber) {
|
|
36
|
+
if (0 === folds.length) return Array.from({
|
|
37
|
+
length: totalLines
|
|
38
|
+
}, (_, index)=>({
|
|
39
|
+
type: 'line',
|
|
40
|
+
index,
|
|
41
|
+
lineNumber: startingLineNumber + index
|
|
42
|
+
}));
|
|
43
|
+
const items = [];
|
|
44
|
+
let currentIndex = 0;
|
|
45
|
+
for (const fold of folds){
|
|
46
|
+
const foldStartIndex = fold.startLine - startingLineNumber;
|
|
47
|
+
const foldEndIndex = fold.endLine - startingLineNumber;
|
|
48
|
+
while(currentIndex < foldStartIndex){
|
|
49
|
+
items.push({
|
|
50
|
+
type: 'line',
|
|
51
|
+
index: currentIndex,
|
|
52
|
+
lineNumber: startingLineNumber + currentIndex
|
|
53
|
+
});
|
|
54
|
+
currentIndex++;
|
|
55
|
+
}
|
|
56
|
+
if (collapsedFolds.has(fold.id)) {
|
|
57
|
+
const lineCount = fold.endLine - fold.startLine + 1;
|
|
58
|
+
items.push({
|
|
59
|
+
type: 'fold-summary',
|
|
60
|
+
fold,
|
|
61
|
+
lineCount
|
|
62
|
+
});
|
|
63
|
+
} else for(let i = foldStartIndex; i <= foldEndIndex; i++)items.push({
|
|
64
|
+
type: 'line',
|
|
65
|
+
index: i,
|
|
66
|
+
lineNumber: startingLineNumber + i
|
|
67
|
+
});
|
|
68
|
+
currentIndex = foldEndIndex + 1;
|
|
69
|
+
}
|
|
70
|
+
while(currentIndex < totalLines){
|
|
71
|
+
items.push({
|
|
72
|
+
type: 'line',
|
|
73
|
+
index: currentIndex,
|
|
74
|
+
lineNumber: startingLineNumber + currentIndex
|
|
75
|
+
});
|
|
76
|
+
currentIndex++;
|
|
77
|
+
}
|
|
78
|
+
return items;
|
|
79
|
+
}
|
|
80
|
+
export { buildDisplayItems, getFoldSummaryLabel, validateFolds };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FoldRegion } from './foldUtils';
|
|
2
|
+
export declare const HTTP_FOLD_ID: {
|
|
3
|
+
readonly headers: "http-headers";
|
|
4
|
+
readonly body: "http-body";
|
|
5
|
+
};
|
|
6
|
+
export type HttpFoldSectionOptions = {
|
|
7
|
+
label?: string;
|
|
8
|
+
defaultCollapsed?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type HttpFoldOptions = {
|
|
11
|
+
headers?: HttpFoldSectionOptions;
|
|
12
|
+
body?: HttpFoldSectionOptions;
|
|
13
|
+
/** Must match the startingLineNumber passed to CodeSnippetRoot. Default: 1 */
|
|
14
|
+
startingLineNumber?: number;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Detects header and body sections in an HTTP request/response and returns fold regions.
|
|
18
|
+
*
|
|
19
|
+
* Expected structure:
|
|
20
|
+
* - Line 1: Request line (e.g. `GET /path HTTP/1.1`) or status line (e.g. `HTTP/1.1 200 OK`)
|
|
21
|
+
* - Lines 2–N: Headers (key: value)
|
|
22
|
+
* - Empty line: separator
|
|
23
|
+
* - Remaining lines: Body (optional)
|
|
24
|
+
*
|
|
25
|
+
* Returns up to two fold regions (headers, body). Sections that don't exist are omitted.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getHttpFolds(code: string, options?: HttpFoldOptions): FoldRegion[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const HTTP_FOLD_ID = {
|
|
2
|
+
headers: 'http-headers',
|
|
3
|
+
body: 'http-body'
|
|
4
|
+
};
|
|
5
|
+
function getHttpFolds(code, options) {
|
|
6
|
+
const lines = code.split('\n');
|
|
7
|
+
const folds = [];
|
|
8
|
+
const offset = (options?.startingLineNumber ?? 1) - 1;
|
|
9
|
+
let separatorIndex = -1;
|
|
10
|
+
for(let i = 1; i < lines.length; i++)if (lines[i]?.trim() === '') {
|
|
11
|
+
separatorIndex = i;
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
const headersStart = 2 + offset;
|
|
15
|
+
const headersEnd = (-1 === separatorIndex ? lines.length : separatorIndex) + offset;
|
|
16
|
+
if (headersEnd >= headersStart) folds.push({
|
|
17
|
+
id: HTTP_FOLD_ID.headers,
|
|
18
|
+
startLine: headersStart,
|
|
19
|
+
endLine: headersEnd,
|
|
20
|
+
label: options?.headers?.label ?? 'Headers',
|
|
21
|
+
defaultCollapsed: options?.headers?.defaultCollapsed
|
|
22
|
+
});
|
|
23
|
+
if (-1 !== separatorIndex) {
|
|
24
|
+
const bodyStart = separatorIndex + 2 + offset;
|
|
25
|
+
const bodyEnd = lines.length + offset;
|
|
26
|
+
if (bodyEnd >= bodyStart) folds.push({
|
|
27
|
+
id: HTTP_FOLD_ID.body,
|
|
28
|
+
startLine: bodyStart,
|
|
29
|
+
endLine: bodyEnd,
|
|
30
|
+
label: options?.body?.label ?? 'Body',
|
|
31
|
+
defaultCollapsed: options?.body?.defaultCollapsed
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return folds;
|
|
35
|
+
}
|
|
36
|
+
export { HTTP_FOLD_ID, getHttpFolds };
|
|
@@ -22,7 +22,10 @@ function useCopyTooltip({ text, enabled = true }) {
|
|
|
22
22
|
setCopied(true);
|
|
23
23
|
setKeepOpen(true);
|
|
24
24
|
clearTimer();
|
|
25
|
-
timerRef.current = setTimeout(()=>
|
|
25
|
+
timerRef.current = setTimeout(()=>{
|
|
26
|
+
setKeepOpen(false);
|
|
27
|
+
setCopied(false);
|
|
28
|
+
}, 2000);
|
|
26
29
|
}, [
|
|
27
30
|
enabled,
|
|
28
31
|
text,
|
|
@@ -33,6 +36,7 @@ function useCopyTooltip({ text, enabled = true }) {
|
|
|
33
36
|
let listenerAdded = false;
|
|
34
37
|
const dismiss = ()=>{
|
|
35
38
|
setKeepOpen(false);
|
|
39
|
+
setCopied(false);
|
|
36
40
|
clearTimer();
|
|
37
41
|
};
|
|
38
42
|
const frame = requestAnimationFrame(()=>{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"generatedAt": "2026-04-10T07:
|
|
2
|
+
"version": "0.22.0",
|
|
3
|
+
"generatedAt": "2026-04-10T07:46:52.991Z",
|
|
4
4
|
"components": [
|
|
5
5
|
{
|
|
6
6
|
"name": "Alert",
|
|
@@ -10031,7 +10031,7 @@
|
|
|
10031
10031
|
"type": "Record<number, LineConfig> | undefined",
|
|
10032
10032
|
"required": false,
|
|
10033
10033
|
"description": "Per-line configuration (color, prefix) keyed by line number",
|
|
10034
|
-
"defaultValue": "
|
|
10034
|
+
"defaultValue": "EMPTY_LINES"
|
|
10035
10035
|
},
|
|
10036
10036
|
{
|
|
10037
10037
|
"name": "startingLineNumber",
|
|
@@ -10052,6 +10052,12 @@
|
|
|
10052
10052
|
"required": false,
|
|
10053
10053
|
"description": "Max lines before collapsing (for ShowMore)",
|
|
10054
10054
|
"defaultValue": "0"
|
|
10055
|
+
},
|
|
10056
|
+
{
|
|
10057
|
+
"name": "folds",
|
|
10058
|
+
"type": "FoldRegion[] | undefined",
|
|
10059
|
+
"required": false,
|
|
10060
|
+
"description": "Foldable regions that can be collapsed/expanded"
|
|
10055
10061
|
}
|
|
10056
10062
|
]
|
|
10057
10063
|
},
|
|
@@ -11128,6 +11134,26 @@
|
|
|
11128
11134
|
"name": "ShowMore",
|
|
11129
11135
|
"code": "() => (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '24px', width: '500px' }}>\n <div>\n <p style={{ margin: '0 0 8px', fontSize: '14px', color: '#64748b' }}>\n 12 lines, maxLines=7 — 5 hidden, button shown\n </p>\n <CodeSnippetRoot code={showMoreCode} language='text' maxLines={7}>\n <CodeSnippetContent>\n <CodeSnippetCode />\n </CodeSnippetContent>\n </CodeSnippetRoot>\n </div>\n <div>\n <p style={{ margin: '0 0 8px', fontSize: '14px', color: '#64748b' }}>\n 9 lines, maxLines=7 — 2 hidden, below threshold, no button\n </p>\n <CodeSnippetRoot code={showMoreThresholdCode} language='text' maxLines={7}>\n <CodeSnippetContent>\n <CodeSnippetCode />\n </CodeSnippetContent>\n </CodeSnippetRoot>\n </div>\n </div>\n)",
|
|
11130
11136
|
"description": "Show more / show less with threshold behavior.\nFirst snippet (12 lines, maxLines=7) shows the button (5 hidden).\nSecond snippet (9 lines, maxLines=7) hides only 2 lines — below\nthe threshold of 3, so all lines are shown without the button."
|
|
11137
|
+
},
|
|
11138
|
+
{
|
|
11139
|
+
"name": "HTTPRequestWithFolding",
|
|
11140
|
+
"code": "() => (\n <CodeSnippetRoot\n code={foldableRequestCode}\n language='text'\n folds={getHttpFolds(foldableRequestCode)}\n >\n <CodeSnippetContent>\n <CodeSnippetLineNumbers />\n <CodeSnippetCode />\n </CodeSnippetContent>\n </CodeSnippetRoot>\n)",
|
|
11141
|
+
"description": "HTTP Request with foldable headers and body using `getHttpFolds`."
|
|
11142
|
+
},
|
|
11143
|
+
{
|
|
11144
|
+
"name": "HTTPResponseWithFolding",
|
|
11145
|
+
"code": "() => (\n <CodeSnippetRoot\n code={foldableResponseCode}\n language='text'\n folds={getHttpFolds(foldableResponseCode)}\n >\n <CodeSnippetContent>\n <CodeSnippetLineNumbers />\n <CodeSnippetCode />\n </CodeSnippetContent>\n </CodeSnippetRoot>\n)",
|
|
11146
|
+
"description": "HTTP Response with foldable headers and body using `getHttpFolds`."
|
|
11147
|
+
},
|
|
11148
|
+
{
|
|
11149
|
+
"name": "WithFoldingExpanded",
|
|
11150
|
+
"code": "() => (\n <CodeSnippetRoot\n code={foldableRequestCode}\n language='text'\n folds={getHttpFolds(foldableRequestCode, {\n headers: { defaultCollapsed: false },\n body: { defaultCollapsed: false },\n })}\n lines={{\n 3: { color: 'warning' },\n 4: { color: 'danger', ranges: [{ start: 14, end: 42, color: 'danger' }] },\n 10: { color: 'info' },\n 11: { color: 'info' },\n }}\n >\n <CodeSnippetContent>\n <CodeSnippetLineNumbers />\n <CodeSnippetCode />\n </CodeSnippetContent>\n </CodeSnippetRoot>\n)",
|
|
11151
|
+
"description": "Folds combined with line highlights — colors and decorations\non folded lines are hidden when collapsed, visible when expanded."
|
|
11152
|
+
},
|
|
11153
|
+
{
|
|
11154
|
+
"name": "WithFoldingAndShowMore",
|
|
11155
|
+
"code": "() => (\n <CodeSnippetAdapterProvider adapter={loadShikiAdapter}>\n <CodeSnippetRoot\n code={foldableResponseCode}\n language='http'\n startingLineNumber={100}\n maxLines={6}\n folds={getHttpFolds(foldableResponseCode, { startingLineNumber: 100 })}\n >\n <CodeSnippetContent>\n <CodeSnippetLineNumbers />\n <CodeSnippetCode />\n </CodeSnippetContent>\n </CodeSnippetRoot>\n </CodeSnippetAdapterProvider>\n)",
|
|
11156
|
+
"description": "Folds combined with maxLines (ShowMore) and a custom startingLineNumber.\nFold line numbers are absolute — `getHttpFolds` accepts `startingLineNumber`\nto offset them automatically."
|
|
11131
11157
|
}
|
|
11132
11158
|
]
|
|
11133
11159
|
},
|
|
@@ -28483,7 +28509,7 @@
|
|
|
28483
28509
|
},
|
|
28484
28510
|
{
|
|
28485
28511
|
"name": "--color-syntax-keyword",
|
|
28486
|
-
"value": "var(--color-
|
|
28512
|
+
"value": "var(--color-purple-600)"
|
|
28487
28513
|
},
|
|
28488
28514
|
{
|
|
28489
28515
|
"name": "--color-syntax-function",
|
|
@@ -28491,35 +28517,35 @@
|
|
|
28491
28517
|
},
|
|
28492
28518
|
{
|
|
28493
28519
|
"name": "--color-syntax-string",
|
|
28494
|
-
"value": "var(--color-
|
|
28520
|
+
"value": "var(--color-green-600)"
|
|
28495
28521
|
},
|
|
28496
28522
|
{
|
|
28497
28523
|
"name": "--color-syntax-number-boolean",
|
|
28498
|
-
"value": "var(--color-
|
|
28524
|
+
"value": "var(--color-amber-700)"
|
|
28499
28525
|
},
|
|
28500
28526
|
{
|
|
28501
28527
|
"name": "--color-syntax-tag",
|
|
28502
|
-
"value": "var(--color-
|
|
28528
|
+
"value": "var(--color-red-600)"
|
|
28503
28529
|
},
|
|
28504
28530
|
{
|
|
28505
28531
|
"name": "--color-syntax-attribute",
|
|
28506
|
-
"value": "var(--color-
|
|
28532
|
+
"value": "var(--color-amber-700)"
|
|
28507
28533
|
},
|
|
28508
28534
|
{
|
|
28509
28535
|
"name": "--color-syntax-comment",
|
|
28510
|
-
"value": "var(--color-slate-
|
|
28536
|
+
"value": "var(--color-slate-400)"
|
|
28511
28537
|
},
|
|
28512
28538
|
{
|
|
28513
28539
|
"name": "--color-syntax-type",
|
|
28514
|
-
"value": "var(--color-
|
|
28540
|
+
"value": "var(--color-yellow-700)"
|
|
28515
28541
|
},
|
|
28516
28542
|
{
|
|
28517
28543
|
"name": "--color-syntax-operator",
|
|
28518
|
-
"value": "var(--color-slate-
|
|
28544
|
+
"value": "var(--color-slate-700)"
|
|
28519
28545
|
},
|
|
28520
28546
|
{
|
|
28521
28547
|
"name": "--color-syntax-variable",
|
|
28522
|
-
"value": "var(--color-
|
|
28548
|
+
"value": "var(--color-red-600)"
|
|
28523
28549
|
},
|
|
28524
28550
|
{
|
|
28525
28551
|
"name": "--color-syntax-number",
|
|
@@ -28527,11 +28553,11 @@
|
|
|
28527
28553
|
},
|
|
28528
28554
|
{
|
|
28529
28555
|
"name": "--color-syntax-boolean",
|
|
28530
|
-
"value": "var(--color-syntax-
|
|
28556
|
+
"value": "var(--color-syntax-keyword)"
|
|
28531
28557
|
},
|
|
28532
28558
|
{
|
|
28533
28559
|
"name": "--color-syntax-constant",
|
|
28534
|
-
"value": "var(--color-syntax-
|
|
28560
|
+
"value": "var(--color-syntax-keyword)"
|
|
28535
28561
|
},
|
|
28536
28562
|
{
|
|
28537
28563
|
"name": "--color-syntax-builtin",
|
|
@@ -29541,43 +29567,43 @@
|
|
|
29541
29567
|
},
|
|
29542
29568
|
{
|
|
29543
29569
|
"name": "--color-syntax-keyword",
|
|
29544
|
-
"value": "var(--color-
|
|
29570
|
+
"value": "var(--color-purple-400)"
|
|
29545
29571
|
},
|
|
29546
29572
|
{
|
|
29547
29573
|
"name": "--color-syntax-function",
|
|
29548
|
-
"value": "var(--color-blue-
|
|
29574
|
+
"value": "var(--color-blue-400)"
|
|
29549
29575
|
},
|
|
29550
29576
|
{
|
|
29551
29577
|
"name": "--color-syntax-string",
|
|
29552
|
-
"value": "var(--color-
|
|
29578
|
+
"value": "var(--color-green-300)"
|
|
29553
29579
|
},
|
|
29554
29580
|
{
|
|
29555
29581
|
"name": "--color-syntax-number-boolean",
|
|
29556
|
-
"value": "var(--color-
|
|
29582
|
+
"value": "var(--color-amber-300)"
|
|
29557
29583
|
},
|
|
29558
29584
|
{
|
|
29559
29585
|
"name": "--color-syntax-tag",
|
|
29560
|
-
"value": "var(--color-
|
|
29586
|
+
"value": "var(--color-red-400)"
|
|
29561
29587
|
},
|
|
29562
29588
|
{
|
|
29563
29589
|
"name": "--color-syntax-attribute",
|
|
29564
|
-
"value": "var(--color-
|
|
29590
|
+
"value": "var(--color-amber-300)"
|
|
29565
29591
|
},
|
|
29566
29592
|
{
|
|
29567
29593
|
"name": "--color-syntax-comment",
|
|
29568
|
-
"value": "var(--color-slate-
|
|
29594
|
+
"value": "var(--color-slate-500)"
|
|
29569
29595
|
},
|
|
29570
29596
|
{
|
|
29571
29597
|
"name": "--color-syntax-type",
|
|
29572
|
-
"value": "var(--color-
|
|
29598
|
+
"value": "var(--color-yellow-300)"
|
|
29573
29599
|
},
|
|
29574
29600
|
{
|
|
29575
29601
|
"name": "--color-syntax-operator",
|
|
29576
|
-
"value": "var(--color-slate-
|
|
29602
|
+
"value": "var(--color-slate-300)"
|
|
29577
29603
|
},
|
|
29578
29604
|
{
|
|
29579
29605
|
"name": "--color-syntax-variable",
|
|
29580
|
-
"value": "var(--color-
|
|
29606
|
+
"value": "var(--color-red-300)"
|
|
29581
29607
|
},
|
|
29582
29608
|
{
|
|
29583
29609
|
"name": "--color-syntax-number",
|
|
@@ -29585,11 +29611,11 @@
|
|
|
29585
29611
|
},
|
|
29586
29612
|
{
|
|
29587
29613
|
"name": "--color-syntax-boolean",
|
|
29588
|
-
"value": "var(--color-syntax-
|
|
29614
|
+
"value": "var(--color-syntax-keyword)"
|
|
29589
29615
|
},
|
|
29590
29616
|
{
|
|
29591
29617
|
"name": "--color-syntax-constant",
|
|
29592
|
-
"value": "var(--color-syntax-
|
|
29618
|
+
"value": "var(--color-syntax-keyword)"
|
|
29593
29619
|
},
|
|
29594
29620
|
{
|
|
29595
29621
|
"name": "--color-syntax-builtin",
|
package/dist/theme/index.css
CHANGED
package/dist/theme/semantic.css
CHANGED
|
@@ -217,25 +217,30 @@
|
|
|
217
217
|
|
|
218
218
|
/* ========================================
|
|
219
219
|
* SYNTAX COLORS
|
|
220
|
+
* ⚠️ CODE-MANAGED — DO NOT SYNC FROM FIGMA
|
|
221
|
+
* These token-to-color mappings are intentionally decoupled from Figma.
|
|
222
|
+
* The underlying palette values (e.g. --color-slate-900's hex) may change,
|
|
223
|
+
* but the mapping here (e.g. keyword → purple-600) must not be overwritten
|
|
224
|
+
* by Figma design token imports or AI-assisted Figma syncs.
|
|
220
225
|
* ======================================== */
|
|
221
226
|
--color-syntax-no-syntax: var(--color-slate-900);
|
|
222
227
|
|
|
223
|
-
/* Syntax Highlighting — base tokens (
|
|
224
|
-
--color-syntax-keyword: var(--color-
|
|
228
|
+
/* Syntax Highlighting — base tokens (One Light / GitHub Light family) */
|
|
229
|
+
--color-syntax-keyword: var(--color-purple-600);
|
|
225
230
|
--color-syntax-function: var(--color-blue-600);
|
|
226
|
-
--color-syntax-string: var(--color-
|
|
227
|
-
--color-syntax-number-boolean: var(--color-
|
|
228
|
-
--color-syntax-tag: var(--color-
|
|
229
|
-
--color-syntax-attribute: var(--color-
|
|
230
|
-
--color-syntax-comment: var(--color-slate-
|
|
231
|
-
--color-syntax-type: var(--color-
|
|
232
|
-
--color-syntax-operator: var(--color-slate-
|
|
233
|
-
--color-syntax-variable: var(--color-
|
|
231
|
+
--color-syntax-string: var(--color-green-600);
|
|
232
|
+
--color-syntax-number-boolean: var(--color-amber-700);
|
|
233
|
+
--color-syntax-tag: var(--color-red-600);
|
|
234
|
+
--color-syntax-attribute: var(--color-amber-700);
|
|
235
|
+
--color-syntax-comment: var(--color-slate-400);
|
|
236
|
+
--color-syntax-type: var(--color-yellow-700);
|
|
237
|
+
--color-syntax-operator: var(--color-slate-700);
|
|
238
|
+
--color-syntax-variable: var(--color-red-600);
|
|
234
239
|
|
|
235
240
|
/* Syntax Highlighting — aliases for CodeToken mapping */
|
|
236
241
|
--color-syntax-number: var(--color-syntax-number-boolean);
|
|
237
|
-
--color-syntax-boolean: var(--color-syntax-
|
|
238
|
-
--color-syntax-constant: var(--color-syntax-
|
|
242
|
+
--color-syntax-boolean: var(--color-syntax-keyword);
|
|
243
|
+
--color-syntax-constant: var(--color-syntax-keyword);
|
|
239
244
|
--color-syntax-builtin: var(--color-syntax-keyword);
|
|
240
245
|
--color-syntax-class-name: var(--color-syntax-type);
|
|
241
246
|
--color-syntax-property: var(--color-syntax-variable);
|
|
@@ -607,25 +612,27 @@
|
|
|
607
612
|
|
|
608
613
|
/* ========================================
|
|
609
614
|
* SYNTAX COLORS
|
|
615
|
+
* ⚠️ CODE-MANAGED — DO NOT SYNC FROM FIGMA
|
|
616
|
+
* See light-mode comment above for details.
|
|
610
617
|
* ======================================== */
|
|
611
618
|
--color-syntax-no-syntax: var(--color-slate-200);
|
|
612
619
|
|
|
613
|
-
/* Syntax Highlighting — base tokens (
|
|
614
|
-
--color-syntax-keyword: var(--color-
|
|
615
|
-
--color-syntax-function: var(--color-blue-
|
|
616
|
-
--color-syntax-string: var(--color-
|
|
617
|
-
--color-syntax-number-boolean: var(--color-
|
|
618
|
-
--color-syntax-tag: var(--color-
|
|
619
|
-
--color-syntax-attribute: var(--color-
|
|
620
|
-
--color-syntax-comment: var(--color-slate-
|
|
621
|
-
--color-syntax-type: var(--color-
|
|
622
|
-
--color-syntax-operator: var(--color-slate-
|
|
623
|
-
--color-syntax-variable: var(--color-
|
|
620
|
+
/* Syntax Highlighting — base tokens (One Dark Pro / Dark+ family) */
|
|
621
|
+
--color-syntax-keyword: var(--color-purple-400);
|
|
622
|
+
--color-syntax-function: var(--color-blue-400);
|
|
623
|
+
--color-syntax-string: var(--color-green-300);
|
|
624
|
+
--color-syntax-number-boolean: var(--color-amber-300);
|
|
625
|
+
--color-syntax-tag: var(--color-red-400);
|
|
626
|
+
--color-syntax-attribute: var(--color-amber-300);
|
|
627
|
+
--color-syntax-comment: var(--color-slate-500);
|
|
628
|
+
--color-syntax-type: var(--color-yellow-300);
|
|
629
|
+
--color-syntax-operator: var(--color-slate-300);
|
|
630
|
+
--color-syntax-variable: var(--color-red-300);
|
|
624
631
|
|
|
625
632
|
/* Syntax Highlighting — aliases for CodeToken mapping */
|
|
626
633
|
--color-syntax-number: var(--color-syntax-number-boolean);
|
|
627
|
-
--color-syntax-boolean: var(--color-syntax-
|
|
628
|
-
--color-syntax-constant: var(--color-syntax-
|
|
634
|
+
--color-syntax-boolean: var(--color-syntax-keyword);
|
|
635
|
+
--color-syntax-constant: var(--color-syntax-keyword);
|
|
629
636
|
--color-syntax-builtin: var(--color-syntax-keyword);
|
|
630
637
|
--color-syntax-class-name: var(--color-syntax-type);
|
|
631
638
|
--color-syntax-property: var(--color-syntax-variable);
|