@qoretechnologies/reqraft 0.3.1 → 0.3.3
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/form/fields/Field.js +24 -31
- package/dist/components/form/fields/Field.js.map +1 -1
- package/dist/components/form/fields/boolean/Boolean.js +5 -9
- package/dist/components/form/fields/boolean/Boolean.js.map +1 -1
- package/dist/components/form/fields/color/Color.js +7 -14
- package/dist/components/form/fields/color/Color.js.map +1 -1
- package/dist/components/form/fields/cron/Cron.js +12 -19
- package/dist/components/form/fields/cron/Cron.js.map +1 -1
- package/dist/components/form/fields/long-string/LongString.js +5 -9
- package/dist/components/form/fields/long-string/LongString.js.map +1 -1
- package/dist/components/form/fields/markdown/Markdown.js +13 -20
- package/dist/components/form/fields/markdown/Markdown.js.map +1 -1
- package/dist/components/form/fields/number/Number.js +5 -9
- package/dist/components/form/fields/number/Number.js.map +1 -1
- package/dist/components/form/fields/radio-group/RadioGroup.js +7 -11
- package/dist/components/form/fields/radio-group/RadioGroup.js.map +1 -1
- package/dist/components/form/fields/string/String.js +4 -8
- package/dist/components/form/fields/string/String.js.map +1 -1
- package/dist/components/form/index.js +8 -24
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/menu/Menu.js +23 -31
- package/dist/components/menu/Menu.js.map +1 -1
- package/dist/contexts/FetchContext.js +2 -5
- package/dist/contexts/FetchContext.js.map +1 -1
- package/dist/contexts/ReqraftContext.js +2 -5
- package/dist/contexts/ReqraftContext.js.map +1 -1
- package/dist/contexts/StorageContext.js +2 -5
- package/dist/contexts/StorageContext.js.map +1 -1
- package/dist/hooks/useFetch/useFetch.js +10 -14
- package/dist/hooks/useFetch/useFetch.js.map +1 -1
- package/dist/hooks/useReqraftProperty.js +4 -8
- package/dist/hooks/useReqraftProperty.js.map +1 -1
- package/dist/hooks/useStorage/useStorage.d.ts.map +1 -1
- package/dist/hooks/useStorage/useStorage.js +13 -13
- package/dist/hooks/useStorage/useStorage.js.map +1 -1
- package/dist/hooks/useValidation.js +1 -5
- package/dist/hooks/useValidation.js.map +1 -1
- package/dist/index.js +6 -30
- package/dist/index.js.map +1 -1
- package/dist/providers/FetchProvider.js +11 -15
- package/dist/providers/FetchProvider.js.map +1 -1
- package/dist/providers/ReqraftProvider.js +12 -17
- package/dist/providers/ReqraftProvider.js.map +1 -1
- package/dist/providers/StorageProvider.d.ts +1 -1
- package/dist/providers/StorageProvider.d.ts.map +1 -1
- package/dist/providers/StorageProvider.js +17 -21
- package/dist/providers/StorageProvider.js.map +1 -1
- package/dist/types/Form.js +1 -2
- package/dist/types.js +1 -2
- package/dist/utils/fetch.js +12 -17
- package/dist/utils/fetch.js.map +1 -1
- package/package.json +1 -1
- package/src/components/form/fields/Field.stories.tsx +165 -0
- package/src/components/form/fields/Field.tsx +172 -0
- package/src/components/form/fields/boolean/Boolean.stories.tsx +46 -0
- package/src/components/form/fields/boolean/Boolean.tsx +34 -0
- package/src/components/form/fields/color/Color.stories.tsx +60 -0
- package/src/components/form/fields/color/Color.tsx +43 -0
- package/src/components/form/fields/cron/Cron.stories.tsx +60 -0
- package/src/components/form/fields/cron/Cron.tsx +77 -0
- package/src/components/form/fields/long-string/LongString.stories.tsx +62 -0
- package/src/components/form/fields/long-string/LongString.tsx +35 -0
- package/src/components/form/fields/markdown/Markdown.stories.tsx +47 -0
- package/src/components/form/fields/markdown/Markdown.tsx +106 -0
- package/src/components/form/fields/number/Number.stories.tsx +74 -0
- package/src/components/form/fields/number/Number.tsx +53 -0
- package/src/components/form/fields/radio-group/RadioGroup.stories.tsx +79 -0
- package/src/components/form/fields/radio-group/RadioGroup.tsx +46 -0
- package/src/components/form/fields/radio-group/images/java-96x128.png +0 -0
- package/src/components/form/fields/radio-group/images/python-129x128.png +0 -0
- package/src/components/form/fields/radio-group/images/qore-106x128.png +0 -0
- package/src/components/form/fields/string/String.stories.tsx +70 -0
- package/src/components/form/fields/string/String.tsx +43 -0
- package/src/components/form/index.tsx +8 -0
- package/src/components/menu/Menu.stories.tsx +73 -0
- package/src/components/menu/Menu.tsx +244 -0
- package/src/contexts/FetchContext.tsx +25 -0
- package/src/contexts/ReqraftContext.tsx +9 -0
- package/src/contexts/StorageContext.tsx +33 -0
- package/src/global.d.ts +4 -0
- package/src/hooks/useFetch/useFetch.stories.tsx +123 -0
- package/src/hooks/useFetch/useFetch.tsx +71 -0
- package/src/hooks/useReqraftProperty.ts +16 -0
- package/src/hooks/useStorage/useStorage.stories.tsx +85 -0
- package/src/hooks/useStorage/useStorage.ts +43 -0
- package/src/hooks/useValidation.ts +9 -0
- package/src/index.tsx +17 -0
- package/src/providers/FetchProvider.tsx +40 -0
- package/src/providers/ReqraftProvider.tsx +52 -0
- package/src/providers/StorageProvider.tsx +85 -0
- package/src/types/Form.ts +46 -0
- package/src/types.ts +14 -0
- package/src/utils/fetch.ts +121 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { StoryObj } from '@storybook/react';
|
|
2
|
+
import { expect, fn, userEvent } from '@storybook/test';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { StoryMeta } from '../../../../types';
|
|
6
|
+
import { ColorFormField } from './Color';
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
component: ColorFormField,
|
|
10
|
+
title: 'Components/Form/Color',
|
|
11
|
+
args: {
|
|
12
|
+
onChange: fn(),
|
|
13
|
+
},
|
|
14
|
+
} as StoryMeta<typeof ColorFormField>;
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
type Story = StoryObj<typeof meta>;
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {
|
|
20
|
+
args: {
|
|
21
|
+
value: { r: 0, g: 0, b: 0, a: 1 },
|
|
22
|
+
},
|
|
23
|
+
render(args) {
|
|
24
|
+
const [value, setValue] = useState(args.value);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<ColorFormField
|
|
28
|
+
{...args}
|
|
29
|
+
value={value}
|
|
30
|
+
onChange={(value) => {
|
|
31
|
+
args.onChange?.(value);
|
|
32
|
+
setValue(value);
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
async play({ canvasElement, args }) {
|
|
38
|
+
const picker = canvasElement.querySelector('.sketch-picker');
|
|
39
|
+
const colorPanel = canvasElement.querySelector('.saturation-white');
|
|
40
|
+
const valueInput = canvasElement.querySelector('input');
|
|
41
|
+
|
|
42
|
+
await expect(picker).toBeInTheDocument();
|
|
43
|
+
await expect(colorPanel).toBeInTheDocument();
|
|
44
|
+
await expect(valueInput).toBeInTheDocument();
|
|
45
|
+
await expect(valueInput).toHaveValue('000000');
|
|
46
|
+
|
|
47
|
+
await userEvent.pointer({
|
|
48
|
+
keys: '[MouseLeft]',
|
|
49
|
+
target: colorPanel,
|
|
50
|
+
coords: { x: 0, y: 0 },
|
|
51
|
+
});
|
|
52
|
+
await expect(valueInput).toHaveValue('FFFFFF');
|
|
53
|
+
await expect(args.onChange).toHaveBeenLastCalledWith({
|
|
54
|
+
r: 255,
|
|
55
|
+
g: 255,
|
|
56
|
+
b: 255,
|
|
57
|
+
a: 1,
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { SketchPicker, SketchPickerProps } from 'react-color';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
export interface IColorFormFieldProps extends Omit<SketchPickerProps, 'onChange'> {
|
|
5
|
+
value: SketchPickerProps['color'];
|
|
6
|
+
onChange(value: IColorFormFieldProps['value']);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const StyledSketchPicker = styled(SketchPicker)`
|
|
10
|
+
background-color: transparent !important;
|
|
11
|
+
width: 100% !important;
|
|
12
|
+
max-width: 400px !important;
|
|
13
|
+
box-shadow: none !important;
|
|
14
|
+
padding: 0 !important;
|
|
15
|
+
|
|
16
|
+
> div:first-child {
|
|
17
|
+
padding-bottom: unset !important;
|
|
18
|
+
height: 100px !important;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
label {
|
|
22
|
+
color: #fff !important;
|
|
23
|
+
}
|
|
24
|
+
input {
|
|
25
|
+
width: 100% !important;
|
|
26
|
+
}
|
|
27
|
+
.flexbox-fix {
|
|
28
|
+
border: none !important;
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
export const ColorFormField = ({ value, onChange, ...rest }: IColorFormFieldProps) => {
|
|
33
|
+
return (
|
|
34
|
+
<StyledSketchPicker
|
|
35
|
+
onChange={(color) => onChange(color.rgb)}
|
|
36
|
+
color={value}
|
|
37
|
+
disableAlpha
|
|
38
|
+
{...rest}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default ColorFormField;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { StoryObj } from '@storybook/react';
|
|
2
|
+
import { expect, fn, userEvent, within } from '@storybook/test';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { StoryMeta } from '../../../../types';
|
|
5
|
+
import { CronFormField } from './Cron';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
component: CronFormField,
|
|
9
|
+
title: 'Components/Form/Cron',
|
|
10
|
+
args: {
|
|
11
|
+
wrapperProps: {
|
|
12
|
+
'aria-label': 'Cron',
|
|
13
|
+
},
|
|
14
|
+
onChange: fn(),
|
|
15
|
+
},
|
|
16
|
+
render(args) {
|
|
17
|
+
const [value, setValue] = useState(args.value);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<CronFormField
|
|
21
|
+
{...args}
|
|
22
|
+
value={value}
|
|
23
|
+
onChange={(value) => {
|
|
24
|
+
args.onChange?.(value);
|
|
25
|
+
setValue(value);
|
|
26
|
+
}}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
} as StoryMeta<typeof CronFormField>;
|
|
31
|
+
|
|
32
|
+
export default meta;
|
|
33
|
+
type Story = StoryObj<typeof meta>;
|
|
34
|
+
|
|
35
|
+
export const Default: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
value: '1 1 1 1 1',
|
|
38
|
+
inputProps: ['Minute', 'Hour', 'Day', 'Month', 'Weekday'].map((label) => ({
|
|
39
|
+
'aria-label': label,
|
|
40
|
+
})),
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async play({ args, canvasElement }) {
|
|
44
|
+
const canvas = within(canvasElement);
|
|
45
|
+
const wrapper = canvas.getByLabelText('Cron');
|
|
46
|
+
await expect(wrapper).toBeInTheDocument();
|
|
47
|
+
|
|
48
|
+
await Promise.all(
|
|
49
|
+
args.inputProps
|
|
50
|
+
.map((p) => p['aria-label'])
|
|
51
|
+
.map((label) => expect(canvas.getByLabelText(label)).toBeInTheDocument())
|
|
52
|
+
);
|
|
53
|
+
await userEvent.clear(canvas.getByLabelText('Minute'));
|
|
54
|
+
await userEvent.type(canvas.getByLabelText('Minute'), '30');
|
|
55
|
+
await expect(args.onChange).toHaveBeenLastCalledWith('30 1 1 1 1');
|
|
56
|
+
|
|
57
|
+
await userEvent.click(canvas.getByRole('button'));
|
|
58
|
+
await expect(args.onChange).toHaveBeenLastCalledWith('');
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ReqoreButton,
|
|
3
|
+
ReqoreControlGroup,
|
|
4
|
+
ReqoreInput,
|
|
5
|
+
ReqoreMessage,
|
|
6
|
+
} from '@qoretechnologies/reqore';
|
|
7
|
+
import { IReqoreControlGroupProps } from '@qoretechnologies/reqore/dist/components/ControlGroup';
|
|
8
|
+
import { IReqoreInputProps } from '@qoretechnologies/reqore/dist/components/Input';
|
|
9
|
+
import cronstrue from 'cronstrue';
|
|
10
|
+
import { useMemo } from 'react';
|
|
11
|
+
|
|
12
|
+
export interface ICronFormFieldProps {
|
|
13
|
+
value?: string;
|
|
14
|
+
onChange?(value: string): void;
|
|
15
|
+
wrapperProps?: Partial<IReqoreControlGroupProps>;
|
|
16
|
+
inputProps?: IReqoreInputProps[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const CronFormField = ({
|
|
20
|
+
onChange,
|
|
21
|
+
value,
|
|
22
|
+
wrapperProps,
|
|
23
|
+
inputProps,
|
|
24
|
+
}: ICronFormFieldProps) => {
|
|
25
|
+
const { message, isError } = useMemo(() => {
|
|
26
|
+
try {
|
|
27
|
+
return { message: cronstrue.toString(value, {}) };
|
|
28
|
+
} catch (message: any) {
|
|
29
|
+
return {
|
|
30
|
+
message,
|
|
31
|
+
isError: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}, [value]);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<ReqoreControlGroup fluid stack={false} vertical gapSize='big' {...wrapperProps}>
|
|
38
|
+
<ReqoreControlGroup fluid stack>
|
|
39
|
+
{['Minute', 'Hour', 'Day', 'Month', 'Weekday'].map((label, index) => {
|
|
40
|
+
return (
|
|
41
|
+
<ReqoreInput
|
|
42
|
+
{...inputProps[index]}
|
|
43
|
+
key={index}
|
|
44
|
+
aria-label={label}
|
|
45
|
+
placeholder={label}
|
|
46
|
+
onChange={(event) => {
|
|
47
|
+
const cronData: Record<string, string> = {};
|
|
48
|
+
[
|
|
49
|
+
cronData.minute = '',
|
|
50
|
+
cronData.hour = '',
|
|
51
|
+
cronData.day = '',
|
|
52
|
+
cronData.month = '',
|
|
53
|
+
cronData.weekday = '',
|
|
54
|
+
] = value.split(' ');
|
|
55
|
+
cronData[label.toLowerCase() as any] = event.currentTarget.value;
|
|
56
|
+
|
|
57
|
+
onChange?.(
|
|
58
|
+
`${cronData.minute} ${cronData.hour} ${cronData.day} ${cronData.month} ${cronData.weekday}`
|
|
59
|
+
);
|
|
60
|
+
}}
|
|
61
|
+
value={value.split(' ')?.[index] ?? ''}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
})}
|
|
65
|
+
|
|
66
|
+
<ReqoreButton fixed onClick={() => onChange('')} icon={'CloseLine'} />
|
|
67
|
+
</ReqoreControlGroup>
|
|
68
|
+
{value && (
|
|
69
|
+
<ReqoreMessage intent={isError ? 'danger' : 'info'} opaque={false}>
|
|
70
|
+
{message}
|
|
71
|
+
</ReqoreMessage>
|
|
72
|
+
)}
|
|
73
|
+
</ReqoreControlGroup>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default CronFormField;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { StoryObj } from '@storybook/react';
|
|
2
|
+
import { expect, fn, userEvent, within } from '@storybook/test';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { longStringText } from '../../../../../mock/fields';
|
|
6
|
+
import { StoryMeta } from '../../../../types';
|
|
7
|
+
import { LongStringFormField } from './LongString';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
component: LongStringFormField,
|
|
11
|
+
title: 'Components/Form/LongString',
|
|
12
|
+
args: {
|
|
13
|
+
onChange: fn(),
|
|
14
|
+
onClearClick: fn(),
|
|
15
|
+
'aria-label': `LongString`,
|
|
16
|
+
value: longStringText,
|
|
17
|
+
},
|
|
18
|
+
render(args) {
|
|
19
|
+
const [value, setValue] = useState(args.value);
|
|
20
|
+
return (
|
|
21
|
+
<LongStringFormField
|
|
22
|
+
{...args}
|
|
23
|
+
value={value}
|
|
24
|
+
onChange={(value) => {
|
|
25
|
+
args.onChange?.(value);
|
|
26
|
+
setValue(value);
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
},
|
|
31
|
+
} as StoryMeta<typeof LongStringFormField>;
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
type Story = StoryObj<typeof meta>;
|
|
35
|
+
|
|
36
|
+
export const Default: Story = {
|
|
37
|
+
play: async ({ canvasElement, args, step }) => {
|
|
38
|
+
const canvas = within(canvasElement);
|
|
39
|
+
const textarea = canvas.getByLabelText('LongString');
|
|
40
|
+
|
|
41
|
+
await step('Initial asserts', async () => {
|
|
42
|
+
await expect(textarea).toBeInTheDocument();
|
|
43
|
+
await expect(textarea).toHaveValue(args.value);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await step('Clear Longstring field', async () => {
|
|
47
|
+
await userEvent.click(textarea.nextElementSibling);
|
|
48
|
+
await expect(textarea).toHaveValue('');
|
|
49
|
+
await expect(args.onChange).toHaveBeenLastCalledWith('');
|
|
50
|
+
await expect(args.onClearClick).toHaveBeenCalledOnce();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await step('Type in the input', async () => {
|
|
54
|
+
await userEvent.type(textarea, 'Qore');
|
|
55
|
+
await expect(textarea).toHaveValue('Qore');
|
|
56
|
+
await expect(args.onChange).toHaveBeenLastCalledWith('Qore');
|
|
57
|
+
|
|
58
|
+
await userEvent.clear(textarea);
|
|
59
|
+
await userEvent.type(textarea, args.value, { delay: null });
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ReqoreTextarea } from '@qoretechnologies/reqore';
|
|
2
|
+
import { IReqoreTextareaProps } from '@qoretechnologies/reqore/dist/components/Textarea';
|
|
3
|
+
import { TFormFieldValueType } from '../../../../types/Form';
|
|
4
|
+
|
|
5
|
+
export interface ILongStringFormFieldProps extends Omit<IReqoreTextareaProps, 'onChange'> {
|
|
6
|
+
onChange?: (
|
|
7
|
+
value?: TFormFieldValueType<'string'>,
|
|
8
|
+
event?: React.FormEvent<HTMLTextAreaElement>
|
|
9
|
+
) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const LongStringFormField = ({
|
|
13
|
+
onChange,
|
|
14
|
+
onClearClick,
|
|
15
|
+
...rest
|
|
16
|
+
}: ILongStringFormFieldProps) => {
|
|
17
|
+
return (
|
|
18
|
+
<ReqoreTextarea
|
|
19
|
+
scaleWithContent
|
|
20
|
+
fluid
|
|
21
|
+
wrapperStyle={{
|
|
22
|
+
width: '100%',
|
|
23
|
+
}}
|
|
24
|
+
onClearClick={() => {
|
|
25
|
+
onClearClick?.();
|
|
26
|
+
onChange?.('');
|
|
27
|
+
}}
|
|
28
|
+
onChange={(event) => onChange(event.currentTarget.value, event)}
|
|
29
|
+
rows={4}
|
|
30
|
+
{...rest}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default LongStringFormField;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { StoryObj } from '@storybook/react';
|
|
2
|
+
import { expect, fn, within } from '@storybook/test';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { markdown } from '../../../../../mock/fields';
|
|
6
|
+
import { StoryMeta } from '../../../../types';
|
|
7
|
+
import { MarkdownFormField } from './Markdown';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
component: MarkdownFormField,
|
|
11
|
+
title: 'Components/Form/Markdown',
|
|
12
|
+
args: {
|
|
13
|
+
onChange: fn(),
|
|
14
|
+
value: markdown,
|
|
15
|
+
},
|
|
16
|
+
render(args) {
|
|
17
|
+
const [value, setValue] = useState(args.value);
|
|
18
|
+
return (
|
|
19
|
+
<MarkdownFormField
|
|
20
|
+
{...args}
|
|
21
|
+
value={value}
|
|
22
|
+
onChange={(value) => {
|
|
23
|
+
args.onChange?.(value);
|
|
24
|
+
setValue(value);
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
},
|
|
29
|
+
} as StoryMeta<typeof MarkdownFormField>;
|
|
30
|
+
|
|
31
|
+
export default meta;
|
|
32
|
+
type Story = StoryObj<typeof meta>;
|
|
33
|
+
|
|
34
|
+
export const Default: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
'aria-label': 'MarkdownEditor',
|
|
37
|
+
},
|
|
38
|
+
async play({ canvasElement, args }) {
|
|
39
|
+
const canvas = within(canvasElement);
|
|
40
|
+
const editor = canvas.getByLabelText('MarkdownEditor');
|
|
41
|
+
const preview = canvas.getByLabelText('Preview');
|
|
42
|
+
|
|
43
|
+
await expect(editor).toBeInTheDocument();
|
|
44
|
+
await expect(preview).toBeInTheDocument();
|
|
45
|
+
await expect(editor).toHaveValue(args.value);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ReqoreColumn, ReqoreColumns, ReqoreMessage } from '@qoretechnologies/reqore';
|
|
2
|
+
import { IReqoreMessageProps } from '@qoretechnologies/reqore/dist/components/Message';
|
|
3
|
+
import { ComponentProps } from 'react';
|
|
4
|
+
import ReactMarkdown from 'react-markdown';
|
|
5
|
+
import styled from 'styled-components';
|
|
6
|
+
import LongStringFormField, { ILongStringFormFieldProps } from '../long-string/LongString';
|
|
7
|
+
|
|
8
|
+
export interface IMarkdownFormFieldProps extends ILongStringFormFieldProps {
|
|
9
|
+
markdownPreviewProps?: Partial<ComponentProps<typeof ReactMarkdown>>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const StyledWrapper = styled(ReqoreColumns)<ComponentProps<typeof ReqoreColumns>>`
|
|
13
|
+
width: 100%;
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const StyledLongStringWrapper = styled(ReqoreColumn)`
|
|
17
|
+
.reqore-control-wrapper {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const StyledMarkdown = styled(ReactMarkdown)<ComponentProps<typeof ReactMarkdown>>`
|
|
24
|
+
p {
|
|
25
|
+
font-size: 14px;
|
|
26
|
+
|
|
27
|
+
&:first-child {
|
|
28
|
+
margin-top: 0;
|
|
29
|
+
}
|
|
30
|
+
&:last-child {
|
|
31
|
+
margin-bottom: 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
h1,
|
|
35
|
+
h2,
|
|
36
|
+
p,
|
|
37
|
+
i,
|
|
38
|
+
a {
|
|
39
|
+
color: rgba(255, 255, 255, 0.84);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
h1 {
|
|
43
|
+
text-align: left;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
h2 {
|
|
47
|
+
font-weight: 700;
|
|
48
|
+
padding: 0;
|
|
49
|
+
text-align: left;
|
|
50
|
+
line-height: 34.5px;
|
|
51
|
+
letter-spacing: -0.45px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
p,
|
|
55
|
+
i,
|
|
56
|
+
a {
|
|
57
|
+
letter-spacing: -0.03px;
|
|
58
|
+
line-height: 1.58;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
a {
|
|
62
|
+
text-decoration: underline;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
blockquote {
|
|
66
|
+
font-style: italic;
|
|
67
|
+
letter-spacing: -0.36px;
|
|
68
|
+
line-height: 44.4px;
|
|
69
|
+
overflow-wrap: break-word;
|
|
70
|
+
color: rgba(255, 255, 255, 0.68);
|
|
71
|
+
padding: 0 0 0 50px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
code {
|
|
75
|
+
background: rgba(255, 255, 255, 0.05);
|
|
76
|
+
border-radius: 2px;
|
|
77
|
+
padding: 34px 6px;
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
const StyledPreviewColumn = styled(ReqoreColumn)`
|
|
82
|
+
width: 100%;
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
const StyledPreviewWrapper = styled(ReqoreMessage)<IReqoreMessageProps>`
|
|
86
|
+
& div div {
|
|
87
|
+
justify-content: start;
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
export const MarkdownFormField = ({ markdownPreviewProps, ...rest }: IMarkdownFormFieldProps) => {
|
|
92
|
+
return (
|
|
93
|
+
<StyledWrapper columnsGap='10px'>
|
|
94
|
+
<StyledLongStringWrapper flexFlow='column'>
|
|
95
|
+
<LongStringFormField {...rest} />
|
|
96
|
+
</StyledLongStringWrapper>
|
|
97
|
+
<StyledPreviewColumn>
|
|
98
|
+
<StyledPreviewWrapper size='small' aria-label='Preview' flat fluid>
|
|
99
|
+
<StyledMarkdown {...markdownPreviewProps}>{rest.value ?? ''}</StyledMarkdown>
|
|
100
|
+
</StyledPreviewWrapper>
|
|
101
|
+
</StyledPreviewColumn>
|
|
102
|
+
</StyledWrapper>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export default MarkdownFormField;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { StoryObj } from '@storybook/react';
|
|
2
|
+
import { expect, fn, userEvent, within } from '@storybook/test';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { StoryMeta } from '../../../../types';
|
|
5
|
+
import { NumberFormField } from './Number';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
component: NumberFormField,
|
|
9
|
+
title: 'Components/Form/Number',
|
|
10
|
+
args: {
|
|
11
|
+
'aria-label': 'Number',
|
|
12
|
+
onChange: fn(),
|
|
13
|
+
},
|
|
14
|
+
render(args) {
|
|
15
|
+
const [value, setValue] = useState(args.value);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<NumberFormField
|
|
19
|
+
{...args}
|
|
20
|
+
value={value}
|
|
21
|
+
onChange={(value) => {
|
|
22
|
+
args.onChange?.(value);
|
|
23
|
+
setValue(value);
|
|
24
|
+
}}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
},
|
|
28
|
+
} as StoryMeta<typeof NumberFormField>;
|
|
29
|
+
|
|
30
|
+
export default meta;
|
|
31
|
+
type Story = StoryObj<typeof meta>;
|
|
32
|
+
|
|
33
|
+
export const Default: Story = {
|
|
34
|
+
args: {
|
|
35
|
+
value: 0,
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
async play({ args, canvasElement }) {
|
|
39
|
+
const canvas = within(canvasElement);
|
|
40
|
+
const input = canvas.getByLabelText('Number');
|
|
41
|
+
|
|
42
|
+
await expect(input).toBeInTheDocument();
|
|
43
|
+
await expect(input).toHaveValue(0);
|
|
44
|
+
await expect(input).toHaveAttribute('type', 'number');
|
|
45
|
+
|
|
46
|
+
await userEvent.type(input, '10');
|
|
47
|
+
await expect(input).toHaveValue(10);
|
|
48
|
+
await expect(args.onChange).toHaveBeenLastCalledWith(10);
|
|
49
|
+
|
|
50
|
+
await userEvent.click(input.nextElementSibling);
|
|
51
|
+
await expect(args.onChange).toHaveBeenLastCalledWith(undefined);
|
|
52
|
+
|
|
53
|
+
await userEvent.type(input, '10.5');
|
|
54
|
+
await expect(input).toHaveValue(10);
|
|
55
|
+
await expect(args.onChange).toHaveBeenLastCalledWith(10);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const Float: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
value: 0,
|
|
62
|
+
type: 'float',
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async play({ args, canvasElement }) {
|
|
66
|
+
const canvas = within(canvasElement);
|
|
67
|
+
const input = canvas.getByLabelText('Number');
|
|
68
|
+
|
|
69
|
+
await userEvent.clear(input);
|
|
70
|
+
await userEvent.type(input, '10.9');
|
|
71
|
+
await expect(input).toHaveValue(10.9);
|
|
72
|
+
await expect(args.onChange).toHaveBeenLastCalledWith(10.9);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ReqoreInput } from '@qoretechnologies/reqore';
|
|
2
|
+
import { IReqoreInputProps } from '@qoretechnologies/reqore/dist/components/Input';
|
|
3
|
+
import { ChangeEvent, ChangeEventHandler } from 'react';
|
|
4
|
+
|
|
5
|
+
export interface INumberFormFieldProps
|
|
6
|
+
extends Omit<IReqoreInputProps, 'value' | 'onChange' | 'type'> {
|
|
7
|
+
value?: number;
|
|
8
|
+
onChange?(value: number): void;
|
|
9
|
+
type?: 'int' | 'float';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const NumberFormField = ({
|
|
13
|
+
onChange,
|
|
14
|
+
autoFocus,
|
|
15
|
+
type = 'int',
|
|
16
|
+
value,
|
|
17
|
+
...rest
|
|
18
|
+
}: INumberFormFieldProps) => {
|
|
19
|
+
const handleInputChange: ChangeEventHandler<HTMLInputElement> = (
|
|
20
|
+
event: ChangeEvent<HTMLInputElement>
|
|
21
|
+
): void => {
|
|
22
|
+
const value = type === 'int' ? parseInt(event.target.value) : parseFloat(event.target.value);
|
|
23
|
+
onChange?.(value ?? undefined);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const handleResetClick = (): void => {
|
|
27
|
+
onChange(undefined);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<ReqoreInput
|
|
32
|
+
wrapperStyle={{
|
|
33
|
+
width: '100%',
|
|
34
|
+
}}
|
|
35
|
+
value={value ?? ''}
|
|
36
|
+
onChange={handleInputChange}
|
|
37
|
+
type='number'
|
|
38
|
+
onClearClick={handleResetClick}
|
|
39
|
+
focusRules={
|
|
40
|
+
autoFocus ?
|
|
41
|
+
{
|
|
42
|
+
type: 'auto',
|
|
43
|
+
viewportOnly: true,
|
|
44
|
+
}
|
|
45
|
+
: undefined
|
|
46
|
+
}
|
|
47
|
+
step={type === 'int' ? 1 : 0.1}
|
|
48
|
+
{...rest}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default NumberFormField;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { StoryObj } from '@storybook/react';
|
|
2
|
+
import { expect, fn, userEvent, within } from '@storybook/test';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { StoryMeta } from '../../../../types';
|
|
6
|
+
import { RadioGroupFormField } from './RadioGroup';
|
|
7
|
+
|
|
8
|
+
import java from './images/java-96x128.png';
|
|
9
|
+
import python from './images/python-129x128.png';
|
|
10
|
+
import qore from './images/qore-106x128.png';
|
|
11
|
+
|
|
12
|
+
const meta = {
|
|
13
|
+
component: RadioGroupFormField,
|
|
14
|
+
title: 'Components/Form/Radio',
|
|
15
|
+
args: {
|
|
16
|
+
onChange: fn(),
|
|
17
|
+
},
|
|
18
|
+
render(args) {
|
|
19
|
+
const [value, setValue] = useState(args.value);
|
|
20
|
+
return (
|
|
21
|
+
<RadioGroupFormField
|
|
22
|
+
{...args}
|
|
23
|
+
value={value}
|
|
24
|
+
onChange={(value) => {
|
|
25
|
+
args.onChange(value);
|
|
26
|
+
setValue(value);
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
},
|
|
31
|
+
} as StoryMeta<typeof RadioGroupFormField>;
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
type Story = StoryObj<typeof meta>;
|
|
35
|
+
|
|
36
|
+
export const Default: Story = {
|
|
37
|
+
args: {
|
|
38
|
+
value: 'Qore',
|
|
39
|
+
items: [
|
|
40
|
+
{ label: 'Qore', value: 'Qore', 'aria-label': 'Qore' },
|
|
41
|
+
{ label: 'Java', value: 'Java', 'aria-label': 'Java' },
|
|
42
|
+
{ label: 'Python', value: 'Python', 'aria-label': 'Python' },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
async play({ canvasElement, args }) {
|
|
46
|
+
const canvas = within(canvasElement);
|
|
47
|
+
const java = canvas.getByLabelText('Java');
|
|
48
|
+
await userEvent.click(java);
|
|
49
|
+
await expect(args.onChange).toHaveBeenLastCalledWith('Java');
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const WithImages: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
value: 'Qore',
|
|
56
|
+
items: [
|
|
57
|
+
{ label: 'Qore', value: 'Qore', image: qore },
|
|
58
|
+
{ label: 'Java', value: 'Java', image: java },
|
|
59
|
+
{ label: 'Python', value: 'Python', image: python },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
async play({ canvasElement }) {
|
|
63
|
+
await expect(canvasElement.querySelector(`img[src="${qore}"]`)).toBeInTheDocument();
|
|
64
|
+
await expect(canvasElement.querySelector(`img[src="${java}"]`)).toBeInTheDocument();
|
|
65
|
+
await expect(canvasElement.querySelector(`img[src="${python}"]`)).toBeInTheDocument();
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const Disabled: Story = {
|
|
70
|
+
args: { ...Default.args, disabled: true },
|
|
71
|
+
async play({ canvasElement, args }) {
|
|
72
|
+
const canvas = within(canvasElement);
|
|
73
|
+
const java = canvas.getByLabelText('Java');
|
|
74
|
+
|
|
75
|
+
// expect rejection caused by clicking an element with pointer-events:none
|
|
76
|
+
await expect(() => userEvent.click(java)).rejects.toBeTruthy();
|
|
77
|
+
await expect(args.onChange).not.toHaveBeenCalled();
|
|
78
|
+
},
|
|
79
|
+
};
|