@centreon/ui 24.9.2 → 24.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/Form/Inputs/Text.tsx +15 -4
- package/src/Form/Inputs/models.ts +2 -1
- package/src/PopoverMenu/index.tsx +4 -4
- package/src/components/CopyCommand/CopyCommand.cypress.spec.tsx +155 -0
- package/src/components/CopyCommand/CopyCommand.stories.tsx +54 -0
- package/src/components/CopyCommand/CopyCommand.styles.tsx +93 -0
- package/src/components/CopyCommand/CopyCommand.tsx +80 -0
- package/src/components/CopyCommand/translatedLabels.ts +2 -0
- package/src/components/index.ts +1 -0
- package/src/Form/storiesData.tsx-E +0 -481
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centreon/ui",
|
|
3
|
-
"version": "24.9.
|
|
3
|
+
"version": "24.9.4",
|
|
4
4
|
"description": "Centreon UI Components",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"update:deps": "pnpx npm-check-updates -i --format group",
|
|
@@ -134,6 +134,7 @@
|
|
|
134
134
|
"anylogger": "^1.0.11",
|
|
135
135
|
"d3-array": "3.2.4",
|
|
136
136
|
"dayjs": "^1.11.12",
|
|
137
|
+
"highlight.js": "^11.10.0",
|
|
137
138
|
"humanize-duration": "^3.32.1",
|
|
138
139
|
"lexical": "^0.17.0",
|
|
139
140
|
"notistack": "^3.0.1",
|
package/src/Form/Inputs/Text.tsx
CHANGED
|
@@ -36,15 +36,21 @@ const Text = ({
|
|
|
36
36
|
|
|
37
37
|
const [isVisible, setIsVisible] = useState(false);
|
|
38
38
|
|
|
39
|
-
const {
|
|
40
|
-
|
|
39
|
+
const {
|
|
40
|
+
values,
|
|
41
|
+
setFieldValue,
|
|
42
|
+
touched,
|
|
43
|
+
errors,
|
|
44
|
+
handleBlur,
|
|
45
|
+
setFieldTouched
|
|
46
|
+
} = useFormikContext<FormikValues>();
|
|
41
47
|
|
|
42
48
|
const fieldNamePath = split('.', fieldName);
|
|
43
49
|
|
|
44
50
|
const changeText = (event: ChangeEvent<HTMLInputElement>): void => {
|
|
45
51
|
const { value } = event.target;
|
|
46
52
|
if (change) {
|
|
47
|
-
change({ setFieldValue, value });
|
|
53
|
+
change({ setFieldValue, value, setFieldTouched });
|
|
48
54
|
|
|
49
55
|
return;
|
|
50
56
|
}
|
|
@@ -111,7 +117,7 @@ const Text = ({
|
|
|
111
117
|
EndAdornment={EndAdornment}
|
|
112
118
|
ariaLabel={t(label) || ''}
|
|
113
119
|
autoFocus={autoFocus}
|
|
114
|
-
dataTestId={dataTestId ||
|
|
120
|
+
dataTestId={dataTestId || label}
|
|
115
121
|
disabled={disabled}
|
|
116
122
|
error={error as string | undefined}
|
|
117
123
|
label={t(label)}
|
|
@@ -123,6 +129,11 @@ const Text = ({
|
|
|
123
129
|
value={value || ''}
|
|
124
130
|
onBlur={handleBlur(fieldName)}
|
|
125
131
|
onChange={changeText}
|
|
132
|
+
inputProps={{
|
|
133
|
+
'data-testid': dataTestId || label,
|
|
134
|
+
'aria-label': label,
|
|
135
|
+
min: text?.min
|
|
136
|
+
}}
|
|
126
137
|
/>
|
|
127
138
|
),
|
|
128
139
|
memoProps: [
|
|
@@ -38,7 +38,7 @@ export interface InputProps {
|
|
|
38
38
|
creatable?: boolean;
|
|
39
39
|
options: Array<SelectEntry>;
|
|
40
40
|
};
|
|
41
|
-
change?: ({ setFieldValue, value }) => void;
|
|
41
|
+
change?: ({ setFieldValue, value, setFieldTouched }) => void;
|
|
42
42
|
checkbox?: {
|
|
43
43
|
direction?: 'horizontal' | 'vertical';
|
|
44
44
|
labelPlacement?: LabelPlacement;
|
|
@@ -102,6 +102,7 @@ export interface InputProps {
|
|
|
102
102
|
multilineRows?: number;
|
|
103
103
|
placeholder?: string;
|
|
104
104
|
type?: string;
|
|
105
|
+
min?: number;
|
|
105
106
|
};
|
|
106
107
|
type: InputType;
|
|
107
108
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
|
1
|
+
import { type Dispatch, type SetStateAction, useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { makeStyles } from 'tss-react/mui';
|
|
4
4
|
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
ClickAwayListener,
|
|
7
7
|
Paper,
|
|
8
8
|
Popper,
|
|
9
|
-
PopperPlacementType
|
|
9
|
+
type PopperPlacementType
|
|
10
10
|
} from '@mui/material';
|
|
11
|
-
import { PopperProps } from '@mui/material/Popper';
|
|
11
|
+
import type { PopperProps } from '@mui/material/Popper';
|
|
12
12
|
|
|
13
13
|
import { IconButton } from '..';
|
|
14
14
|
|
|
@@ -80,7 +80,7 @@ const PopoverMenu = ({
|
|
|
80
80
|
};
|
|
81
81
|
|
|
82
82
|
useEffect(() => {
|
|
83
|
-
if (!canOpen) {
|
|
83
|
+
if (!canOpen && isOpen) {
|
|
84
84
|
close();
|
|
85
85
|
}
|
|
86
86
|
}, [canOpen]);
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { ThemeMode, userAtom } from '@centreon/ui-context';
|
|
2
|
+
import { mount } from 'cypress/react18';
|
|
3
|
+
import { Provider, createStore } from 'jotai';
|
|
4
|
+
import SnackbarProvider from '../../Snackbar/SnackbarProvider';
|
|
5
|
+
import ThemeProvider from '../../ThemeProvider';
|
|
6
|
+
import CopyCommand, { CopyCommandProps } from './CopyCommand';
|
|
7
|
+
|
|
8
|
+
const initialize = (props: CopyCommandProps & { theme?: ThemeMode }): void => {
|
|
9
|
+
const store = createStore();
|
|
10
|
+
store.set(userAtom, { themeMode: props.theme || ThemeMode.light });
|
|
11
|
+
mount(
|
|
12
|
+
<Provider store={store}>
|
|
13
|
+
<ThemeProvider>
|
|
14
|
+
<SnackbarProvider>
|
|
15
|
+
<CopyCommand {...props} />
|
|
16
|
+
</SnackbarProvider>
|
|
17
|
+
</ThemeProvider>
|
|
18
|
+
</Provider>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('CopyCommand', () => {
|
|
23
|
+
it('displays bash code when props are set', () => {
|
|
24
|
+
initialize({
|
|
25
|
+
text: `# a simple command
|
|
26
|
+
echo "hello" | grep "hel"`,
|
|
27
|
+
language: 'bash',
|
|
28
|
+
commandToCopy: 'echo "hello" | grep "hel"'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
cy.contains('# a simple command').should('be.visible');
|
|
32
|
+
cy.contains('echo').should('be.visible');
|
|
33
|
+
cy.contains('bash').should('be.visible');
|
|
34
|
+
cy.findByTestId('Copy command').should('be.visible');
|
|
35
|
+
|
|
36
|
+
cy.makeSnapshot();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('displays yaml code when props are set', () => {
|
|
40
|
+
initialize({
|
|
41
|
+
text: `key:
|
|
42
|
+
with:
|
|
43
|
+
input: "input"`,
|
|
44
|
+
language: 'yaml',
|
|
45
|
+
commandToCopy: 'echo "hello" | grep "hel"'
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
cy.contains('key').should('be.visible');
|
|
49
|
+
cy.contains('"input"').should('be.visible');
|
|
50
|
+
cy.contains('yaml').should('be.visible');
|
|
51
|
+
|
|
52
|
+
cy.makeSnapshot();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('displays php code when props are set', () => {
|
|
56
|
+
initialize({
|
|
57
|
+
text: `<?php
|
|
58
|
+
echo 'Hello ' . htmlspecialchars($_POST["name"]) . '!';
|
|
59
|
+
?>`,
|
|
60
|
+
language: 'php',
|
|
61
|
+
commandToCopy: 'echo "hello" | grep "hel"'
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
cy.contains('echo').should('be.visible');
|
|
65
|
+
cy.contains('"name"').should('be.visible');
|
|
66
|
+
cy.contains('php').should('be.visible');
|
|
67
|
+
|
|
68
|
+
cy.makeSnapshot();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('displays json code when props are set', () => {
|
|
72
|
+
initialize({
|
|
73
|
+
text: `{
|
|
74
|
+
"number": 1,
|
|
75
|
+
"boolean": true,
|
|
76
|
+
"array": [
|
|
77
|
+
{
|
|
78
|
+
"string": "text"
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}`,
|
|
82
|
+
language: 'json',
|
|
83
|
+
commandToCopy: 'echo "hello" | grep "hel"'
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
cy.contains('"number"').should('be.visible');
|
|
87
|
+
cy.contains('true').should('be.visible');
|
|
88
|
+
cy.contains('json').should('be.visible');
|
|
89
|
+
|
|
90
|
+
cy.makeSnapshot();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('does not display the copy button when the corresponding prop is not passed', () => {
|
|
94
|
+
initialize({
|
|
95
|
+
text: `{
|
|
96
|
+
"number": 1,
|
|
97
|
+
"boolean": true,
|
|
98
|
+
"array": [
|
|
99
|
+
{
|
|
100
|
+
"string": "text"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}`,
|
|
104
|
+
language: 'json'
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
cy.findByTestId('Copy command').should('not.exist');
|
|
108
|
+
|
|
109
|
+
cy.makeSnapshot();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('displays the highlighted code in dark mode when the theme is changed', () => {
|
|
113
|
+
initialize({
|
|
114
|
+
text: `{
|
|
115
|
+
"number": 1,
|
|
116
|
+
"boolean": true,
|
|
117
|
+
"array": [
|
|
118
|
+
{
|
|
119
|
+
"string": "text"
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}`,
|
|
123
|
+
language: 'json',
|
|
124
|
+
theme: ThemeMode.dark
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
cy.get('.hljs-keyword').should('have.css', 'color', 'rgb(255, 123, 114)');
|
|
128
|
+
|
|
129
|
+
cy.makeSnapshot();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('copies the command to the clipboard when the button is clicked', () => {
|
|
133
|
+
initialize({
|
|
134
|
+
text: `# a simple command
|
|
135
|
+
echo "hello" | grep "hel"`,
|
|
136
|
+
language: 'bash',
|
|
137
|
+
commandToCopy: 'echo "hello" | grep "hel"'
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
cy.window()
|
|
141
|
+
.its('navigator.clipboard')
|
|
142
|
+
.then((clipboard) => {
|
|
143
|
+
cy.spy(clipboard, 'writeText').as('writeText');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
cy.findByTestId('Copy command').click();
|
|
147
|
+
|
|
148
|
+
cy.get('@writeText').should(
|
|
149
|
+
'have.been.calledOnceWith',
|
|
150
|
+
'echo "hello" | grep "hel"'
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
cy.makeSnapshot();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import CopyCommand from './CopyCommand';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof CopyCommand> = {
|
|
5
|
+
component: CopyCommand
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default meta;
|
|
9
|
+
type Story = StoryObj<typeof CopyCommand>;
|
|
10
|
+
|
|
11
|
+
export const Default: Story = {
|
|
12
|
+
args: {
|
|
13
|
+
text: 'key:\n with:\n input: "heyyy"',
|
|
14
|
+
language: 'yaml'
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const OneLine: Story = {
|
|
19
|
+
args: {
|
|
20
|
+
text: 'echo "hello" | grep "hel"',
|
|
21
|
+
language: 'bash'
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const WithCopyCommandIcon: Story = {
|
|
26
|
+
args: {
|
|
27
|
+
text: `# a simple command
|
|
28
|
+
echo "hello" | grep "hel"`,
|
|
29
|
+
language: 'bash',
|
|
30
|
+
commandToCopy: 'echo "hello" | grep "hel"'
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const UsingJson: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
text: `{
|
|
37
|
+
"number": 1,
|
|
38
|
+
"boolean": true,
|
|
39
|
+
"array": [
|
|
40
|
+
{
|
|
41
|
+
"string": "text"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}`,
|
|
45
|
+
language: 'json'
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const UsingPhp: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
text: "<?php echo '<p>Hello World</p>'; ?>",
|
|
52
|
+
language: 'php'
|
|
53
|
+
}
|
|
54
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ThemeMode } from '@centreon/ui-context';
|
|
2
|
+
import { Theme } from '@mui/material';
|
|
3
|
+
import { equals } from 'ramda';
|
|
4
|
+
import { makeStyles } from 'tss-react/mui';
|
|
5
|
+
|
|
6
|
+
const isDarkMode = (theme: Theme): boolean => {
|
|
7
|
+
return equals(theme.palette.mode, ThemeMode.dark);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const useCopyCommandStyle = makeStyles()((theme) => ({
|
|
11
|
+
code: {
|
|
12
|
+
background: theme.palette.background.default,
|
|
13
|
+
padding: theme.spacing(1.5),
|
|
14
|
+
borderRadius: theme.shape.borderRadius
|
|
15
|
+
},
|
|
16
|
+
codeContainer: {
|
|
17
|
+
position: 'relative'
|
|
18
|
+
},
|
|
19
|
+
languageChip: {
|
|
20
|
+
position: 'absolute',
|
|
21
|
+
top: 0,
|
|
22
|
+
right: 0,
|
|
23
|
+
borderRadius: `0px ${theme.shape.borderRadius}px ${theme.shape.borderRadius}px ${theme.shape.borderRadius}px`,
|
|
24
|
+
background: theme.palette.background.listingHeader,
|
|
25
|
+
color: theme.palette.common.white,
|
|
26
|
+
padding: `0px ${theme.spacing(0.5)}`,
|
|
27
|
+
fontSize: theme.typography.body2.fontSize
|
|
28
|
+
},
|
|
29
|
+
copyButton: {
|
|
30
|
+
position: 'absolute',
|
|
31
|
+
top: theme.spacing(0.5),
|
|
32
|
+
right: theme.spacing(6),
|
|
33
|
+
opacity: 0.8,
|
|
34
|
+
'&:hover': {
|
|
35
|
+
opacity: 1
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
highlight: {
|
|
39
|
+
'.hljs': {
|
|
40
|
+
color: isDarkMode(theme) ? '#c9d1d9' : '#24292e'
|
|
41
|
+
},
|
|
42
|
+
'.hljs-doctag, .hljs-keyword, .hljs-meta, .hljs-keyword, .hljs-template-tag, .hljs-template-variable, .hljs-type, .hljs-variable.language_':
|
|
43
|
+
{
|
|
44
|
+
color: isDarkMode(theme) ? '#ff7b72' : '#d73a49'
|
|
45
|
+
},
|
|
46
|
+
'.hljs-title, .hljs-title.class,_ .hljs-title.class_.inherited,__ .hljs-title.function_':
|
|
47
|
+
{
|
|
48
|
+
color: isDarkMode(theme) ? '#d2a8ff' : '#6f42c1'
|
|
49
|
+
},
|
|
50
|
+
'.hljs-attr, .hljs-attribute, .hljs-literal, .hljs-meta, .hljs-number, .hljs-operator, .hljs-variable, .hljs-selector-attr, .hljs-selector-class, .hljs-selector-id':
|
|
51
|
+
{
|
|
52
|
+
color: isDarkMode(theme) ? '#79c0ff' : '#005cc5'
|
|
53
|
+
},
|
|
54
|
+
'.hljs-regexp, .hljs-string, .hljs-meta, .hljs-string': {
|
|
55
|
+
color: isDarkMode(theme) ? '#a5d6ff' : '#032f62'
|
|
56
|
+
},
|
|
57
|
+
'.hljs-built_in, .hljs-symbol': {
|
|
58
|
+
color: isDarkMode(theme) ? '#ffa657' : '#e36209'
|
|
59
|
+
},
|
|
60
|
+
'.hljs-code, .hljs-comment, .hljs-formula': {
|
|
61
|
+
color: isDarkMode(theme) ? '#8b949e' : '#6a737d'
|
|
62
|
+
},
|
|
63
|
+
'.hljs-name, .hljs-quote, .hljs-selector-tag, .hljs-selector-pseudo': {
|
|
64
|
+
color: isDarkMode(theme) ? '#7ee787' : '#22863a'
|
|
65
|
+
},
|
|
66
|
+
'.hljs-subst': {
|
|
67
|
+
color: isDarkMode(theme) ? '#c9d1d9' : '#24292e'
|
|
68
|
+
},
|
|
69
|
+
'.hljs-section': {
|
|
70
|
+
color: isDarkMode(theme) ? '#1f6feb' : '#005cc5',
|
|
71
|
+
fontWeight: theme.typography.fontWeightBold
|
|
72
|
+
},
|
|
73
|
+
'.hljs-bullet': {
|
|
74
|
+
color: isDarkMode(theme) ? '#f2cc60' : '#735c0f'
|
|
75
|
+
},
|
|
76
|
+
'.hljs-emphasis': {
|
|
77
|
+
color: isDarkMode(theme) ? '#c9d1d9' : '#24292e',
|
|
78
|
+
fontStyle: 'italic'
|
|
79
|
+
},
|
|
80
|
+
'.hljs-strong': {
|
|
81
|
+
color: isDarkMode(theme) ? '#c9d1d9' : '#24292e',
|
|
82
|
+
fontWeight: theme.typography.fontWeightBold
|
|
83
|
+
},
|
|
84
|
+
'.hljs-addition': {
|
|
85
|
+
color: isDarkMode(theme) ? '#aff5b4' : '#22863a'
|
|
86
|
+
},
|
|
87
|
+
'.hljs-deletion': {
|
|
88
|
+
color: isDarkMode(theme) ? '#ffdcd7' : '#b31d28'
|
|
89
|
+
},
|
|
90
|
+
'.hljs-char.escape_, .hljs-link, .hljs-params, .hljs-property, .hljs-punctuation, .hljs-tag':
|
|
91
|
+
{}
|
|
92
|
+
}
|
|
93
|
+
}));
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import hljs from 'highlight.js/lib/core';
|
|
2
|
+
|
|
3
|
+
import bash from 'highlight.js/lib/languages/bash';
|
|
4
|
+
import json from 'highlight.js/lib/languages/json';
|
|
5
|
+
import php from 'highlight.js/lib/languages/php';
|
|
6
|
+
import yaml from 'highlight.js/lib/languages/yaml';
|
|
7
|
+
|
|
8
|
+
import ContentCopy from '@mui/icons-material/ContentCopy';
|
|
9
|
+
import parse from 'html-react-parser';
|
|
10
|
+
import { useTranslation } from 'react-i18next';
|
|
11
|
+
import { useCopyToClipboard } from '../../utils';
|
|
12
|
+
import { IconButton } from '../Button';
|
|
13
|
+
import { useCopyCommandStyle } from './CopyCommand.styles';
|
|
14
|
+
import {
|
|
15
|
+
labelCommandCopied,
|
|
16
|
+
labelFailedToCopyCommand
|
|
17
|
+
} from './translatedLabels';
|
|
18
|
+
|
|
19
|
+
hljs.registerLanguage('bash', bash);
|
|
20
|
+
hljs.registerLanguage('shell', bash);
|
|
21
|
+
hljs.registerLanguage('yaml', yaml);
|
|
22
|
+
hljs.registerLanguage('yml', yaml);
|
|
23
|
+
hljs.registerLanguage('json', json);
|
|
24
|
+
hljs.registerLanguage('php', php);
|
|
25
|
+
|
|
26
|
+
hljs.addPlugin({
|
|
27
|
+
'after:highlight': (result) => {
|
|
28
|
+
const leadingSpaces = result.value.match(/\n\s+/g);
|
|
29
|
+
|
|
30
|
+
leadingSpaces?.forEach((leadingSpace) => {
|
|
31
|
+
const sanitizedLeadingSpace = leadingSpace.replace(/\s/g, ' ');
|
|
32
|
+
result.value = result.value.replace(
|
|
33
|
+
leadingSpace,
|
|
34
|
+
`\n${sanitizedLeadingSpace}`
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export interface CopyCommandProps {
|
|
41
|
+
text: string;
|
|
42
|
+
commandToCopy?: string;
|
|
43
|
+
language: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const CopyCommand = ({ text, commandToCopy, language }: CopyCommandProps) => {
|
|
47
|
+
const { t } = useTranslation();
|
|
48
|
+
const { classes, cx } = useCopyCommandStyle();
|
|
49
|
+
|
|
50
|
+
const { copy } = useCopyToClipboard({
|
|
51
|
+
successMessage: t(labelCommandCopied),
|
|
52
|
+
errorMessage: t(labelFailedToCopyCommand)
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const copyCommand = (): Promise<void> | undefined | '' =>
|
|
56
|
+
commandToCopy && copy(commandToCopy);
|
|
57
|
+
|
|
58
|
+
const highlightedText = hljs.highlight(text, { language }).value;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className={classes.codeContainer}>
|
|
62
|
+
<pre className={cx(classes.code, classes.highlight)}>
|
|
63
|
+
{parse(`${highlightedText}`)}
|
|
64
|
+
</pre>
|
|
65
|
+
<div className={classes.languageChip}>{language}</div>
|
|
66
|
+
{commandToCopy && (
|
|
67
|
+
<IconButton
|
|
68
|
+
data-testid="Copy command"
|
|
69
|
+
variant="ghost"
|
|
70
|
+
className={classes.copyButton}
|
|
71
|
+
icon={<ContentCopy fontSize="small" />}
|
|
72
|
+
size="small"
|
|
73
|
+
onClick={copyCommand}
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default CopyCommand;
|
package/src/components/index.ts
CHANGED
|
@@ -1,481 +0,0 @@
|
|
|
1
|
-
import { FormikValues, useFormikContext } from 'formik';
|
|
2
|
-
import { equals, prop } from 'ramda';
|
|
3
|
-
|
|
4
|
-
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
|
|
5
|
-
import MailIcon from '@mui/icons-material/MailOutline';
|
|
6
|
-
import { Typography } from '@mui/material';
|
|
7
|
-
|
|
8
|
-
import { SelectEntry } from '../InputField/Select';
|
|
9
|
-
import { Listing } from '../api/models';
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
Group,
|
|
13
|
-
InputProps,
|
|
14
|
-
InputPropsWithoutGroup,
|
|
15
|
-
InputType
|
|
16
|
-
} from './Inputs/models';
|
|
17
|
-
import { object } from ';
|
|
18
|
-
|
|
19
|
-
export interface BasicForm {
|
|
20
|
-
active: boolean;
|
|
21
|
-
activeSortableFieldsTable: boolean;
|
|
22
|
-
animals: Array<SelectEntry>;
|
|
23
|
-
anotherText: string;
|
|
24
|
-
certificate: string;
|
|
25
|
-
class: { id: number; name: string } | null;
|
|
26
|
-
custom: string;
|
|
27
|
-
email: string;
|
|
28
|
-
group: { id: number; name: string } | null;
|
|
29
|
-
inviteUsers: Array<{
|
|
30
|
-
role: SelectEntry;
|
|
31
|
-
user: string;
|
|
32
|
-
}>;
|
|
33
|
-
inviteUsers2: Array<string>;
|
|
34
|
-
isForced: boolean;
|
|
35
|
-
language: string;
|
|
36
|
-
name: string;
|
|
37
|
-
password: string;
|
|
38
|
-
roleMapping: Array<{
|
|
39
|
-
role: SelectEntry;
|
|
40
|
-
value: string;
|
|
41
|
-
}>;
|
|
42
|
-
scopes: Array<string>;
|
|
43
|
-
sports: Array<SelectEntry>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const selectEntryValidationSchema = object().shape({
|
|
47
|
-
id: number().required('Required'),
|
|
48
|
-
name: string().required('Required')
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
export const basicFormValidationSchema = object().shape({
|
|
52
|
-
active: boolean().required('Active is required'),
|
|
53
|
-
activeSortableFieldsTable: boolean().required(
|
|
54
|
-
'Active Sortable FieldsTable is required'
|
|
55
|
-
),
|
|
56
|
-
animals: array().of(selectEntryValidationSchema.required('Required')),
|
|
57
|
-
anotherText: string(),
|
|
58
|
-
certificate: string(),
|
|
59
|
-
class: selectEntryValidationSchema.nullable().required('Required'),
|
|
60
|
-
custom: string().required('Custom is required'),
|
|
61
|
-
email: string().email('Invalid email').required('Email is required'),
|
|
62
|
-
group: selectEntryValidationSchema.nullable().required('Required'),
|
|
63
|
-
inviteUsers: array().of(
|
|
64
|
-
object({
|
|
65
|
-
email: string()
|
|
66
|
-
.email('Invalid user email')
|
|
67
|
-
.required('Email is required'),
|
|
68
|
-
role: selectEntryValidationSchema
|
|
69
|
-
})
|
|
70
|
-
),
|
|
71
|
-
inviteUsers2: array().of(string().email('Invalid user email')),
|
|
72
|
-
isForced: boolean().required('Is forced is required'),
|
|
73
|
-
language: string().required('Language is required'),
|
|
74
|
-
name: string().required('Name is required'),
|
|
75
|
-
password: string().required('Password is required'),
|
|
76
|
-
roleMapping: array().of(
|
|
77
|
-
object({
|
|
78
|
-
role: selectEntryValidationSchema,
|
|
79
|
-
value: string().required('Role value is required')
|
|
80
|
-
})
|
|
81
|
-
),
|
|
82
|
-
scopes: array().of(
|
|
83
|
-
string().min(3, '3 characters min').required('Required')
|
|
84
|
-
),
|
|
85
|
-
sports: Yup.array().of(selectEntryValidationSchema.required('Required'))
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const roleEntries: Array<SelectEntry> = [
|
|
89
|
-
{
|
|
90
|
-
id: 1,
|
|
91
|
-
name: 'Administrator'
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
id: 2,
|
|
95
|
-
name: 'User'
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
id: 3,
|
|
99
|
-
name: 'Editor'
|
|
100
|
-
}
|
|
101
|
-
];
|
|
102
|
-
|
|
103
|
-
export const basicFormInitialValues = {
|
|
104
|
-
active: false,
|
|
105
|
-
activeSortableFieldsTable: false,
|
|
106
|
-
animals: [],
|
|
107
|
-
certificate: '',
|
|
108
|
-
class: { id: 0, name: 'Class 0' },
|
|
109
|
-
custom: '',
|
|
110
|
-
email: '',
|
|
111
|
-
group: null,
|
|
112
|
-
inviteUsers: [],
|
|
113
|
-
inviteUsers2: [],
|
|
114
|
-
isForced: false,
|
|
115
|
-
language: 'French',
|
|
116
|
-
name: '',
|
|
117
|
-
notifications: {
|
|
118
|
-
channels: { Icon: MailIcon, checked: true, label: 'mail' },
|
|
119
|
-
hostevents: ['ok', 'warning'],
|
|
120
|
-
includeServices: { checked: true, label: 'Include services for this host' }
|
|
121
|
-
},
|
|
122
|
-
password: '',
|
|
123
|
-
roleMapping: [
|
|
124
|
-
{
|
|
125
|
-
priority: 0,
|
|
126
|
-
role: roleEntries[0],
|
|
127
|
-
value: 'example'
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
priority: 1,
|
|
131
|
-
role: roleEntries[1],
|
|
132
|
-
value: 'example2'
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
priority: 2,
|
|
136
|
-
role: roleEntries[2],
|
|
137
|
-
value: 'example3'
|
|
138
|
-
}
|
|
139
|
-
],
|
|
140
|
-
scopes: [],
|
|
141
|
-
sports: []
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
export const classOptions = [...Array(10).keys()].map((idx) => ({
|
|
145
|
-
id: idx,
|
|
146
|
-
name: `Class ${idx}`
|
|
147
|
-
}));
|
|
148
|
-
|
|
149
|
-
export const sportOptions = [...Array(10).keys()].map((idx) => ({
|
|
150
|
-
id: idx,
|
|
151
|
-
name: `Sport ${idx}`
|
|
152
|
-
}));
|
|
153
|
-
|
|
154
|
-
export const basicFormGroups: Array<Group> = [
|
|
155
|
-
{
|
|
156
|
-
name: 'First group',
|
|
157
|
-
order: 1
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
EndIcon: () => <HelpOutlineIcon />,
|
|
161
|
-
TooltipContent: (): JSX.Element => <Typography>Tooltip content</Typography>,
|
|
162
|
-
name: 'Second group',
|
|
163
|
-
order: 2
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
name: 'Third group',
|
|
167
|
-
order: 3
|
|
168
|
-
}
|
|
169
|
-
];
|
|
170
|
-
|
|
171
|
-
export const basicFormInputs: Array<InputProps> = [
|
|
172
|
-
{
|
|
173
|
-
fieldName: 'name',
|
|
174
|
-
group: 'First group',
|
|
175
|
-
label: 'Name',
|
|
176
|
-
type: InputType.Text
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
fieldName: 'email',
|
|
180
|
-
group: 'First group',
|
|
181
|
-
label: 'Email',
|
|
182
|
-
text: {
|
|
183
|
-
endAdornment: <MailIcon />,
|
|
184
|
-
placeholder: 'Your email here'
|
|
185
|
-
},
|
|
186
|
-
type: InputType.Text
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
fieldName: 'active',
|
|
190
|
-
group: 'Second group',
|
|
191
|
-
label: 'Active',
|
|
192
|
-
type: InputType.Switch
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
additionalLabel: 'This a very special label',
|
|
196
|
-
fieldName: 'password',
|
|
197
|
-
group: 'First group',
|
|
198
|
-
hideInput: (values) => values.active,
|
|
199
|
-
label: 'Password',
|
|
200
|
-
type: InputType.Password
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
fieldName: 'language',
|
|
204
|
-
group: 'First group',
|
|
205
|
-
label: 'Language',
|
|
206
|
-
radio: {
|
|
207
|
-
options: [
|
|
208
|
-
{
|
|
209
|
-
label: 'French',
|
|
210
|
-
value: 'French'
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
label: 'English',
|
|
214
|
-
value: 'English'
|
|
215
|
-
}
|
|
216
|
-
]
|
|
217
|
-
},
|
|
218
|
-
type: InputType.Radio
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
additionalLabel: 'Notifications',
|
|
222
|
-
fieldName: '',
|
|
223
|
-
grid: {
|
|
224
|
-
alignItems: 'center',
|
|
225
|
-
columns: [
|
|
226
|
-
{
|
|
227
|
-
checkbox: {
|
|
228
|
-
direction: 'horizontal'
|
|
229
|
-
},
|
|
230
|
-
fieldName: 'notifications.channels',
|
|
231
|
-
label: 'channels',
|
|
232
|
-
type: InputType.Checkbox
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
fieldName: 'notifications.includeServices',
|
|
236
|
-
label: 'Iclude services',
|
|
237
|
-
type: InputType.Checkbox
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
checkbox: {
|
|
241
|
-
direction: 'horizontal',
|
|
242
|
-
labelPlacement: 'top',
|
|
243
|
-
options: ['ok', 'warning', 'critical', 'unknown']
|
|
244
|
-
},
|
|
245
|
-
fieldName: 'notifications.hostevents',
|
|
246
|
-
label: 'host events',
|
|
247
|
-
type: InputType.CheckboxGroup
|
|
248
|
-
}
|
|
249
|
-
]
|
|
250
|
-
},
|
|
251
|
-
group: 'Third group',
|
|
252
|
-
label: 'Notifications',
|
|
253
|
-
type: InputType.Grid
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
fieldName: 'anotherText',
|
|
257
|
-
group: 'First group',
|
|
258
|
-
hideInput: ({ language }) => equals(language, 'French'),
|
|
259
|
-
label: 'Another Text input',
|
|
260
|
-
type: InputType.Text
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
fieldName: 'isForced',
|
|
264
|
-
group: 'First group',
|
|
265
|
-
label: 'Is Forced?',
|
|
266
|
-
radio: {
|
|
267
|
-
options: [
|
|
268
|
-
{
|
|
269
|
-
label: 'Is not forced',
|
|
270
|
-
value: false
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
label: 'Is forced',
|
|
274
|
-
value: true
|
|
275
|
-
}
|
|
276
|
-
]
|
|
277
|
-
},
|
|
278
|
-
type: InputType.Radio
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
fieldName: '',
|
|
282
|
-
grid: {
|
|
283
|
-
columns: [
|
|
284
|
-
{
|
|
285
|
-
autocomplete: {
|
|
286
|
-
options: classOptions
|
|
287
|
-
},
|
|
288
|
-
fieldName: 'class',
|
|
289
|
-
label: 'Class (Single autocomplete)',
|
|
290
|
-
type: InputType.SingleAutocomplete
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
autocomplete: {
|
|
294
|
-
options: sportOptions
|
|
295
|
-
},
|
|
296
|
-
fieldName: 'sports',
|
|
297
|
-
label: 'Sports (Multi autocomplete)',
|
|
298
|
-
type: InputType.MultiAutocomplete
|
|
299
|
-
}
|
|
300
|
-
]
|
|
301
|
-
},
|
|
302
|
-
group: 'First group',
|
|
303
|
-
label: 'autocompletes',
|
|
304
|
-
type: InputType.Grid
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
autocomplete: {
|
|
308
|
-
creatable: true,
|
|
309
|
-
options: []
|
|
310
|
-
},
|
|
311
|
-
fieldName: 'scopes',
|
|
312
|
-
group: 'First group',
|
|
313
|
-
label: 'Scopes (Multi autocomplete that allows value creation)',
|
|
314
|
-
type: InputType.MultiAutocomplete
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
fieldName: '',
|
|
318
|
-
grid: {
|
|
319
|
-
columns: [
|
|
320
|
-
{
|
|
321
|
-
connectedAutocomplete: {
|
|
322
|
-
additionalConditionParameters: [],
|
|
323
|
-
endpoint: 'endpoint'
|
|
324
|
-
},
|
|
325
|
-
fieldName: 'group',
|
|
326
|
-
label: 'Group (Single connected autocomplete)',
|
|
327
|
-
type: InputType.SingleConnectedAutocomplete
|
|
328
|
-
},
|
|
329
|
-
{
|
|
330
|
-
connectedAutocomplete: {
|
|
331
|
-
additionalConditionParameters: [],
|
|
332
|
-
endpoint: 'endpoint'
|
|
333
|
-
},
|
|
334
|
-
fieldName: 'animals',
|
|
335
|
-
label: 'Animals (Multi connected autocomplete)',
|
|
336
|
-
type: InputType.MultiConnectedAutocomplete
|
|
337
|
-
}
|
|
338
|
-
],
|
|
339
|
-
gridTemplateColumns: '400px 1fr'
|
|
340
|
-
},
|
|
341
|
-
group: 'First group',
|
|
342
|
-
label: 'connected autocompletes',
|
|
343
|
-
type: InputType.Grid
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
custom: {
|
|
347
|
-
Component: ({ label }: InputPropsWithoutGroup): JSX.Element => (
|
|
348
|
-
<Typography>This is a {label} component</Typography>
|
|
349
|
-
)
|
|
350
|
-
},
|
|
351
|
-
fieldName: 'custom',
|
|
352
|
-
group: 'Second group',
|
|
353
|
-
label: 'Custom',
|
|
354
|
-
type: InputType.Custom
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
fieldName: 'inviteUsers',
|
|
358
|
-
fieldsTable: {
|
|
359
|
-
columns: [
|
|
360
|
-
{
|
|
361
|
-
fieldName: 'email',
|
|
362
|
-
label: 'Email',
|
|
363
|
-
required: true,
|
|
364
|
-
type: InputType.Text
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
autocomplete: {
|
|
368
|
-
creatable: false,
|
|
369
|
-
options: roleEntries
|
|
370
|
-
},
|
|
371
|
-
fieldName: 'role',
|
|
372
|
-
label: 'Role',
|
|
373
|
-
type: InputType.SingleAutocomplete
|
|
374
|
-
}
|
|
375
|
-
],
|
|
376
|
-
defaultRowValue: {
|
|
377
|
-
email: 'example@test.fr',
|
|
378
|
-
role: null
|
|
379
|
-
},
|
|
380
|
-
deleteLabel: 'Delete'
|
|
381
|
-
},
|
|
382
|
-
group: 'First group',
|
|
383
|
-
label: 'inviteUsers',
|
|
384
|
-
type: InputType.FieldsTable
|
|
385
|
-
},
|
|
386
|
-
{
|
|
387
|
-
fieldName: 'inviteUsers2',
|
|
388
|
-
fieldsTable: {
|
|
389
|
-
columns: [
|
|
390
|
-
{
|
|
391
|
-
fieldName: 'email',
|
|
392
|
-
label: 'Email',
|
|
393
|
-
required: true,
|
|
394
|
-
type: InputType.Text
|
|
395
|
-
}
|
|
396
|
-
],
|
|
397
|
-
defaultRowValue: 'example',
|
|
398
|
-
deleteLabel: 'Delete',
|
|
399
|
-
hasSingleValue: true
|
|
400
|
-
},
|
|
401
|
-
group: 'First group',
|
|
402
|
-
label: 'inviteUsers2',
|
|
403
|
-
type: InputType.FieldsTable
|
|
404
|
-
},
|
|
405
|
-
{
|
|
406
|
-
fieldName: 'activeSortableFieldsTable',
|
|
407
|
-
group: 'First group',
|
|
408
|
-
label: 'Active Sortable Fields Table',
|
|
409
|
-
type: InputType.Switch
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
fieldName: 'roleMapping',
|
|
413
|
-
fieldsTable: {
|
|
414
|
-
columns: [
|
|
415
|
-
{
|
|
416
|
-
fieldName: 'value',
|
|
417
|
-
label: 'RoleValue',
|
|
418
|
-
required: true,
|
|
419
|
-
type: InputType.Text
|
|
420
|
-
},
|
|
421
|
-
{
|
|
422
|
-
autocomplete: {
|
|
423
|
-
creatable: false,
|
|
424
|
-
options: roleEntries
|
|
425
|
-
},
|
|
426
|
-
fieldName: 'role',
|
|
427
|
-
label: 'RoleAcl',
|
|
428
|
-
type: InputType.SingleAutocomplete
|
|
429
|
-
}
|
|
430
|
-
],
|
|
431
|
-
defaultRowValue: {
|
|
432
|
-
role: null,
|
|
433
|
-
value: ''
|
|
434
|
-
},
|
|
435
|
-
deleteLabel: 'Delete',
|
|
436
|
-
getSortable: (values: FormikValues): boolean =>
|
|
437
|
-
prop('activeSortableFieldsTable', values)
|
|
438
|
-
},
|
|
439
|
-
group: 'First group',
|
|
440
|
-
label: 'roleMapping',
|
|
441
|
-
type: InputType.FieldsTable
|
|
442
|
-
},
|
|
443
|
-
{
|
|
444
|
-
fieldName: 'certificate',
|
|
445
|
-
group: 'First group',
|
|
446
|
-
label: 'Certificate',
|
|
447
|
-
text: {
|
|
448
|
-
multilineRows: 4
|
|
449
|
-
},
|
|
450
|
-
type: InputType.Text
|
|
451
|
-
}
|
|
452
|
-
];
|
|
453
|
-
|
|
454
|
-
export const CustomButton = (): JSX.Element => {
|
|
455
|
-
const { dirty, isValid } = useFormikContext();
|
|
456
|
-
|
|
457
|
-
return (
|
|
458
|
-
<div>
|
|
459
|
-
<Typography>Has form changed? {JSON.stringify(dirty)}</Typography>
|
|
460
|
-
<Typography>Is valid? {JSON.stringify(isValid)}</Typography>
|
|
461
|
-
</div>
|
|
462
|
-
);
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
const buildEntities = (from): Array<SelectEntry> => {
|
|
466
|
-
return Array(10)
|
|
467
|
-
.fill(0)
|
|
468
|
-
.map((_, index) => ({
|
|
469
|
-
id: from + index,
|
|
470
|
-
name: `Entity ${from + index}`
|
|
471
|
-
}));
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
export const buildResult = (page): Listing<SelectEntry> => ({
|
|
475
|
-
meta: {
|
|
476
|
-
limit: 10,
|
|
477
|
-
page,
|
|
478
|
-
total: 40
|
|
479
|
-
},
|
|
480
|
-
result: buildEntities((page - 1) * 10)
|
|
481
|
-
});
|