@dxos/react-ui-syntax-highlighter 0.8.4-main.9be5663bfe → 0.8.4-main.abd8ff62ef
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/lib/browser/index.mjs +133 -84
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +133 -84
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/JsonHighlighter/JsonHighlighter.d.ts +23 -0
- package/dist/types/src/JsonHighlighter/JsonHighlighter.d.ts.map +1 -0
- package/dist/types/src/JsonHighlighter/JsonHighlighter.stories.d.ts +14 -0
- package/dist/types/src/JsonHighlighter/JsonHighlighter.stories.d.ts.map +1 -0
- package/dist/types/src/JsonHighlighter/index.d.ts +2 -0
- package/dist/types/src/JsonHighlighter/index.d.ts.map +1 -0
- package/dist/types/src/Syntax/Syntax.d.ts +49 -0
- package/dist/types/src/Syntax/Syntax.d.ts.map +1 -0
- package/dist/types/src/Syntax/Syntax.stories.d.ts +23 -0
- package/dist/types/src/Syntax/Syntax.stories.d.ts.map +1 -0
- package/dist/types/src/Syntax/index.d.ts +2 -0
- package/dist/types/src/Syntax/index.d.ts.map +1 -0
- package/dist/types/src/SyntaxHighlighter/SyntaxHighlighter.d.ts +9 -2
- package/dist/types/src/SyntaxHighlighter/SyntaxHighlighter.d.ts.map +1 -1
- package/dist/types/src/SyntaxHighlighter/SyntaxHighlighter.stories.d.ts +1 -1
- package/dist/types/src/SyntaxHighlighter/SyntaxHighlighter.stories.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -14
- package/src/JsonHighlighter/JsonHighlighter.stories.tsx +65 -0
- package/src/JsonHighlighter/JsonHighlighter.tsx +47 -0
- package/src/JsonHighlighter/index.ts +5 -0
- package/src/{Json/Json.stories.tsx → Syntax/Syntax.stories.tsx} +37 -44
- package/src/Syntax/Syntax.tsx +229 -0
- package/src/Syntax/index.ts +5 -0
- package/src/SyntaxHighlighter/SyntaxHighlighter.stories.tsx +2 -2
- package/src/SyntaxHighlighter/SyntaxHighlighter.tsx +77 -25
- package/src/index.ts +2 -1
- package/dist/types/src/Json/Json.d.ts +0 -37
- package/dist/types/src/Json/Json.d.ts.map +0 -1
- package/dist/types/src/Json/Json.stories.d.ts +0 -21
- package/dist/types/src/Json/Json.stories.d.ts.map +0 -1
- package/dist/types/src/Json/index.d.ts +0 -2
- package/dist/types/src/Json/index.d.ts.map +0 -1
- package/src/Json/Json.tsx +0 -190
- package/src/Json/index.ts +0 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-syntax-highlighter",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.abd8ff62ef",
|
|
4
4
|
"description": "A syntax highlighter wrapper.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -21,18 +21,16 @@
|
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"types": "dist/types/src/index.d.ts",
|
|
24
|
-
"typesVersions": {
|
|
25
|
-
"*": {}
|
|
26
|
-
},
|
|
27
24
|
"files": [
|
|
28
25
|
"dist",
|
|
29
26
|
"src"
|
|
30
27
|
],
|
|
31
28
|
"dependencies": {
|
|
29
|
+
"@radix-ui/react-context": "1.1.1",
|
|
32
30
|
"jsonpath-plus": "^10.3.0",
|
|
33
31
|
"react-syntax-highlighter": "^15.6.1",
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/
|
|
32
|
+
"@dxos/util": "0.8.4-main.abd8ff62ef",
|
|
33
|
+
"@dxos/ui-types": "0.8.4-main.abd8ff62ef"
|
|
36
34
|
},
|
|
37
35
|
"devDependencies": {
|
|
38
36
|
"@types/react": "~19.2.7",
|
|
@@ -40,18 +38,18 @@
|
|
|
40
38
|
"@types/react-syntax-highlighter": "^15.5.13",
|
|
41
39
|
"react": "~19.2.3",
|
|
42
40
|
"react-dom": "~19.2.3",
|
|
43
|
-
"vite": "^
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
41
|
+
"vite": "^8.0.10",
|
|
42
|
+
"@dxos/storybook-utils": "0.8.4-main.abd8ff62ef",
|
|
43
|
+
"@dxos/react-ui": "0.8.4-main.abd8ff62ef",
|
|
44
|
+
"@dxos/util": "0.8.4-main.abd8ff62ef",
|
|
45
|
+
"@dxos/ui-theme": "0.8.4-main.abd8ff62ef",
|
|
46
|
+
"@dxos/random": "0.8.4-main.abd8ff62ef"
|
|
49
47
|
},
|
|
50
48
|
"peerDependencies": {
|
|
51
49
|
"react": "~19.2.3",
|
|
52
50
|
"react-dom": "~19.2.3",
|
|
53
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
54
|
-
"@dxos/ui-theme": "0.8.4-main.
|
|
51
|
+
"@dxos/react-ui": "0.8.4-main.abd8ff62ef",
|
|
52
|
+
"@dxos/ui-theme": "0.8.4-main.abd8ff62ef"
|
|
55
53
|
},
|
|
56
54
|
"publishConfig": {
|
|
57
55
|
"access": "public"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
|
|
7
|
+
import { random } from '@dxos/random';
|
|
8
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
9
|
+
|
|
10
|
+
import { JsonHighlighter } from './JsonHighlighter';
|
|
11
|
+
|
|
12
|
+
random.seed(0);
|
|
13
|
+
|
|
14
|
+
const createNode = () => {
|
|
15
|
+
const data: Record<string, any> = {};
|
|
16
|
+
const keys = [...Array(random.number.int({ min: 1, max: 5 }))].map(() => random.lorem.word());
|
|
17
|
+
keys.forEach((key) => {
|
|
18
|
+
switch (random.helpers.arrayElement(['object', 'string', 'number', 'boolean', 'null'])) {
|
|
19
|
+
case 'object':
|
|
20
|
+
data[key] = createNode();
|
|
21
|
+
break;
|
|
22
|
+
case 'string':
|
|
23
|
+
data[key] = random.lorem.word();
|
|
24
|
+
break;
|
|
25
|
+
case 'number':
|
|
26
|
+
data[key] = random.number.int();
|
|
27
|
+
break;
|
|
28
|
+
case 'boolean':
|
|
29
|
+
data[key] = random.datatype.boolean();
|
|
30
|
+
break;
|
|
31
|
+
case 'null':
|
|
32
|
+
data[key] = null;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return data;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const createCycle = () => {
|
|
40
|
+
const data: any = { a: 1, b: [] };
|
|
41
|
+
data.b.push(data);
|
|
42
|
+
return data;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const meta = {
|
|
46
|
+
title: 'ui/react-ui-syntax-highlighter/JsonHighlighter',
|
|
47
|
+
component: JsonHighlighter,
|
|
48
|
+
decorators: [withTheme(), withLayout({ layout: 'column' })],
|
|
49
|
+
} satisfies Meta<typeof JsonHighlighter>;
|
|
50
|
+
|
|
51
|
+
export default meta;
|
|
52
|
+
|
|
53
|
+
type Story = StoryObj<typeof JsonHighlighter>;
|
|
54
|
+
|
|
55
|
+
export const Default: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
data: createNode(),
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const Cycle: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
data: createCycle(),
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { composable } from '@dxos/ui-theme';
|
|
8
|
+
import { type CreateReplacerProps, createReplacer, safeStringify } from '@dxos/util';
|
|
9
|
+
|
|
10
|
+
import { SyntaxHighlighter, type SyntaxHighlighterProps } from '../SyntaxHighlighter';
|
|
11
|
+
|
|
12
|
+
export type JsonReplacer = CreateReplacerProps | ((key: string, value: any) => any);
|
|
13
|
+
|
|
14
|
+
export type JsonHighlighterProps = Omit<SyntaxHighlighterProps, 'children' | 'language'> & {
|
|
15
|
+
data?: any;
|
|
16
|
+
replacer?: JsonReplacer;
|
|
17
|
+
/** Indentation passed to `JSON.stringify`. Default: `2` (pretty-printed). Pass `0` for single-line output. */
|
|
18
|
+
indent?: number;
|
|
19
|
+
testId?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const resolveReplacer = (replacer?: JsonReplacer) => {
|
|
23
|
+
if (!replacer) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
return typeof replacer === 'function' ? replacer : createReplacer(replacer);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Inline, non-scrolling JSON renderer.
|
|
31
|
+
*
|
|
32
|
+
* Thin wrapper around `SyntaxHighlighter` that stringifies `data` with an optional replacer.
|
|
33
|
+
* `replacer` accepts either `CreateReplacerProps` (declarative truncation) or a raw
|
|
34
|
+
* `JSON.stringify`-compatible function (for bespoke serialization).
|
|
35
|
+
* For filtering and scroll behaviour, compose with the `Syntax.*` namespace.
|
|
36
|
+
*/
|
|
37
|
+
export const JsonHighlighter = composable<HTMLDivElement, JsonHighlighterProps>(
|
|
38
|
+
({ data, replacer, indent = 2, testId, ...props }, forwardedRef) => {
|
|
39
|
+
return (
|
|
40
|
+
<SyntaxHighlighter {...props} language='json' data-testid={testId} ref={forwardedRef}>
|
|
41
|
+
{safeStringify(data, resolveReplacer(replacer), indent)}
|
|
42
|
+
</SyntaxHighlighter>
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
JsonHighlighter.displayName = 'JsonHighlighter';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//
|
|
2
|
-
// Copyright
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
@@ -7,8 +7,9 @@ import React from 'react';
|
|
|
7
7
|
|
|
8
8
|
import { random } from '@dxos/random';
|
|
9
9
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
10
|
+
import { trim } from '@dxos/util';
|
|
10
11
|
|
|
11
|
-
import {
|
|
12
|
+
import { Syntax } from './Syntax';
|
|
12
13
|
|
|
13
14
|
random.seed(0);
|
|
14
15
|
|
|
@@ -34,7 +35,6 @@ const createNode = () => {
|
|
|
34
35
|
break;
|
|
35
36
|
}
|
|
36
37
|
});
|
|
37
|
-
|
|
38
38
|
return data;
|
|
39
39
|
};
|
|
40
40
|
|
|
@@ -42,30 +42,16 @@ const createData = ({ depth = 2, children = 3 } = {}): any => {
|
|
|
42
42
|
const createChildren = (root: any, d = 0) => {
|
|
43
43
|
if (d < depth) {
|
|
44
44
|
const num = random.number.int({ min: 1, max: Math.round(Math.log(depth + 1 - d) * children) });
|
|
45
|
-
root.children = [...new Array(num)].map(() =>
|
|
46
|
-
return createChildren(createNode(), d + 1);
|
|
47
|
-
});
|
|
45
|
+
root.children = [...new Array(num)].map(() => createChildren(createNode(), d + 1));
|
|
48
46
|
}
|
|
49
|
-
|
|
50
47
|
return root;
|
|
51
48
|
};
|
|
52
|
-
|
|
53
49
|
return createChildren(createNode());
|
|
54
50
|
};
|
|
55
51
|
|
|
56
|
-
const createCycle = () => {
|
|
57
|
-
const data: any = {
|
|
58
|
-
a: 1,
|
|
59
|
-
b: [],
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
data.b.push(data);
|
|
63
|
-
return data;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
52
|
const meta = {
|
|
67
|
-
title: 'ui/react-ui-syntax-highlighter/
|
|
68
|
-
component:
|
|
53
|
+
title: 'ui/react-ui-syntax-highlighter/Syntax',
|
|
54
|
+
component: Syntax.Root,
|
|
69
55
|
decorators: [withTheme(), withLayout({ layout: 'column' })],
|
|
70
56
|
} satisfies Meta;
|
|
71
57
|
|
|
@@ -73,34 +59,41 @@ export default meta;
|
|
|
73
59
|
|
|
74
60
|
type Story = StoryObj<typeof meta>;
|
|
75
61
|
|
|
76
|
-
/**
|
|
77
|
-
export const
|
|
78
|
-
render: (args) =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
62
|
+
/** JSON composite with filter and scrolling viewport. */
|
|
63
|
+
export const Json: Story = {
|
|
64
|
+
render: (args) => (
|
|
65
|
+
<Syntax.Root {...args}>
|
|
66
|
+
<Syntax.Content>
|
|
67
|
+
<Syntax.Filter />
|
|
68
|
+
<Syntax.Viewport>
|
|
69
|
+
<Syntax.Code />
|
|
70
|
+
</Syntax.Viewport>
|
|
71
|
+
</Syntax.Content>
|
|
72
|
+
</Syntax.Root>
|
|
73
|
+
),
|
|
87
74
|
args: {
|
|
88
|
-
data:
|
|
89
|
-
|
|
75
|
+
data: createData({ depth: 5 }),
|
|
76
|
+
replacer: { maxDepth: 3, maxArrayLen: 10, maxStringLen: 10 },
|
|
77
|
+
} as any,
|
|
90
78
|
};
|
|
91
79
|
|
|
92
|
-
/**
|
|
93
|
-
export const
|
|
80
|
+
/** Text composite (TypeScript source) with scrolling viewport and no filter. */
|
|
81
|
+
export const Text: Story = {
|
|
94
82
|
render: (args) => (
|
|
95
|
-
<
|
|
96
|
-
<
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
</Json.Root>
|
|
83
|
+
<Syntax.Root {...args}>
|
|
84
|
+
<Syntax.Viewport>
|
|
85
|
+
<Syntax.Code />
|
|
86
|
+
</Syntax.Viewport>
|
|
87
|
+
</Syntax.Root>
|
|
101
88
|
),
|
|
102
89
|
args: {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
90
|
+
language: 'tsx',
|
|
91
|
+
source: trim`
|
|
92
|
+
import React from 'react'
|
|
93
|
+
|
|
94
|
+
const Test = () => {
|
|
95
|
+
return <div>Test</div>
|
|
96
|
+
}
|
|
97
|
+
`,
|
|
98
|
+
} as any,
|
|
106
99
|
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { createContextScope, type Scope } from '@radix-ui/react-context';
|
|
6
|
+
import { JSONPath } from 'jsonpath-plus';
|
|
7
|
+
import React, { type FC, type PropsWithChildren, forwardRef, useMemo, useState } from 'react';
|
|
8
|
+
|
|
9
|
+
import { Input, ScrollArea } from '@dxos/react-ui';
|
|
10
|
+
import { composable, composableProps } from '@dxos/ui-theme';
|
|
11
|
+
import { type ComposableProps } from '@dxos/ui-types';
|
|
12
|
+
|
|
13
|
+
import { JsonHighlighter, type JsonReplacer } from '../JsonHighlighter';
|
|
14
|
+
import { SyntaxHighlighter } from '../SyntaxHighlighter';
|
|
15
|
+
|
|
16
|
+
//
|
|
17
|
+
// Context
|
|
18
|
+
//
|
|
19
|
+
|
|
20
|
+
const SYNTAX_NAME = 'Syntax';
|
|
21
|
+
|
|
22
|
+
type SyntaxContextValue = {
|
|
23
|
+
mode: 'text' | 'json';
|
|
24
|
+
// Text mode.
|
|
25
|
+
source?: string;
|
|
26
|
+
language?: string;
|
|
27
|
+
// JSON mode.
|
|
28
|
+
data?: any;
|
|
29
|
+
filteredData?: any;
|
|
30
|
+
filterText: string;
|
|
31
|
+
setFilterText: (text: string) => void;
|
|
32
|
+
filterError: Error | null;
|
|
33
|
+
replacer?: JsonReplacer;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type ScopedProps<P> = P & { __scopeSyntax?: Scope };
|
|
37
|
+
|
|
38
|
+
const [createSyntaxContext, createSyntaxScope] = createContextScope(SYNTAX_NAME);
|
|
39
|
+
const [SyntaxProvider, useSyntaxContext] = createSyntaxContext<SyntaxContextValue>(SYNTAX_NAME);
|
|
40
|
+
|
|
41
|
+
//
|
|
42
|
+
// Root
|
|
43
|
+
//
|
|
44
|
+
|
|
45
|
+
const SYNTAX_ROOT_NAME = 'Syntax.Root';
|
|
46
|
+
|
|
47
|
+
type SyntaxRootProps = PropsWithChildren<{
|
|
48
|
+
// Text mode.
|
|
49
|
+
language?: string;
|
|
50
|
+
source?: string;
|
|
51
|
+
// JSON mode (presence of the `data` prop selects JSON mode; `undefined` is still JSON).
|
|
52
|
+
data?: any;
|
|
53
|
+
/**
|
|
54
|
+
* `JSON.stringify` replacer applied to `data`. Use the function form to follow domain
|
|
55
|
+
* references (e.g. ECHO refs) by returning a transformed value at the root call. Keeps
|
|
56
|
+
* this package free of any domain-specific knowledge.
|
|
57
|
+
*/
|
|
58
|
+
replacer?: JsonReplacer;
|
|
59
|
+
}>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Headless context provider. Selects JSON mode when the `data` prop is passed at all — even
|
|
63
|
+
* `data={undefined}` — so loading states render an empty JSON view rather than flipping to
|
|
64
|
+
* text mode (which would trip `Syntax.Filter`'s JSON-only guard). Mode is chosen by prop
|
|
65
|
+
* presence, not value.
|
|
66
|
+
*/
|
|
67
|
+
const SyntaxRoot: FC<ScopedProps<SyntaxRootProps>> = (props) => {
|
|
68
|
+
const { __scopeSyntax, children, language, source, replacer } = props;
|
|
69
|
+
const isJson = 'data' in props;
|
|
70
|
+
const data = props.data;
|
|
71
|
+
const [filterText, setFilterText] = useState('');
|
|
72
|
+
|
|
73
|
+
const { filteredData, filterError } = useMemo<{ filteredData: any; filterError: Error | null }>(() => {
|
|
74
|
+
if (!isJson || !filterText.trim().length) {
|
|
75
|
+
return { filteredData: data, filterError: null };
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
return { filteredData: JSONPath({ path: filterText, json: data }), filterError: null };
|
|
79
|
+
} catch (err) {
|
|
80
|
+
return { filteredData: data, filterError: err as Error };
|
|
81
|
+
}
|
|
82
|
+
}, [isJson, data, filterText]);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<SyntaxProvider
|
|
86
|
+
scope={__scopeSyntax}
|
|
87
|
+
mode={isJson ? 'json' : 'text'}
|
|
88
|
+
source={source}
|
|
89
|
+
language={language}
|
|
90
|
+
data={data}
|
|
91
|
+
filteredData={filteredData}
|
|
92
|
+
filterText={filterText}
|
|
93
|
+
setFilterText={setFilterText}
|
|
94
|
+
filterError={filterError}
|
|
95
|
+
replacer={replacer}
|
|
96
|
+
>
|
|
97
|
+
{children}
|
|
98
|
+
</SyntaxProvider>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
SyntaxRoot.displayName = SYNTAX_ROOT_NAME;
|
|
103
|
+
|
|
104
|
+
//
|
|
105
|
+
// Content
|
|
106
|
+
//
|
|
107
|
+
|
|
108
|
+
const SYNTAX_CONTENT_NAME = 'Syntax.Content';
|
|
109
|
+
|
|
110
|
+
type SyntaxContentProps = ComposableProps;
|
|
111
|
+
|
|
112
|
+
/** Flex-column layout container for composite parts. */
|
|
113
|
+
const SyntaxContent = composable<HTMLDivElement, SyntaxContentProps>(({ children, ...props }, forwardedRef) => {
|
|
114
|
+
return (
|
|
115
|
+
<div {...composableProps(props, { classNames: 'flex flex-col h-full min-h-0 overflow-hidden' })} ref={forwardedRef}>
|
|
116
|
+
{children}
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
SyntaxContent.displayName = SYNTAX_CONTENT_NAME;
|
|
122
|
+
|
|
123
|
+
//
|
|
124
|
+
// Filter
|
|
125
|
+
//
|
|
126
|
+
|
|
127
|
+
const SYNTAX_FILTER_NAME = 'Syntax.Filter';
|
|
128
|
+
|
|
129
|
+
type SyntaxFilterProps = ComposableProps<{
|
|
130
|
+
placeholder?: string;
|
|
131
|
+
}>;
|
|
132
|
+
|
|
133
|
+
/** JSONPath filter input. Only meaningful when `Syntax.Root` is in JSON mode. */
|
|
134
|
+
const SyntaxFilter = forwardRef<HTMLInputElement, ScopedProps<SyntaxFilterProps>>(
|
|
135
|
+
({ __scopeSyntax, classNames, placeholder = 'JSONPath (e.g., $.graph.nodes)' }, forwardedRef) => {
|
|
136
|
+
const { mode, filterText, setFilterText, filterError } = useSyntaxContext(SYNTAX_FILTER_NAME, __scopeSyntax);
|
|
137
|
+
if (mode !== 'json') {
|
|
138
|
+
throw new Error(`\`${SYNTAX_FILTER_NAME}\` requires \`Syntax.Root\` to be in JSON mode (pass \`data\`).`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<Input.Root validationValence={filterError ? 'error' : 'success'}>
|
|
143
|
+
<Input.TextInput
|
|
144
|
+
classNames={['p-1 px-2 font-mono', filterError && 'border-rose-500', classNames]}
|
|
145
|
+
variant='subdued'
|
|
146
|
+
value={filterText}
|
|
147
|
+
placeholder={placeholder}
|
|
148
|
+
onChange={(event) => setFilterText(event.target.value)}
|
|
149
|
+
ref={forwardedRef}
|
|
150
|
+
/>
|
|
151
|
+
</Input.Root>
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
SyntaxFilter.displayName = SYNTAX_FILTER_NAME;
|
|
157
|
+
|
|
158
|
+
//
|
|
159
|
+
// Viewport
|
|
160
|
+
//
|
|
161
|
+
|
|
162
|
+
const SYNTAX_VIEWPORT_NAME = 'Syntax.Viewport';
|
|
163
|
+
|
|
164
|
+
type SyntaxViewportProps = ComposableProps;
|
|
165
|
+
|
|
166
|
+
/** Optional scroll wrapper. Compose around `Syntax.Code` to make it scrollable. */
|
|
167
|
+
const SyntaxViewport = composable<HTMLDivElement, SyntaxViewportProps>(({ children, ...props }, forwardedRef) => {
|
|
168
|
+
return (
|
|
169
|
+
<ScrollArea.Root {...composableProps(props)} thin ref={forwardedRef}>
|
|
170
|
+
<ScrollArea.Viewport>{children}</ScrollArea.Viewport>
|
|
171
|
+
</ScrollArea.Root>
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
SyntaxViewport.displayName = SYNTAX_VIEWPORT_NAME;
|
|
176
|
+
|
|
177
|
+
//
|
|
178
|
+
// Code
|
|
179
|
+
//
|
|
180
|
+
|
|
181
|
+
const SYNTAX_CODE_NAME = 'Syntax.Code';
|
|
182
|
+
|
|
183
|
+
type SyntaxCodeProps = ComposableProps<{
|
|
184
|
+
testId?: string;
|
|
185
|
+
}>;
|
|
186
|
+
|
|
187
|
+
/** Highlighted code leaf. Reads source/data from `Syntax.Root` context. */
|
|
188
|
+
const SyntaxCode = composable<HTMLDivElement, ScopedProps<SyntaxCodeProps>>(
|
|
189
|
+
({ __scopeSyntax, testId, ...props }, forwardedRef) => {
|
|
190
|
+
const context = useSyntaxContext(SYNTAX_CODE_NAME, __scopeSyntax);
|
|
191
|
+
const merged = composableProps(props, { classNames: 'py-1 px-2 text-sm' });
|
|
192
|
+
|
|
193
|
+
if (context.mode === 'json') {
|
|
194
|
+
return (
|
|
195
|
+
<JsonHighlighter
|
|
196
|
+
{...merged}
|
|
197
|
+
data={context.filteredData}
|
|
198
|
+
replacer={context.replacer}
|
|
199
|
+
testId={testId}
|
|
200
|
+
ref={forwardedRef}
|
|
201
|
+
/>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<SyntaxHighlighter {...merged} language={context.language} data-testid={testId} ref={forwardedRef}>
|
|
207
|
+
{context.source ?? ''}
|
|
208
|
+
</SyntaxHighlighter>
|
|
209
|
+
);
|
|
210
|
+
},
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
SyntaxCode.displayName = SYNTAX_CODE_NAME;
|
|
214
|
+
|
|
215
|
+
//
|
|
216
|
+
// Syntax
|
|
217
|
+
//
|
|
218
|
+
|
|
219
|
+
export const Syntax = {
|
|
220
|
+
Root: SyntaxRoot,
|
|
221
|
+
Content: SyntaxContent,
|
|
222
|
+
Filter: SyntaxFilter,
|
|
223
|
+
Viewport: SyntaxViewport,
|
|
224
|
+
Code: SyntaxCode,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export { createSyntaxScope };
|
|
228
|
+
|
|
229
|
+
export type { SyntaxRootProps, SyntaxContentProps, SyntaxFilterProps, SyntaxViewportProps, SyntaxCodeProps };
|
|
@@ -14,7 +14,7 @@ import { SyntaxHighlighter } from './SyntaxHighlighter';
|
|
|
14
14
|
const meta = {
|
|
15
15
|
title: 'ui/react-ui-syntax-highlighter/SyntaxHighlighter',
|
|
16
16
|
component: SyntaxHighlighter,
|
|
17
|
-
decorators: [withTheme(), withLayout({ layout: 'column' })],
|
|
17
|
+
decorators: [withTheme(), withLayout({ layout: 'column', scroll: true })],
|
|
18
18
|
parameters: {
|
|
19
19
|
layout: 'fullscreen',
|
|
20
20
|
},
|
|
@@ -27,8 +27,8 @@ type Story = StoryObj<typeof SyntaxHighlighter>;
|
|
|
27
27
|
export const Default: Story = {
|
|
28
28
|
args: {
|
|
29
29
|
language: 'json',
|
|
30
|
-
classNames: 'text-sm',
|
|
31
30
|
children: TEXT,
|
|
31
|
+
copyButton: true,
|
|
32
32
|
},
|
|
33
33
|
};
|
|
34
34
|
|