@qoretechnologies/reqraft 0.3.2 → 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 -2
- 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,46 @@
|
|
|
1
|
+
import { ReqoreRadioGroup } from '@qoretechnologies/reqore';
|
|
2
|
+
import { IReqoreRadioGroupProps } from '@qoretechnologies/reqore/dist/components/RadioGroup';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
|
|
5
|
+
export interface IRadioGroupFormFieldProps
|
|
6
|
+
extends Omit<IReqoreRadioGroupProps, 'onChange' | 'onSelectClick' | 'selected'> {
|
|
7
|
+
value: string;
|
|
8
|
+
onChange(value: string): void;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const RadioGroupFormField = ({
|
|
13
|
+
items: _items,
|
|
14
|
+
disabled,
|
|
15
|
+
onChange,
|
|
16
|
+
value,
|
|
17
|
+
...rest
|
|
18
|
+
}: IRadioGroupFormFieldProps) => {
|
|
19
|
+
const items: IReqoreRadioGroupProps['items'] = useMemo(
|
|
20
|
+
() =>
|
|
21
|
+
_items.map((item) => ({
|
|
22
|
+
margin: 'right',
|
|
23
|
+
labelEffect: {
|
|
24
|
+
spaced: 1,
|
|
25
|
+
weight: 'bold',
|
|
26
|
+
uppercase: true,
|
|
27
|
+
textSize: 'small',
|
|
28
|
+
},
|
|
29
|
+
...item,
|
|
30
|
+
disabled: disabled || item.disabled,
|
|
31
|
+
})),
|
|
32
|
+
[_items]
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<ReqoreRadioGroup
|
|
37
|
+
items={items}
|
|
38
|
+
disabled={disabled}
|
|
39
|
+
onSelectClick={onChange}
|
|
40
|
+
selected={value}
|
|
41
|
+
{...rest}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default RadioGroupFormField;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,70 @@
|
|
|
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 { StringFormField } from './String';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
component: StringFormField,
|
|
9
|
+
title: 'Components/Form/String',
|
|
10
|
+
args: {
|
|
11
|
+
onChange: fn(),
|
|
12
|
+
onClearClick: fn(),
|
|
13
|
+
'aria-label': 'Name',
|
|
14
|
+
},
|
|
15
|
+
render(args) {
|
|
16
|
+
const [value, setValue] = useState(args.value);
|
|
17
|
+
return (
|
|
18
|
+
<StringFormField
|
|
19
|
+
{...args}
|
|
20
|
+
value={value}
|
|
21
|
+
onChange={(value, event) => {
|
|
22
|
+
args.onChange?.(value, event);
|
|
23
|
+
setValue(value);
|
|
24
|
+
}}
|
|
25
|
+
onClearClick={() => {
|
|
26
|
+
args.onClearClick?.();
|
|
27
|
+
setValue('');
|
|
28
|
+
}}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
},
|
|
32
|
+
} as StoryMeta<typeof StringFormField>;
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
type Story = StoryObj<typeof meta>;
|
|
36
|
+
|
|
37
|
+
export const Default: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
value: 'Qore',
|
|
40
|
+
},
|
|
41
|
+
play: async ({ canvasElement, args }) => {
|
|
42
|
+
const canvas = within(canvasElement);
|
|
43
|
+
const input = canvas.getByLabelText('Name');
|
|
44
|
+
|
|
45
|
+
await expect(input).toBeInTheDocument();
|
|
46
|
+
await expect(input).toHaveValue('Qore');
|
|
47
|
+
|
|
48
|
+
await userEvent.click(input.nextElementSibling);
|
|
49
|
+
await expect(args.onClearClick).toHaveBeenCalledOnce();
|
|
50
|
+
await expect(input).toHaveValue('');
|
|
51
|
+
|
|
52
|
+
await userEvent.type(input, 'Java');
|
|
53
|
+
await expect(input).toHaveValue('Java');
|
|
54
|
+
await expect(args.onChange).toHaveBeenLastCalledWith('Java', expect.objectContaining({}));
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const Sensitive: Story = {
|
|
59
|
+
args: {
|
|
60
|
+
sensitive: true,
|
|
61
|
+
value: 'password',
|
|
62
|
+
},
|
|
63
|
+
play: async ({ canvasElement }) => {
|
|
64
|
+
const canvas = within(canvasElement);
|
|
65
|
+
const input = canvas.getByLabelText('Name');
|
|
66
|
+
await expect(input).toBeInTheDocument();
|
|
67
|
+
await expect(input).toHaveAttribute('type', 'password');
|
|
68
|
+
await expect(input).toHaveValue('password');
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ReqoreInput } from '@qoretechnologies/reqore';
|
|
2
|
+
import { IReqoreInputProps } from '@qoretechnologies/reqore/dist/components/Input';
|
|
3
|
+
import { ChangeEvent } from 'react';
|
|
4
|
+
|
|
5
|
+
import { TFormFieldValueType } from '../../../../types/Form';
|
|
6
|
+
|
|
7
|
+
export interface IStringFormFieldProps extends Omit<IReqoreInputProps, 'onChange' | 'value'> {
|
|
8
|
+
sensitive?: boolean;
|
|
9
|
+
value?: TFormFieldValueType<'string'>;
|
|
10
|
+
onChange?: (value?: TFormFieldValueType<'string'>, event?: ChangeEvent<HTMLInputElement>) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const StringFormField = ({ onChange, sensitive, ...rest }: IStringFormFieldProps) => {
|
|
14
|
+
// When input value changes
|
|
15
|
+
const handleChange = (event: ChangeEvent<HTMLInputElement>): void => {
|
|
16
|
+
onChange(event.target.value?.toString(), event);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Clear the input on reset click
|
|
20
|
+
const handleClearClick = (): void => {
|
|
21
|
+
onChange('');
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<ReqoreInput
|
|
26
|
+
wrapperStyle={{
|
|
27
|
+
width: '100%',
|
|
28
|
+
}}
|
|
29
|
+
onFocus={(event) => {
|
|
30
|
+
event.stopPropagation();
|
|
31
|
+
rest?.onFocus?.(event);
|
|
32
|
+
}}
|
|
33
|
+
onClick={(event) => {
|
|
34
|
+
event.stopPropagation();
|
|
35
|
+
rest?.onClick?.(event);
|
|
36
|
+
}}
|
|
37
|
+
onChange={handleChange}
|
|
38
|
+
type={sensitive ? 'password' : 'text'}
|
|
39
|
+
onClearClick={handleClearClick}
|
|
40
|
+
{...rest}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './fields/Field';
|
|
2
|
+
export * from './fields/boolean/Boolean';
|
|
3
|
+
export * from './fields/color/Color';
|
|
4
|
+
export * from './fields/long-string/LongString';
|
|
5
|
+
export * from './fields/markdown/Markdown';
|
|
6
|
+
export * from './fields/number/Number';
|
|
7
|
+
export * from './fields/radio-group/RadioGroup';
|
|
8
|
+
export * from './fields/string/String';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { StoryObj } from '@storybook/react';
|
|
2
|
+
import { expect, fireEvent, fn, waitFor } from '@storybook/test';
|
|
3
|
+
import { storiesStorageMock, storiesStorageMockEmpty } from '../../../__tests__/ mock';
|
|
4
|
+
import { testsWaitForText } from '../../../__tests__/utils';
|
|
5
|
+
import menu from '../../../mock/menu';
|
|
6
|
+
import { StoryMeta } from '../../types';
|
|
7
|
+
import { ReqraftMenu, TReqraftMenu } from './Menu';
|
|
8
|
+
|
|
9
|
+
const typedMenu = menu as TReqraftMenu;
|
|
10
|
+
|
|
11
|
+
const meta = {
|
|
12
|
+
component: ReqraftMenu,
|
|
13
|
+
title: 'Components/Menu',
|
|
14
|
+
render: (props) => <ReqraftMenu {...props} />,
|
|
15
|
+
parameters: {
|
|
16
|
+
mockData: [...storiesStorageMockEmpty],
|
|
17
|
+
},
|
|
18
|
+
} as StoryMeta<typeof ReqraftMenu>;
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
export type Story = StoryObj<typeof meta>;
|
|
22
|
+
|
|
23
|
+
export const Basic: Story = {
|
|
24
|
+
args: {
|
|
25
|
+
menu: typedMenu,
|
|
26
|
+
},
|
|
27
|
+
play: async () => {
|
|
28
|
+
await testsWaitForText('Developer Portal');
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export const ActivePath: Story = {
|
|
32
|
+
args: {
|
|
33
|
+
path: '/Interfaces/mapper',
|
|
34
|
+
menu: typedMenu,
|
|
35
|
+
},
|
|
36
|
+
play: async () => {
|
|
37
|
+
await testsWaitForText('Developer Portal');
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const WithDefaultQuery: Story = {
|
|
42
|
+
args: {
|
|
43
|
+
menu: typedMenu,
|
|
44
|
+
defaultQuery: 'mapper',
|
|
45
|
+
},
|
|
46
|
+
play: async () => {
|
|
47
|
+
await testsWaitForText('Developer Portal');
|
|
48
|
+
await expect(document.querySelector('.reqore-input')).toHaveValue('mapper');
|
|
49
|
+
await expect(document.querySelectorAll('.reqore-menu-item')).toHaveLength(2);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const Filtered: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
menu: typedMenu,
|
|
56
|
+
onQueryChange: fn(),
|
|
57
|
+
},
|
|
58
|
+
play: async () => {
|
|
59
|
+
await testsWaitForText('Developer Portal');
|
|
60
|
+
await fireEvent.change(document.querySelector('.reqore-input'), { target: { value: 'step' } });
|
|
61
|
+
|
|
62
|
+
await waitFor(() => expect(document.querySelectorAll('.reqore-menu-item')).toHaveLength(2), {
|
|
63
|
+
timeout: 1000,
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const WidthFromStorage: Story = {
|
|
69
|
+
...ActivePath,
|
|
70
|
+
parameters: {
|
|
71
|
+
mockData: [...storiesStorageMock],
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ReqoreButton,
|
|
3
|
+
ReqoreControlGroup,
|
|
4
|
+
ReqoreInput,
|
|
5
|
+
ReqoreMenuDivider,
|
|
6
|
+
ReqoreMenuItem,
|
|
7
|
+
ReqoreMenuSection,
|
|
8
|
+
} from '@qoretechnologies/reqore';
|
|
9
|
+
import ReqoreMenu, { IReqoreMenuProps } from '@qoretechnologies/reqore/dist/components/Menu';
|
|
10
|
+
import { IReqoreMenuDividerProps } from '@qoretechnologies/reqore/dist/components/Menu/divider';
|
|
11
|
+
import { IReqoreMenuItemProps } from '@qoretechnologies/reqore/dist/components/Menu/item';
|
|
12
|
+
import { map, reduce, size } from 'lodash';
|
|
13
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
14
|
+
import { useReqraftStorage } from '../../hooks/useStorage/useStorage';
|
|
15
|
+
|
|
16
|
+
export interface IReqraftMenuItem extends IReqoreMenuItemProps {
|
|
17
|
+
submenu?: TReqraftMenuItem[];
|
|
18
|
+
activePaths?: string[];
|
|
19
|
+
to?: string;
|
|
20
|
+
href?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type TReqraftMenuItem = IReqraftMenuItem | ({ divider: true } & IReqoreMenuDividerProps);
|
|
24
|
+
export type TReqraftMenu = TReqraftMenuItem[];
|
|
25
|
+
|
|
26
|
+
export interface IReqraftMenuProps extends Partial<Omit<IReqoreMenuProps, 'resizable'>> {
|
|
27
|
+
hidden?: boolean;
|
|
28
|
+
onHideClick?: () => void;
|
|
29
|
+
|
|
30
|
+
defaultQuery?: string;
|
|
31
|
+
onQueryChange?: (query: string) => void;
|
|
32
|
+
|
|
33
|
+
menu: TReqraftMenu;
|
|
34
|
+
inputFocusShortcut?: string;
|
|
35
|
+
path?: string;
|
|
36
|
+
|
|
37
|
+
resizable?: boolean;
|
|
38
|
+
onResizeChange?: (width: number) => void;
|
|
39
|
+
defaultWidth?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const ReqraftMenuItem = ({
|
|
43
|
+
path,
|
|
44
|
+
isCollapsed,
|
|
45
|
+
...props
|
|
46
|
+
}: TReqraftMenuItem & { path?: string; isCollapsed?: boolean }) => {
|
|
47
|
+
if ('divider' in props) {
|
|
48
|
+
return <ReqoreMenuDivider />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const isActive = useMemo(
|
|
52
|
+
() =>
|
|
53
|
+
props.activePaths?.some(
|
|
54
|
+
(activePath) => activePath === path || path?.startsWith(`${activePath}/`)
|
|
55
|
+
),
|
|
56
|
+
[path]
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (props.submenu) {
|
|
60
|
+
const { submenu, ...menuData } = props;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<ReqoreMenuSection
|
|
64
|
+
label={menuData.label}
|
|
65
|
+
icon={menuData.icon}
|
|
66
|
+
isCollapsed={isCollapsed && !isActive}
|
|
67
|
+
verticalPadding='tiny'
|
|
68
|
+
{...menuData}
|
|
69
|
+
>
|
|
70
|
+
{map(submenu, (submenuData, submenuId) => (
|
|
71
|
+
<ReqraftMenuItem key={submenuId} {...submenuData} path={path} />
|
|
72
|
+
))}
|
|
73
|
+
</ReqoreMenuSection>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<ReqoreMenuItem
|
|
79
|
+
customTheme={{ main: '#050505' }}
|
|
80
|
+
effect={
|
|
81
|
+
isActive
|
|
82
|
+
? {
|
|
83
|
+
gradient: {
|
|
84
|
+
colors: {
|
|
85
|
+
0: 'info:darken:5:0.4',
|
|
86
|
+
40: '#181818',
|
|
87
|
+
100: '#181818',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
: undefined
|
|
92
|
+
}
|
|
93
|
+
leftIconColor={isActive ? 'info:lighten:10' : undefined}
|
|
94
|
+
verticalPadding='tiny'
|
|
95
|
+
{...props}
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const ReqraftMenu = ({
|
|
101
|
+
defaultQuery,
|
|
102
|
+
defaultWidth = 250,
|
|
103
|
+
inputFocusShortcut = '/',
|
|
104
|
+
hidden,
|
|
105
|
+
menu,
|
|
106
|
+
onQueryChange,
|
|
107
|
+
onResizeChange,
|
|
108
|
+
onHideClick,
|
|
109
|
+
resizable,
|
|
110
|
+
path,
|
|
111
|
+
...rest
|
|
112
|
+
}: IReqraftMenuProps) => {
|
|
113
|
+
const [query, setQuery] = useState<string>(defaultQuery);
|
|
114
|
+
|
|
115
|
+
const [isSidebarOpen, update] = useReqraftStorage<boolean>('sidebar-open', true, false);
|
|
116
|
+
const [sidebarSize, updateSidebarSize] = useReqraftStorage<number>(
|
|
117
|
+
'sidebar-size',
|
|
118
|
+
defaultWidth,
|
|
119
|
+
false
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (defaultQuery) {
|
|
124
|
+
setQuery(defaultQuery);
|
|
125
|
+
}
|
|
126
|
+
}, [defaultQuery]);
|
|
127
|
+
|
|
128
|
+
const handleQueryChange = (newQuery: string) => {
|
|
129
|
+
setQuery(newQuery);
|
|
130
|
+
onQueryChange?.(newQuery);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const handleWidthChange = (newWidth: number) => {
|
|
134
|
+
updateSidebarSize(newWidth);
|
|
135
|
+
onResizeChange?.(newWidth);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const filteredMenu: TReqraftMenu = useMemo<TReqraftMenu>(() => {
|
|
139
|
+
if (!query) {
|
|
140
|
+
return menu;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const filterItems = (items: TReqraftMenu): TReqraftMenu => {
|
|
144
|
+
return reduce(
|
|
145
|
+
items,
|
|
146
|
+
(acc, item) => {
|
|
147
|
+
if ('divider' in item) {
|
|
148
|
+
acc.push(item);
|
|
149
|
+
return acc;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (item.submenu) {
|
|
153
|
+
const submenu = filterItems(item.submenu);
|
|
154
|
+
const hasChildMatch = size(submenu);
|
|
155
|
+
|
|
156
|
+
if (hasChildMatch) {
|
|
157
|
+
acc.push({
|
|
158
|
+
...item,
|
|
159
|
+
submenu,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return acc;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (item.label.toString().toLowerCase().includes(query.toLowerCase())) {
|
|
167
|
+
acc.push(item);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return acc;
|
|
171
|
+
},
|
|
172
|
+
[]
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return filterItems(menu);
|
|
177
|
+
}, [menu, query]);
|
|
178
|
+
|
|
179
|
+
if (hidden) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<ReqoreMenu
|
|
185
|
+
width='250px'
|
|
186
|
+
minimal
|
|
187
|
+
position='left'
|
|
188
|
+
resizable={{
|
|
189
|
+
enable: { right: resizable, left: false },
|
|
190
|
+
minWidth: '250px',
|
|
191
|
+
maxWidth: '350px',
|
|
192
|
+
onResizeStop: (_e, _direction, _ref, d) => {
|
|
193
|
+
handleWidthChange(sidebarSize + d.width);
|
|
194
|
+
},
|
|
195
|
+
size: {
|
|
196
|
+
width: `${sidebarSize}px`,
|
|
197
|
+
height: '100%',
|
|
198
|
+
},
|
|
199
|
+
}}
|
|
200
|
+
rounded={false}
|
|
201
|
+
customTheme={{ main: '#181818' }}
|
|
202
|
+
{...rest}
|
|
203
|
+
>
|
|
204
|
+
<ReqoreControlGroup>
|
|
205
|
+
<ReqoreInput
|
|
206
|
+
icon='Search2Line'
|
|
207
|
+
minimal={false}
|
|
208
|
+
flat={false}
|
|
209
|
+
placeholder={`Filter menu "${inputFocusShortcut}"`}
|
|
210
|
+
intent={query ? 'info' : 'muted'}
|
|
211
|
+
leftIconProps={{ size: 'small' }}
|
|
212
|
+
iconColor={query ? 'info' : 'muted'}
|
|
213
|
+
pill
|
|
214
|
+
value={query}
|
|
215
|
+
onClearClick={() => handleQueryChange('')}
|
|
216
|
+
onChange={(e: any) => handleQueryChange(e.target.value)}
|
|
217
|
+
focusRules={{
|
|
218
|
+
shortcut: inputFocusShortcut,
|
|
219
|
+
type: 'keypress',
|
|
220
|
+
clearOnFocus: true,
|
|
221
|
+
doNotInsertShortcut: true,
|
|
222
|
+
}}
|
|
223
|
+
/>
|
|
224
|
+
<ReqoreButton
|
|
225
|
+
icon='SideBarLine'
|
|
226
|
+
fixed
|
|
227
|
+
minimal={false}
|
|
228
|
+
onClick={() => {
|
|
229
|
+
update(!isSidebarOpen);
|
|
230
|
+
onHideClick?.();
|
|
231
|
+
}}
|
|
232
|
+
/>
|
|
233
|
+
</ReqoreControlGroup>
|
|
234
|
+
{map(filteredMenu, (menuData, menuId) => (
|
|
235
|
+
<ReqraftMenuItem
|
|
236
|
+
key={menuId}
|
|
237
|
+
{...menuData}
|
|
238
|
+
path={path}
|
|
239
|
+
isCollapsed={!query && !!(menuData as IReqraftMenuItem).submenu}
|
|
240
|
+
/>
|
|
241
|
+
))}
|
|
242
|
+
</ReqoreMenu>
|
|
243
|
+
);
|
|
244
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createContext } from 'use-context-selector';
|
|
2
|
+
import { IReqraftFetchResponse, IReqraftQueryConfig } from '../utils/fetch';
|
|
3
|
+
|
|
4
|
+
export type TReqraftContextQueryConfig = Omit<IReqraftQueryConfig, 'method'>;
|
|
5
|
+
export interface IReqraftFetchContext {
|
|
6
|
+
get: <T>(config?: TReqraftContextQueryConfig) => Promise<IReqraftFetchResponse<T>>;
|
|
7
|
+
post: <T>(config?: TReqraftContextQueryConfig) => Promise<IReqraftFetchResponse<T>>;
|
|
8
|
+
put: <T>(config?: TReqraftContextQueryConfig) => Promise<IReqraftFetchResponse<T>>;
|
|
9
|
+
del: <T>(config?: TReqraftContextQueryConfig) => Promise<IReqraftFetchResponse<T>>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const FetchContext = createContext<IReqraftFetchContext>({
|
|
13
|
+
get: async () => {
|
|
14
|
+
throw new Error('FetchContext not implemented');
|
|
15
|
+
},
|
|
16
|
+
post: async () => {
|
|
17
|
+
throw new Error('FetchContext not implemented');
|
|
18
|
+
},
|
|
19
|
+
put: async () => {
|
|
20
|
+
throw new Error('FetchContext not implemented');
|
|
21
|
+
},
|
|
22
|
+
del: async () => {
|
|
23
|
+
throw new Error('FetchContext not implemented');
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Get } from 'type-fest';
|
|
2
|
+
import { createContext } from 'use-context-selector';
|
|
3
|
+
import { TReqraftStorageValue } from '../hooks/useStorage/useStorage';
|
|
4
|
+
|
|
5
|
+
export type TReqraftStorage = Record<string, any>;
|
|
6
|
+
|
|
7
|
+
export interface IReqraftStorageContext {
|
|
8
|
+
storage?: Record<string, any>;
|
|
9
|
+
getStorage?: <T extends TReqraftStorageValue>(
|
|
10
|
+
path: string,
|
|
11
|
+
defaultValue?: T,
|
|
12
|
+
includeAppPrefix?: boolean
|
|
13
|
+
) => Get<TReqraftStorage, string>;
|
|
14
|
+
updateStorage?: <T extends TReqraftStorageValue>(
|
|
15
|
+
path: string,
|
|
16
|
+
value: T,
|
|
17
|
+
includeAppPrefix?: boolean
|
|
18
|
+
) => void;
|
|
19
|
+
removeStorageValue: (path: string, includeAppPrefix?: boolean) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const ReqraftStorageContext = createContext<IReqraftStorageContext>({
|
|
23
|
+
storage: {},
|
|
24
|
+
getStorage: () => {
|
|
25
|
+
throw new Error('Storage not implemented');
|
|
26
|
+
},
|
|
27
|
+
updateStorage: () => {
|
|
28
|
+
throw new Error('Storage not implemented');
|
|
29
|
+
},
|
|
30
|
+
removeStorageValue: () => {
|
|
31
|
+
throw new Error('Storage not implemented');
|
|
32
|
+
},
|
|
33
|
+
});
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { ReqoreSpinner, ReqoreTree } from '@qoretechnologies/reqore';
|
|
2
|
+
import { StoryObj } from '@storybook/react';
|
|
3
|
+
import { useEffectOnce } from 'react-use';
|
|
4
|
+
import { testsWaitForText } from '../../../__tests__/utils';
|
|
5
|
+
import { StoryMeta } from '../../types';
|
|
6
|
+
import { useFetch } from './useFetch';
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: 'Hooks/useFetch',
|
|
10
|
+
render: (args) => {
|
|
11
|
+
const { data = {}, load, loading } = useFetch<any>({ url: 'public/info', method: args.method });
|
|
12
|
+
|
|
13
|
+
useEffectOnce(() => {
|
|
14
|
+
load();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return loading ? (
|
|
18
|
+
<ReqoreSpinner />
|
|
19
|
+
) : (
|
|
20
|
+
<ReqoreTree data={data} bottomActions={[{ label: 'Refetch', onClick: load }]} />
|
|
21
|
+
);
|
|
22
|
+
},
|
|
23
|
+
} as StoryMeta<any>;
|
|
24
|
+
|
|
25
|
+
export default meta;
|
|
26
|
+
export type Story = StoryObj<typeof meta>;
|
|
27
|
+
|
|
28
|
+
export const get: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
method: 'GET',
|
|
31
|
+
},
|
|
32
|
+
parameters: {
|
|
33
|
+
mockData: [
|
|
34
|
+
{
|
|
35
|
+
// An array of mock objects which will add in every story
|
|
36
|
+
url: 'https://hq.qoretechnologies.com:8092/api/latest/public/info',
|
|
37
|
+
method: 'GET',
|
|
38
|
+
status: 200,
|
|
39
|
+
delay: 200,
|
|
40
|
+
response: {
|
|
41
|
+
'instance-key': 'rippy-dev1',
|
|
42
|
+
'omq-version': '6.1.0_prod',
|
|
43
|
+
'omq-build': 'd115c45fbed95c03327aea880be455137e9778ba',
|
|
44
|
+
'qore-version': '2.0.0',
|
|
45
|
+
'omq-schema': 'omq@omq',
|
|
46
|
+
edition: 'Enterprise',
|
|
47
|
+
tz_region: 'Europe/Prague',
|
|
48
|
+
tz_utc_offset: 3600,
|
|
49
|
+
noauth: false,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
play: async () => {
|
|
55
|
+
await testsWaitForText('"rippy-dev1"');
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const put: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
method: 'PUT',
|
|
62
|
+
},
|
|
63
|
+
parameters: {
|
|
64
|
+
mockData: [
|
|
65
|
+
{
|
|
66
|
+
// An array of mock objects which will add in every story
|
|
67
|
+
url: 'https://hq.qoretechnologies.com:8092/api/latest/public/info',
|
|
68
|
+
method: 'PUT',
|
|
69
|
+
status: 200,
|
|
70
|
+
response: {
|
|
71
|
+
status: 'Successfuly updated',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
play: async () => {
|
|
77
|
+
await testsWaitForText('"Successfuly updated"');
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const post: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
},
|
|
85
|
+
parameters: {
|
|
86
|
+
mockData: [
|
|
87
|
+
{
|
|
88
|
+
// An array of mock objects which will add in every story
|
|
89
|
+
url: 'https://hq.qoretechnologies.com:8092/api/latest/public/info',
|
|
90
|
+
method: 'POST',
|
|
91
|
+
status: 200,
|
|
92
|
+
response: {
|
|
93
|
+
status: 'Successfuly created',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
play: async () => {
|
|
99
|
+
await testsWaitForText('"Successfuly created"');
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const del: Story = {
|
|
104
|
+
args: {
|
|
105
|
+
method: 'DELETE',
|
|
106
|
+
},
|
|
107
|
+
parameters: {
|
|
108
|
+
mockData: [
|
|
109
|
+
{
|
|
110
|
+
// An array of mock objects which will add in every story
|
|
111
|
+
url: 'https://hq.qoretechnologies.com:8092/api/latest/public/info',
|
|
112
|
+
method: 'DELETE',
|
|
113
|
+
status: 200,
|
|
114
|
+
response: {
|
|
115
|
+
status: 'Successfuly deleted',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
play: async () => {
|
|
121
|
+
await testsWaitForText('"Successfuly deleted"');
|
|
122
|
+
},
|
|
123
|
+
};
|