@scality/core-ui 0.200.0 → 0.201.0
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/buttonv2/CopyButton.component.d.ts +1 -1
- package/dist/components/buttonv2/CopyButton.component.d.ts.map +1 -1
- package/dist/components/buttonv2/CopyButton.component.js +25 -15
- package/dist/components/editor/Editor.component.d.ts +17 -0
- package/dist/components/editor/Editor.component.d.ts.map +1 -0
- package/dist/components/editor/Editor.component.js +118 -0
- package/dist/components/editor/editorTheme.d.ts +5 -0
- package/dist/components/editor/editorTheme.d.ts.map +1 -0
- package/dist/components/editor/editorTheme.js +115 -0
- package/dist/components/editor/index.d.ts +3 -0
- package/dist/components/editor/index.d.ts.map +1 -0
- package/dist/components/editor/index.js +1 -0
- package/dist/components/iconhelper/IconHelper.d.ts.map +1 -1
- package/dist/components/iconhelper/IconHelper.js +0 -1
- package/dist/next.d.ts +2 -0
- package/dist/next.d.ts.map +1 -1
- package/dist/next.js +1 -0
- package/package.json +7 -1
- package/src/lib/components/buttonv2/CopyButton.component.test.tsx +252 -0
- package/src/lib/components/buttonv2/CopyButton.component.tsx +28 -21
- package/src/lib/components/editor/Editor.component.tsx +163 -0
- package/src/lib/components/editor/Editor.test.tsx +295 -0
- package/src/lib/components/editor/editorTheme.ts +126 -0
- package/src/lib/components/editor/index.ts +2 -0
- package/src/lib/components/iconhelper/IconHelper.tsx +0 -1
- package/src/lib/next.ts +2 -0
- package/stories/editor.stories.tsx +132 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import { ThemeProvider } from 'styled-components';
|
|
3
|
+
import { EditorView } from '@codemirror/view';
|
|
4
|
+
import { EditorState } from '@codemirror/state';
|
|
5
|
+
import { coreUIAvailableThemes } from '../../style/theme';
|
|
6
|
+
import { createEditorTheme, isDarkBackground } from './editorTheme';
|
|
7
|
+
import {
|
|
8
|
+
Editor,
|
|
9
|
+
isEditAttempt,
|
|
10
|
+
createReadOnlyTooltipExtension,
|
|
11
|
+
} from './Editor.component';
|
|
12
|
+
import React from "react";
|
|
13
|
+
|
|
14
|
+
const mockJsonSchema = jest.fn(() => []);
|
|
15
|
+
|
|
16
|
+
jest.mock('@uiw/react-codemirror', () => {
|
|
17
|
+
const MockCodeMirror = (props: {
|
|
18
|
+
value?: string;
|
|
19
|
+
readOnly?: boolean;
|
|
20
|
+
height?: string;
|
|
21
|
+
width?: string;
|
|
22
|
+
onChange?: (value: string) => void;
|
|
23
|
+
}) => (
|
|
24
|
+
<textarea
|
|
25
|
+
data-testid="codemirror-editor"
|
|
26
|
+
value={props.value}
|
|
27
|
+
readOnly={props.readOnly}
|
|
28
|
+
style={{ height: props.height, width: props.width }}
|
|
29
|
+
onChange={(e) => props.onChange?.(e.target.value)}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
MockCodeMirror.displayName = 'MockCodeMirror';
|
|
33
|
+
return { __esModule: true, default: MockCodeMirror };
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
jest.mock('codemirror-json-schema', () => ({
|
|
37
|
+
jsonSchema: (...args: unknown[]) => (mockJsonSchema as (...a: unknown[]) => unknown).apply(null, args),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
const darkTheme = coreUIAvailableThemes.darkRebrand;
|
|
41
|
+
const lightTheme = coreUIAvailableThemes.artescaLight;
|
|
42
|
+
|
|
43
|
+
const renderWithTheme = (ui: React.ReactElement, theme = darkTheme) =>
|
|
44
|
+
render(<ThemeProvider theme={theme}>{ui}</ThemeProvider>);
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
mockJsonSchema.mockClear();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('isDarkBackground', () => {
|
|
51
|
+
it('returns true for dark themes', () => {
|
|
52
|
+
expect(isDarkBackground(darkTheme)).toBe(true);
|
|
53
|
+
expect(isDarkBackground(coreUIAvailableThemes.ring9dark)).toBe(true);
|
|
54
|
+
expect(isDarkBackground(coreUIAvailableThemes['G-Dark'])).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('returns false for light themes', () => {
|
|
58
|
+
expect(isDarkBackground(lightTheme)).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('Editor', () => {
|
|
63
|
+
it('renders with dark theme', () => {
|
|
64
|
+
renderWithTheme(<Editor value='{"key": "value"}' />, darkTheme);
|
|
65
|
+
expect(screen.getByTestId('codemirror-editor')).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('renders with light theme', () => {
|
|
69
|
+
renderWithTheme(<Editor value='{"key": "value"}' />, lightTheme);
|
|
70
|
+
expect(screen.getByTestId('codemirror-editor')).toBeInTheDocument();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('renders in read-only mode', () => {
|
|
74
|
+
renderWithTheme(<Editor value='{"key": "value"}' readOnly />);
|
|
75
|
+
expect(screen.getByTestId('codemirror-editor')).toHaveAttribute('readonly');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('displays the provided value', () => {
|
|
79
|
+
const json = '{"hello": "world"}';
|
|
80
|
+
renderWithTheme(<Editor value={json} />);
|
|
81
|
+
expect(screen.getByTestId('codemirror-editor')).toHaveValue(json);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('renders with custom dimensions', () => {
|
|
85
|
+
renderWithTheme(<Editor value="" height="200px" width="500px" />);
|
|
86
|
+
const editor = screen.getByTestId('codemirror-editor');
|
|
87
|
+
expect(editor).toHaveStyle({ height: '200px', width: '500px' });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('accepts language string shorthand', () => {
|
|
91
|
+
renderWithTheme(<Editor value='{"key": "value"}' language="json" />);
|
|
92
|
+
expect(screen.getByTestId('codemirror-editor')).toBeInTheDocument();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('calls jsonSchema extension when schema is provided', () => {
|
|
96
|
+
const schema = {
|
|
97
|
+
type: 'object' as const,
|
|
98
|
+
properties: { name: { type: 'string' as const } },
|
|
99
|
+
};
|
|
100
|
+
renderWithTheme(
|
|
101
|
+
<Editor value='{"name": "test"}' language={{ name: 'json', schema }} />,
|
|
102
|
+
);
|
|
103
|
+
expect(mockJsonSchema).toHaveBeenCalledWith(schema);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('does not call jsonSchema extension without schema', () => {
|
|
107
|
+
renderWithTheme(<Editor value='{}' language="json" />);
|
|
108
|
+
expect(mockJsonSchema).not.toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('triggers onChange callback', () => {
|
|
112
|
+
const handleChange = jest.fn();
|
|
113
|
+
renderWithTheme(<Editor value='{"a": 1}' onChange={handleChange} />);
|
|
114
|
+
fireEvent.change(screen.getByTestId('codemirror-editor'), {
|
|
115
|
+
target: { value: '{"a": 2}' },
|
|
116
|
+
});
|
|
117
|
+
expect(handleChange).toHaveBeenCalledWith('{"a": 2}');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('isEditAttempt', () => {
|
|
122
|
+
const makeEvent = (overrides: Partial<KeyboardEvent> = {}): KeyboardEvent =>
|
|
123
|
+
({
|
|
124
|
+
ctrlKey: false,
|
|
125
|
+
metaKey: false,
|
|
126
|
+
altKey: false,
|
|
127
|
+
...overrides,
|
|
128
|
+
}) as unknown as KeyboardEvent;
|
|
129
|
+
|
|
130
|
+
it('detects regular character typing', () => {
|
|
131
|
+
expect(isEditAttempt(makeEvent({ key: 'a' }))).toBe(true);
|
|
132
|
+
expect(isEditAttempt(makeEvent({ key: '1' }))).toBe(true);
|
|
133
|
+
expect(isEditAttempt(makeEvent({ key: ' ' }))).toBe(true);
|
|
134
|
+
expect(isEditAttempt(makeEvent({ key: '{' }))).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('detects edit keys (Backspace, Delete, Enter, Tab)', () => {
|
|
138
|
+
expect(isEditAttempt(makeEvent({ key: 'Backspace' }))).toBe(true);
|
|
139
|
+
expect(isEditAttempt(makeEvent({ key: 'Delete' }))).toBe(true);
|
|
140
|
+
expect(isEditAttempt(makeEvent({ key: 'Enter' }))).toBe(true);
|
|
141
|
+
expect(isEditAttempt(makeEvent({ key: 'Tab' }))).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('detects cut and paste shortcuts', () => {
|
|
145
|
+
expect(isEditAttempt(makeEvent({ key: 'x', ctrlKey: true }))).toBe(true);
|
|
146
|
+
expect(isEditAttempt(makeEvent({ key: 'v', ctrlKey: true }))).toBe(true);
|
|
147
|
+
expect(isEditAttempt(makeEvent({ key: 'x', metaKey: true }))).toBe(true);
|
|
148
|
+
expect(isEditAttempt(makeEvent({ key: 'v', metaKey: true }))).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('ignores copy, select-all, and undo shortcuts', () => {
|
|
152
|
+
expect(isEditAttempt(makeEvent({ key: 'c', ctrlKey: true }))).toBe(false);
|
|
153
|
+
expect(isEditAttempt(makeEvent({ key: 'a', ctrlKey: true }))).toBe(false);
|
|
154
|
+
expect(isEditAttempt(makeEvent({ key: 'z', ctrlKey: true }))).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('ignores navigation and modifier keys', () => {
|
|
158
|
+
expect(isEditAttempt(makeEvent({ key: 'ArrowLeft' }))).toBe(false);
|
|
159
|
+
expect(isEditAttempt(makeEvent({ key: 'ArrowUp' }))).toBe(false);
|
|
160
|
+
expect(isEditAttempt(makeEvent({ key: 'Escape' }))).toBe(false);
|
|
161
|
+
expect(isEditAttempt(makeEvent({ key: 'Shift' }))).toBe(false);
|
|
162
|
+
expect(isEditAttempt(makeEvent({ key: 'Control' }))).toBe(false);
|
|
163
|
+
expect(isEditAttempt(makeEvent({ key: 'F1' }))).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('createEditorTheme', () => {
|
|
168
|
+
it('returns two extensions for dark theme', () => {
|
|
169
|
+
const result = createEditorTheme(darkTheme);
|
|
170
|
+
expect(Array.isArray(result)).toBe(true);
|
|
171
|
+
expect(result).toHaveLength(2);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('returns two extensions for light theme', () => {
|
|
175
|
+
const result = createEditorTheme(lightTheme);
|
|
176
|
+
expect(Array.isArray(result)).toBe(true);
|
|
177
|
+
expect(result).toHaveLength(2);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('handles all available themes without errors', () => {
|
|
181
|
+
Object.values(coreUIAvailableThemes).forEach((theme) => {
|
|
182
|
+
expect(() => createEditorTheme(theme)).not.toThrow();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('createReadOnlyTooltipExtension', () => {
|
|
188
|
+
let parent: HTMLDivElement;
|
|
189
|
+
|
|
190
|
+
beforeEach(() => {
|
|
191
|
+
parent = document.createElement('div');
|
|
192
|
+
document.body.appendChild(parent);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
afterEach(() => {
|
|
196
|
+
parent.remove();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('shows tooltip with ARIA attributes on edit attempt', () => {
|
|
200
|
+
const view = new EditorView({
|
|
201
|
+
state: EditorState.create({
|
|
202
|
+
doc: 'hello',
|
|
203
|
+
extensions: [createReadOnlyTooltipExtension()],
|
|
204
|
+
}),
|
|
205
|
+
parent,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
jest
|
|
209
|
+
.spyOn(view, 'coordsAtPos')
|
|
210
|
+
.mockReturnValue({ top: 10, bottom: 20, left: 30, right: 40 });
|
|
211
|
+
|
|
212
|
+
view.dom.dispatchEvent(
|
|
213
|
+
new KeyboardEvent('keydown', { key: 'a', bubbles: true }),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const tooltip = parent.querySelector('.cm-readonly-tooltip');
|
|
217
|
+
expect(tooltip).not.toBeNull();
|
|
218
|
+
expect(tooltip?.getAttribute('role')).toBe('status');
|
|
219
|
+
expect(tooltip?.getAttribute('aria-live')).toBe('polite');
|
|
220
|
+
expect(tooltip?.textContent).toBe('Cannot edit in read-only editor');
|
|
221
|
+
|
|
222
|
+
view.destroy();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('dismisses tooltip after 2 seconds', () => {
|
|
226
|
+
jest.useFakeTimers();
|
|
227
|
+
|
|
228
|
+
const view = new EditorView({
|
|
229
|
+
state: EditorState.create({
|
|
230
|
+
doc: 'hello',
|
|
231
|
+
extensions: [createReadOnlyTooltipExtension()],
|
|
232
|
+
}),
|
|
233
|
+
parent,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
jest
|
|
237
|
+
.spyOn(view, 'coordsAtPos')
|
|
238
|
+
.mockReturnValue({ top: 10, bottom: 20, left: 30, right: 40 });
|
|
239
|
+
|
|
240
|
+
view.dom.dispatchEvent(
|
|
241
|
+
new KeyboardEvent('keydown', { key: 'a', bubbles: true }),
|
|
242
|
+
);
|
|
243
|
+
expect(parent.querySelector('.cm-readonly-tooltip')).not.toBeNull();
|
|
244
|
+
|
|
245
|
+
jest.advanceTimersByTime(2000);
|
|
246
|
+
expect(parent.querySelector('.cm-readonly-tooltip')).toBeNull();
|
|
247
|
+
|
|
248
|
+
view.destroy();
|
|
249
|
+
jest.useRealTimers();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('cleans up DOM elements on destroy', () => {
|
|
253
|
+
const view = new EditorView({
|
|
254
|
+
state: EditorState.create({
|
|
255
|
+
doc: 'hello',
|
|
256
|
+
extensions: [createReadOnlyTooltipExtension()],
|
|
257
|
+
}),
|
|
258
|
+
parent,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
jest
|
|
262
|
+
.spyOn(view, 'coordsAtPos')
|
|
263
|
+
.mockReturnValue({ top: 10, bottom: 20, left: 30, right: 40 });
|
|
264
|
+
|
|
265
|
+
view.dom.dispatchEvent(
|
|
266
|
+
new KeyboardEvent('keydown', { key: 'a', bubbles: true }),
|
|
267
|
+
);
|
|
268
|
+
expect(parent.querySelector('.cm-readonly-tooltip')).not.toBeNull();
|
|
269
|
+
|
|
270
|
+
view.destroy();
|
|
271
|
+
expect(parent.querySelector('.cm-readonly-tooltip')).toBeNull();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('does not show tooltip for navigation keys', () => {
|
|
275
|
+
const view = new EditorView({
|
|
276
|
+
state: EditorState.create({
|
|
277
|
+
doc: 'hello',
|
|
278
|
+
extensions: [createReadOnlyTooltipExtension()],
|
|
279
|
+
}),
|
|
280
|
+
parent,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
jest
|
|
284
|
+
.spyOn(view, 'coordsAtPos')
|
|
285
|
+
.mockReturnValue({ top: 10, bottom: 20, left: 30, right: 40 });
|
|
286
|
+
|
|
287
|
+
view.dom.dispatchEvent(
|
|
288
|
+
new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true }),
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
expect(parent.querySelector('.cm-readonly-tooltip')).toBeNull();
|
|
292
|
+
|
|
293
|
+
view.destroy();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { EditorView } from '@codemirror/view';
|
|
2
|
+
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
3
|
+
import { tags } from '@lezer/highlight';
|
|
4
|
+
import { getLuminance } from 'polished';
|
|
5
|
+
import type { Extension } from '@codemirror/state';
|
|
6
|
+
import type { CoreUITheme } from '../../style/theme';
|
|
7
|
+
import { lineColor5 } from '../../style/theme';
|
|
8
|
+
|
|
9
|
+
export function isDarkBackground(theme: CoreUITheme): boolean {
|
|
10
|
+
try {
|
|
11
|
+
return getLuminance(theme.backgroundLevel1) < 0.5;
|
|
12
|
+
} catch {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getSyntaxColors(theme: CoreUITheme, isDark: boolean) {
|
|
18
|
+
if (isDark) {
|
|
19
|
+
return {
|
|
20
|
+
property: theme.statusHealthy,
|
|
21
|
+
string: lineColor5,
|
|
22
|
+
number: lineColor5,
|
|
23
|
+
boolean: lineColor5,
|
|
24
|
+
null: theme.textSecondary,
|
|
25
|
+
bracket: theme.textPrimary,
|
|
26
|
+
punctuation: theme.textPrimary,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
property: '#4078F2',
|
|
31
|
+
string: '#50A14F',
|
|
32
|
+
number: '#986801',
|
|
33
|
+
boolean: '#0184BC',
|
|
34
|
+
null: '#0184BC',
|
|
35
|
+
bracket: '#383A42',
|
|
36
|
+
punctuation: '#383A42',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createEditorTheme(theme: CoreUITheme): Extension {
|
|
41
|
+
const isDark = isDarkBackground(theme);
|
|
42
|
+
const syntax = getSyntaxColors(theme, isDark);
|
|
43
|
+
|
|
44
|
+
const editorViewTheme = EditorView.theme(
|
|
45
|
+
{
|
|
46
|
+
'&': {
|
|
47
|
+
backgroundColor: theme.backgroundLevel1,
|
|
48
|
+
color: theme.textPrimary,
|
|
49
|
+
},
|
|
50
|
+
'.cm-content': {
|
|
51
|
+
caretColor: theme.textPrimary,
|
|
52
|
+
fontFamily: "'Courier New', monospace",
|
|
53
|
+
fontSize: '12px',
|
|
54
|
+
},
|
|
55
|
+
'.cm-cursor, .cm-dropCursor': {
|
|
56
|
+
borderLeftColor: theme.textPrimary,
|
|
57
|
+
},
|
|
58
|
+
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
|
|
59
|
+
{
|
|
60
|
+
backgroundColor: theme.highlight,
|
|
61
|
+
},
|
|
62
|
+
'.cm-gutters': {
|
|
63
|
+
backgroundColor: theme.backgroundLevel1,
|
|
64
|
+
color: theme.textSecondary,
|
|
65
|
+
border: 'none',
|
|
66
|
+
borderRight: `1px solid ${theme.border}`,
|
|
67
|
+
},
|
|
68
|
+
'.cm-activeLineGutter': {
|
|
69
|
+
backgroundColor: theme.backgroundLevel3,
|
|
70
|
+
},
|
|
71
|
+
'.cm-activeLine': {
|
|
72
|
+
backgroundColor: theme.backgroundLevel3,
|
|
73
|
+
},
|
|
74
|
+
'.cm-foldPlaceholder': {
|
|
75
|
+
backgroundColor: 'transparent',
|
|
76
|
+
border: 'none',
|
|
77
|
+
color: theme.textSecondary,
|
|
78
|
+
},
|
|
79
|
+
'.cm-diagnostic-error': {
|
|
80
|
+
borderLeftColor: theme.statusCritical,
|
|
81
|
+
},
|
|
82
|
+
'.cm-diagnostic-warning': {
|
|
83
|
+
borderLeftColor: theme.statusWarning,
|
|
84
|
+
},
|
|
85
|
+
'.cm-lintRange-error': {
|
|
86
|
+
backgroundImage: 'none',
|
|
87
|
+
textDecoration: `underline wavy ${theme.statusCritical}`,
|
|
88
|
+
},
|
|
89
|
+
'.cm-lintRange-warning': {
|
|
90
|
+
backgroundImage: 'none',
|
|
91
|
+
textDecoration: `underline wavy ${theme.statusWarning}`,
|
|
92
|
+
},
|
|
93
|
+
'.cm-tooltip': {
|
|
94
|
+
backgroundColor: theme.backgroundLevel3,
|
|
95
|
+
color: theme.textPrimary,
|
|
96
|
+
border: `1px solid ${theme.border}`,
|
|
97
|
+
},
|
|
98
|
+
'.cm-tooltip-lint': {
|
|
99
|
+
backgroundColor: theme.backgroundLevel3,
|
|
100
|
+
},
|
|
101
|
+
'.cm-panels': {
|
|
102
|
+
backgroundColor: theme.backgroundLevel4,
|
|
103
|
+
color: theme.textPrimary,
|
|
104
|
+
},
|
|
105
|
+
'.cm-readonly-tooltip': {
|
|
106
|
+
backgroundColor: theme.backgroundLevel3,
|
|
107
|
+
color: theme.textPrimary,
|
|
108
|
+
border: `1px solid ${theme.border}`,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{ dark: isDark },
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const highlightStyle = HighlightStyle.define([
|
|
115
|
+
{ tag: tags.propertyName, color: syntax.property },
|
|
116
|
+
{ tag: tags.string, color: syntax.string },
|
|
117
|
+
{ tag: tags.number, color: syntax.number },
|
|
118
|
+
{ tag: tags.bool, color: syntax.boolean },
|
|
119
|
+
{ tag: tags.null, color: syntax.null },
|
|
120
|
+
{ tag: tags.punctuation, color: syntax.punctuation },
|
|
121
|
+
{ tag: tags.brace, color: syntax.bracket },
|
|
122
|
+
{ tag: tags.squareBracket, color: syntax.bracket },
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
return [editorViewTheme, syntaxHighlighting(highlightStyle)];
|
|
126
|
+
}
|
package/src/lib/next.ts
CHANGED
|
@@ -17,6 +17,8 @@ export { CoreUiThemeProvider } from './components/coreuithemeprovider/CoreUiThem
|
|
|
17
17
|
export { Box } from './components/box/Box';
|
|
18
18
|
export { Input } from './components/inputv2/inputv2';
|
|
19
19
|
export { Accordion } from './components/accordion/Accordion.component';
|
|
20
|
+
export { Editor } from './components/editor';
|
|
21
|
+
export type { EditorProps } from './components/editor';
|
|
20
22
|
|
|
21
23
|
// Export all chart components from the consolidated charts folder
|
|
22
24
|
export {
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Editor, EditorProps } from '../src/lib/next';
|
|
3
|
+
|
|
4
|
+
const sampleJson = JSON.stringify(
|
|
5
|
+
{
|
|
6
|
+
Version: '2012-10-17',
|
|
7
|
+
Statement: [
|
|
8
|
+
{
|
|
9
|
+
Sid: 'ExampleStatement',
|
|
10
|
+
Effect: 'Allow',
|
|
11
|
+
Principal: '*',
|
|
12
|
+
Action: 's3:GetObject',
|
|
13
|
+
Resource: 'arn:aws:s3:::my-bucket/*',
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
null,
|
|
18
|
+
2,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const invalidJson = JSON.stringify(
|
|
22
|
+
{
|
|
23
|
+
Version: 'invalid-version',
|
|
24
|
+
Statement: [
|
|
25
|
+
{
|
|
26
|
+
Sid: 'Test',
|
|
27
|
+
Effect: 'InvalidEffect',
|
|
28
|
+
Principal: '*',
|
|
29
|
+
Action: 's3:GetObject',
|
|
30
|
+
Resource: 'arn:aws:s3:::my-bucket/*',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
null,
|
|
35
|
+
2,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const policySchema = {
|
|
39
|
+
type: 'object' as const,
|
|
40
|
+
required: ['Version', 'Statement'],
|
|
41
|
+
properties: {
|
|
42
|
+
Version: {
|
|
43
|
+
type: 'string' as const,
|
|
44
|
+
enum: ['2012-10-17', '2008-10-17'],
|
|
45
|
+
},
|
|
46
|
+
Statement: {
|
|
47
|
+
type: 'array' as const,
|
|
48
|
+
minItems: 1,
|
|
49
|
+
items: {
|
|
50
|
+
type: 'object' as const,
|
|
51
|
+
required: ['Effect', 'Principal', 'Action', 'Resource'],
|
|
52
|
+
properties: {
|
|
53
|
+
Sid: { type: 'string' as const },
|
|
54
|
+
Effect: { type: 'string' as const, enum: ['Allow', 'Deny'] },
|
|
55
|
+
Principal: { type: 'string' as const },
|
|
56
|
+
Action: { type: 'string' as const },
|
|
57
|
+
Resource: { type: 'string' as const },
|
|
58
|
+
},
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default {
|
|
66
|
+
title: 'Components/Inputs/Editor',
|
|
67
|
+
component: Editor,
|
|
68
|
+
argTypes: {
|
|
69
|
+
readOnly: { control: 'boolean' },
|
|
70
|
+
height: { control: 'text' },
|
|
71
|
+
width: { control: 'text' },
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const EditorWithState = (args: EditorProps) => {
|
|
76
|
+
const [value, setValue] = useState(args.value || sampleJson);
|
|
77
|
+
return <Editor {...args} value={value} onChange={setValue} />;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const Default = {
|
|
81
|
+
render: (args: EditorProps) => <EditorWithState {...args} />,
|
|
82
|
+
args: {
|
|
83
|
+
value: sampleJson,
|
|
84
|
+
height: '400px',
|
|
85
|
+
width: '100%',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const ReadOnly = {
|
|
90
|
+
render: (args: EditorProps) => <EditorWithState {...args} />,
|
|
91
|
+
args: {
|
|
92
|
+
value: sampleJson,
|
|
93
|
+
readOnly: true,
|
|
94
|
+
height: '400px',
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Pass `language` as an object with a `schema` to enable
|
|
100
|
+
* validation, autocompletion, and hover tooltips from the schema.
|
|
101
|
+
*
|
|
102
|
+
* Hover over the red-underlined values to see error messages.
|
|
103
|
+
*/
|
|
104
|
+
export const WithSchemaValidation = {
|
|
105
|
+
render: (args: EditorProps) => <EditorWithState {...args} />,
|
|
106
|
+
args: {
|
|
107
|
+
value: invalidJson,
|
|
108
|
+
language: { name: 'json', schema: policySchema },
|
|
109
|
+
height: '400px',
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Valid JSON with schema — no errors shown, but autocompletion is active.
|
|
115
|
+
*/
|
|
116
|
+
export const ValidWithSchema = {
|
|
117
|
+
render: (args: EditorProps) => <EditorWithState {...args} />,
|
|
118
|
+
args: {
|
|
119
|
+
value: sampleJson,
|
|
120
|
+
language: { name: 'json', schema: policySchema },
|
|
121
|
+
height: '400px',
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const CustomDimensions = {
|
|
126
|
+
render: (args: EditorProps) => <EditorWithState {...args} />,
|
|
127
|
+
args: {
|
|
128
|
+
value: '{\n "compact": true\n}',
|
|
129
|
+
height: '200px',
|
|
130
|
+
width: '400px',
|
|
131
|
+
},
|
|
132
|
+
};
|