@axinom/mosaic-ui 0.71.0 → 0.72.0-rc.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/dist/components/Actions/Action/index.d.ts +2 -1
- package/dist/components/Actions/Action/index.d.ts.map +1 -1
- package/dist/components/Actions/index.d.ts +2 -1
- package/dist/components/Actions/index.d.ts.map +1 -1
- package/dist/components/Buttons/Button/index.d.ts +1 -1
- package/dist/components/Buttons/Button/index.d.ts.map +1 -1
- package/dist/components/Buttons/CompositeButton/index.d.ts +1 -1
- package/dist/components/Buttons/CompositeButton/index.d.ts.map +1 -1
- package/dist/components/Buttons/TextButton/index.d.ts +1 -1
- package/dist/components/Buttons/TextButton/index.d.ts.map +1 -1
- package/dist/components/Explorer/BulkEdit/BulkEdit.model.d.ts +7 -0
- package/dist/components/Explorer/BulkEdit/BulkEdit.model.d.ts.map +1 -1
- package/dist/components/Explorer/BulkEdit/GenerateMutation.d.ts.map +1 -1
- package/dist/components/Explorer/BulkEdit/helpers/wrapFilterEnums.d.ts +12 -0
- package/dist/components/Explorer/BulkEdit/helpers/wrapFilterEnums.d.ts.map +1 -0
- package/dist/components/Explorer/BulkEdit/index.d.ts +2 -1
- package/dist/components/Explorer/BulkEdit/index.d.ts.map +1 -1
- package/dist/components/Explorer/index.d.ts +11 -6
- package/dist/components/Explorer/index.d.ts.map +1 -1
- package/dist/components/FormElements/index.d.ts +40 -20
- package/dist/components/FormElements/index.d.ts.map +1 -1
- package/dist/components/FormStation/index.d.ts +6 -3
- package/dist/components/FormStation/index.d.ts.map +1 -1
- package/dist/components/Hub/Tile/index.d.ts +1 -1
- package/dist/components/Hub/Tile/index.d.ts.map +1 -1
- package/dist/components/Hub/index.d.ts +2 -1
- package/dist/components/Hub/index.d.ts.map +1 -1
- package/dist/components/Icons/index.d.ts +2 -1
- package/dist/components/Icons/index.d.ts.map +1 -1
- package/dist/components/InlineMenu/index.d.ts +2 -1
- package/dist/components/InlineMenu/index.d.ts.map +1 -1
- package/dist/components/LandingPageHeader/index.d.ts +2 -1
- package/dist/components/LandingPageHeader/index.d.ts.map +1 -1
- package/dist/components/LandingPageTiles/TileLarge/index.d.ts +2 -1
- package/dist/components/LandingPageTiles/TileLarge/index.d.ts.map +1 -1
- package/dist/components/LandingPageTiles/TileSmall/index.d.ts +2 -1
- package/dist/components/LandingPageTiles/TileSmall/index.d.ts.map +1 -1
- package/dist/components/LandingPageTiles/index.d.ts +2 -1
- package/dist/components/LandingPageTiles/index.d.ts.map +1 -1
- package/dist/components/List/index.d.ts +2 -1
- package/dist/components/List/index.d.ts.map +1 -1
- package/dist/components/MessageBar/index.d.ts +2 -1
- package/dist/components/MessageBar/index.d.ts.map +1 -1
- package/dist/components/Modal/index.d.ts +2 -1
- package/dist/components/Modal/index.d.ts.map +1 -1
- package/dist/components/PageHeader/index.d.ts +3 -2
- package/dist/components/PageHeader/index.d.ts.map +1 -1
- package/dist/components/ProgressBar/index.d.ts +2 -1
- package/dist/components/ProgressBar/index.d.ts.map +1 -1
- package/dist/components/VisualElements/index.d.ts +4 -2
- package/dist/components/VisualElements/index.d.ts.map +1 -1
- package/dist/helpers/index.d.ts +1 -1
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +11 -15
- package/src/components/Actions/Action/index.ts +2 -1
- package/src/components/Actions/Actions.stories.tsx +1 -1
- package/src/components/Actions/index.ts +2 -1
- package/src/components/Buttons/Button/index.ts +1 -1
- package/src/components/Buttons/CompositeButton/index.ts +1 -1
- package/src/components/Buttons/TextButton/index.ts +1 -1
- package/src/components/DynamicDataList/DynamicDataList.stories.tsx +1 -1
- package/src/components/EmptyStation/EmptyStation.stories.tsx +1 -1
- package/src/components/Explorer/BulkEdit/BulkEdit.model.ts +7 -0
- package/src/components/Explorer/BulkEdit/GenerateMutation.spec.tsx +51 -0
- package/src/components/Explorer/BulkEdit/GenerateMutation.tsx +11 -1
- package/src/components/Explorer/BulkEdit/helpers/wrapFilterEnums.spec.ts +80 -0
- package/src/components/Explorer/BulkEdit/helpers/wrapFilterEnums.ts +87 -0
- package/src/components/Explorer/BulkEdit/index.ts +2 -1
- package/src/components/Explorer/Explorer.stories.tsx +1 -1
- package/src/components/Explorer/NavigationExplorer/NavigationExplorer.stories.tsx +1 -1
- package/src/components/Explorer/SelectionExplorer/SelectionExplorer.stories.tsx +1 -1
- package/src/components/Explorer/index.ts +15 -18
- package/src/components/Filters/Filters.stories.tsx +1 -1
- package/src/components/FormElements/Checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/FormElements/CustomTags/CustomTags.stories.tsx +1 -1
- package/src/components/FormElements/DateTimeField/DateTimeText.stories.tsx +1 -1
- package/src/components/FormElements/FileUploadControl/FileUploadControl.stories.tsx +1 -1
- package/src/components/FormElements/MaskedSingleLineText/MaskedSingleLineText.stories.tsx +1 -1
- package/src/components/FormElements/Radio/Radio.stories.tsx +1 -1
- package/src/components/FormElements/Select/Select.stories.tsx +1 -1
- package/src/components/FormElements/SingleLineText/SingleLineText.stories.tsx +1 -1
- package/src/components/FormElements/Tags/Tags.stories.tsx +1 -1
- package/src/components/FormElements/TextArea/TextArea.stories.tsx +1 -1
- package/src/components/FormElements/ToggleButton/ToggleButton.stories.tsx +1 -1
- package/src/components/FormElements/formElements.mdx +12 -0
- package/src/components/FormElements/index.ts +40 -39
- package/src/components/FormStation/Create/Create.stories.tsx +1 -1
- package/src/components/FormStation/FormStation.stories.tsx +1 -1
- package/src/components/FormStation/index.ts +6 -3
- package/src/components/Hub/Tile/index.ts +1 -1
- package/src/components/Hub/index.ts +2 -1
- package/src/components/Icons/index.ts +2 -1
- package/src/components/InlineMenu/InlineMenu.stories.tsx +1 -1
- package/src/components/InlineMenu/index.ts +2 -1
- package/src/components/LandingPageHeader/index.ts +2 -1
- package/src/components/LandingPageTiles/TileLarge/index.ts +2 -1
- package/src/components/LandingPageTiles/TileSmall/index.ts +2 -1
- package/src/components/LandingPageTiles/index.ts +2 -1
- package/src/components/List/index.ts +2 -1
- package/src/components/MessageBar/index.ts +2 -1
- package/src/components/Modal/index.ts +2 -1
- package/src/components/NavigationAwareStation/NavigationAwareStation.stories.tsx +1 -1
- package/src/components/PageHeader/PageHeader.stories.tsx +1 -1
- package/src/components/PageHeader/index.ts +6 -6
- package/src/components/ProgressBar/index.ts +2 -1
- package/src/components/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/VisualElements/index.ts +4 -3
- package/src/docs/{Intro.stories.mdx → Intro.mdx} +1 -3
- package/src/helpers/index.ts +1 -1
- package/src/components/FormElements/formElements.stories.mdx +0 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axinom/mosaic-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.72.0-rc.4",
|
|
4
4
|
"description": "UI components for building Axinom Mosaic applications",
|
|
5
5
|
"author": "Axinom",
|
|
6
6
|
"license": "PROPRIETARY",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"test:ci": "vitest run --reporter=default --reporter=junit --coverage --coverage.reporter=cobertura --coverage.reporter=html",
|
|
29
29
|
"prettier": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}\" --loglevel silent",
|
|
30
30
|
"lint": "eslint . --ext .ts,.tsx,.js --color --cache",
|
|
31
|
-
"storybook": "storybook dev -p 6006",
|
|
31
|
+
"storybook": "cross-env STORYBOOK_A11Y=true storybook dev -p 6006",
|
|
32
32
|
"build-storybook": "storybook build"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@axinom/mosaic-core": "^0.4.
|
|
35
|
+
"@axinom/mosaic-core": "^0.4.34-rc.4",
|
|
36
36
|
"@faker-js/faker": "^7.4.0",
|
|
37
37
|
"@geoffcox/react-splitter": "^2.1.2",
|
|
38
38
|
"@mui/base": "5.0.0-beta.40",
|
|
@@ -62,21 +62,17 @@
|
|
|
62
62
|
"@babel/preset-env": "^7.20.2",
|
|
63
63
|
"@babel/preset-react": "^7.18.6",
|
|
64
64
|
"@babel/preset-typescript": "^7.18.6",
|
|
65
|
-
"@mdx-js/react": "^1.6.22",
|
|
66
65
|
"@optimize-lodash/rollup-plugin": "^4.0.1",
|
|
67
66
|
"@rollup/plugin-commonjs": "^15.1.0",
|
|
68
67
|
"@rollup/plugin-node-resolve": "^9.0.0",
|
|
69
68
|
"@rollup/plugin-url": "^8.0.1",
|
|
70
|
-
"@storybook/addon-
|
|
71
|
-
"@storybook/addon-
|
|
72
|
-
"@storybook/addon-links": "^
|
|
73
|
-
"@storybook/addon-
|
|
74
|
-
"@storybook/addons": "^7.0.6",
|
|
69
|
+
"@storybook/addon-a11y": "10.4.2",
|
|
70
|
+
"@storybook/addon-docs": "^10.4.2",
|
|
71
|
+
"@storybook/addon-links": "^10.4.2",
|
|
72
|
+
"@storybook/addon-webpack5-compiler-babel": "^4.0.1",
|
|
75
73
|
"@storybook/preset-scss": "^1.0.3",
|
|
76
|
-
"@storybook/react": "^
|
|
77
|
-
"@storybook/react-webpack5": "^
|
|
78
|
-
"@storybook/testing-library": "^0.1.0",
|
|
79
|
-
"@storybook/theming": "^7.0.6",
|
|
74
|
+
"@storybook/react": "^10.4.2",
|
|
75
|
+
"@storybook/react-webpack5": "^10.4.2",
|
|
80
76
|
"@testing-library/jest-dom": "^5.16.5",
|
|
81
77
|
"@testing-library/react": "^12.1.5",
|
|
82
78
|
"@testing-library/react-hooks": "^8.0.1",
|
|
@@ -104,7 +100,7 @@
|
|
|
104
100
|
"rollup-plugin-visualizer": "^5.8.3",
|
|
105
101
|
"sass": "^1.57.1",
|
|
106
102
|
"sass-loader": "^12.3.0",
|
|
107
|
-
"storybook": "^
|
|
103
|
+
"storybook": "^10.4.2",
|
|
108
104
|
"style-loader": "^3.3.1",
|
|
109
105
|
"typescript": "^5.9.3",
|
|
110
106
|
"typescript-plugin-css-modules": "^5.0.2",
|
|
@@ -113,5 +109,5 @@
|
|
|
113
109
|
"publishConfig": {
|
|
114
110
|
"access": "public"
|
|
115
111
|
},
|
|
116
|
-
"gitHead": "
|
|
112
|
+
"gitHead": "1286a116f2e06be15726a9094b19987c56d3bc49"
|
|
117
113
|
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { Action
|
|
1
|
+
export { Action } from './Action';
|
|
2
|
+
export type { ActionProps } from './Action';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { action } from '@storybook/addon-actions';
|
|
2
1
|
import { Meta, StoryObj } from '@storybook/react';
|
|
3
2
|
import React from 'react';
|
|
4
3
|
import { MemoryRouter } from 'react-router';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
5
|
import { createGroups, generateItemArray } from '../../helpers/storybook';
|
|
6
6
|
import { Actions, ActionsProps } from './Actions';
|
|
7
7
|
import { ActionData } from './Actions.models';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Button } from './Button';
|
|
2
|
-
export { ButtonProps } from './Button.model';
|
|
2
|
+
export type { ButtonProps } from './Button.model';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { CompositeButton } from './CompositeButton';
|
|
2
|
-
export { CompositeButtonProps } from './CompositeButton.model';
|
|
2
|
+
export type { CompositeButtonProps } from './CompositeButton.model';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { TextButton } from './TextButton';
|
|
2
|
-
export { TextButtonProps } from './TextButton.model';
|
|
2
|
+
export type { TextButtonProps } from './TextButton.model';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import React from 'react';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
5
|
import * as Yup from 'yup';
|
|
6
6
|
import { createGroups, generateItemArray } from '../../helpers/storybook';
|
|
7
7
|
import { ButtonContext } from '../Buttons';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import React from 'react';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
5
|
import { createGroups } from '../../helpers/storybook';
|
|
6
6
|
import { IconName } from '../Icons/Icons.models';
|
|
7
7
|
import { EmptyStation } from './EmptyStation';
|
|
@@ -23,5 +23,12 @@ export interface BulkEditConfig {
|
|
|
23
23
|
set?: string;
|
|
24
24
|
filter?: string;
|
|
25
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* Names of the filter fields whose values are backed by a GraphQL enum.
|
|
28
|
+
* Their values are wrapped so they are emitted as bare enum literals
|
|
29
|
+
* (e.g. `status: { in: [ACTIVE] }`) rather than quoted strings when the
|
|
30
|
+
* filter is inlined into the generated mutation document.
|
|
31
|
+
*/
|
|
32
|
+
filterEnumFields?: string[];
|
|
26
33
|
fields: BulkEditFieldConfigMap;
|
|
27
34
|
}
|
|
@@ -157,6 +157,57 @@ describe('generateBulkEditMutation', () => {
|
|
|
157
157
|
);
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
+
it('should emit enum filter values as bare enum literals, not quoted strings', () => {
|
|
161
|
+
const config: BulkEditConfig = {
|
|
162
|
+
mutation: 'bulkEditUsersAsync',
|
|
163
|
+
filterEnumFields: ['status'],
|
|
164
|
+
fields: {
|
|
165
|
+
name: {
|
|
166
|
+
type: 'String',
|
|
167
|
+
label: 'Name',
|
|
168
|
+
originalFieldName: 'name',
|
|
169
|
+
action: 'set',
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const result = generateBulkEditMutation(
|
|
175
|
+
config,
|
|
176
|
+
{ name: 'New Name' },
|
|
177
|
+
{ status: { in: ['ACTIVE', 'BLOCKED'], isNull: false } },
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// `status` values are bare enum literals, while `isNull` stays boolean.
|
|
181
|
+
expect(result).toBe(
|
|
182
|
+
`mutation { bulkEditUsersAsync (set: {name: "New Name"}, filter: {status: {in: [ACTIVE, BLOCKED], isNull: false}}) { filterMatchedIds } }`,
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should leave the filter untouched when no filterEnumFields are configured', () => {
|
|
187
|
+
const config: BulkEditConfig = {
|
|
188
|
+
mutation: 'bulkEditUsersAsync',
|
|
189
|
+
fields: {
|
|
190
|
+
name: {
|
|
191
|
+
type: 'String',
|
|
192
|
+
label: 'Name',
|
|
193
|
+
originalFieldName: 'name',
|
|
194
|
+
action: 'set',
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const result = generateBulkEditMutation(
|
|
200
|
+
config,
|
|
201
|
+
{ name: 'New Name' },
|
|
202
|
+
{ status: { in: ['ACTIVE'] } },
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Without filterEnumFields the value remains a quoted string.
|
|
206
|
+
expect(result).toBe(
|
|
207
|
+
`mutation { bulkEditUsersAsync (set: {name: "New Name"}, filter: {status: {in: ["ACTIVE"]}}) { filterMatchedIds } }`,
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
|
|
160
211
|
it('should use custom key names when provided in config', () => {
|
|
161
212
|
const configWithCustomKeys: BulkEditConfig = {
|
|
162
213
|
mutation: 'bulkEditCustom',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsonToGraphQLQuery } from 'json-to-graphql-query';
|
|
2
2
|
import { Data } from '../../../types';
|
|
3
3
|
import { BulkEditConfig } from './BulkEdit.model';
|
|
4
|
+
import { wrapFilterEnums } from './helpers/wrapFilterEnums';
|
|
4
5
|
|
|
5
6
|
export const generateBulkEditMutation = <T extends Data>(
|
|
6
7
|
config: BulkEditConfig,
|
|
@@ -154,6 +155,15 @@ export const generateBulkEditMutation = <T extends Data>(
|
|
|
154
155
|
throw new Error('No valid fields to generate mutation');
|
|
155
156
|
}
|
|
156
157
|
|
|
158
|
+
// Enum filter values must be emitted as bare GraphQL enum literals
|
|
159
|
+
// (e.g. `status: { in: [ACTIVE] }`), not quoted strings, when inlined into
|
|
160
|
+
// the mutation document. Wrap them based on the enum fields declared in the
|
|
161
|
+
// config so `jsonToGraphQLQuery` renders them without quotes.
|
|
162
|
+
const wrappedFilter: object | undefined =
|
|
163
|
+
filter && config.filterEnumFields?.length
|
|
164
|
+
? (wrapFilterEnums(filter, config.filterEnumFields) as object)
|
|
165
|
+
: filter;
|
|
166
|
+
|
|
157
167
|
return jsonToGraphQLQuery({
|
|
158
168
|
mutation: {
|
|
159
169
|
[mutation]: {
|
|
@@ -170,7 +180,7 @@ export const generateBulkEditMutation = <T extends Data>(
|
|
|
170
180
|
...(Object.keys(set).length > 0 && {
|
|
171
181
|
[setKey]: set,
|
|
172
182
|
}),
|
|
173
|
-
...(
|
|
183
|
+
...(wrappedFilter && { [filterKey]: wrappedFilter }),
|
|
174
184
|
},
|
|
175
185
|
filterMatchedIds: true,
|
|
176
186
|
},
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { EnumType } from 'json-to-graphql-query';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { wrapFilterEnums } from './wrapFilterEnums';
|
|
4
|
+
|
|
5
|
+
describe('wrapFilterEnums', () => {
|
|
6
|
+
it('wraps the values of an `in` list as enum literals', () => {
|
|
7
|
+
expect(
|
|
8
|
+
wrapFilterEnums({ status: { in: ['ACTIVE', 'BLOCKED'] } }, ['status']),
|
|
9
|
+
).toEqual({
|
|
10
|
+
status: { in: [new EnumType('ACTIVE'), new EnumType('BLOCKED')] },
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('wraps a scalar operator value as an enum literal', () => {
|
|
15
|
+
expect(
|
|
16
|
+
wrapFilterEnums({ status: { equalTo: 'ACTIVE' } }, ['status']),
|
|
17
|
+
).toEqual({ status: { equalTo: new EnumType('ACTIVE') } });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('leaves the boolean `isNull` operator untouched', () => {
|
|
21
|
+
expect(
|
|
22
|
+
wrapFilterEnums({ status: { in: ['ACTIVE'], isNull: false } }, [
|
|
23
|
+
'status',
|
|
24
|
+
]),
|
|
25
|
+
).toEqual({ status: { in: [new EnumType('ACTIVE')], isNull: false } });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('leaves fields that are not enum-backed untouched', () => {
|
|
29
|
+
expect(
|
|
30
|
+
wrapFilterEnums(
|
|
31
|
+
{
|
|
32
|
+
status: { equalTo: 'ACTIVE' },
|
|
33
|
+
email: { includesInsensitive: 'axinom' },
|
|
34
|
+
},
|
|
35
|
+
['status'],
|
|
36
|
+
),
|
|
37
|
+
).toEqual({
|
|
38
|
+
status: { equalTo: new EnumType('ACTIVE') },
|
|
39
|
+
email: { includesInsensitive: 'axinom' },
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('recurses through the logical and/or/not operators', () => {
|
|
44
|
+
expect(
|
|
45
|
+
wrapFilterEnums(
|
|
46
|
+
{
|
|
47
|
+
and: [
|
|
48
|
+
{ status: { equalTo: 'ACTIVE' } },
|
|
49
|
+
{ not: { status: { equalTo: 'BLOCKED' } } },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
['status'],
|
|
53
|
+
),
|
|
54
|
+
).toEqual({
|
|
55
|
+
and: [
|
|
56
|
+
{ status: { equalTo: new EnumType('ACTIVE') } },
|
|
57
|
+
{ not: { status: { equalTo: new EnumType('BLOCKED') } } },
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('returns the filter unchanged when no enum fields are present', () => {
|
|
63
|
+
const filter = { status: { in: ['ACTIVE'] } };
|
|
64
|
+
expect(wrapFilterEnums(filter, [])).toEqual({ status: { in: ['ACTIVE'] } });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('leaves a null enum value untouched rather than wrapping it', () => {
|
|
68
|
+
expect(wrapFilterEnums({ status: { equalTo: null } }, ['status'])).toEqual({
|
|
69
|
+
status: { equalTo: null },
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('throws for a value that is not a valid enum literal token', () => {
|
|
74
|
+
expect(() =>
|
|
75
|
+
wrapFilterEnums({ status: { equalTo: 'ACTIVE"} injection' } }, [
|
|
76
|
+
'status',
|
|
77
|
+
]),
|
|
78
|
+
).toThrow(/Invalid enum value/);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { EnumType } from 'json-to-graphql-query';
|
|
2
|
+
|
|
3
|
+
// A GraphQL enum value is a Name: /[_A-Za-z][_0-9A-Za-z]*/.
|
|
4
|
+
const ENUM_LITERAL_PATTERN = /^[_A-Za-z][_0-9A-Za-z]*$/;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Recursively wraps the values of enum-backed filter fields with `EnumType` so
|
|
8
|
+
* they are serialized as bare GraphQL enum literals (e.g. `status: { in: [ACTIVE] }`)
|
|
9
|
+
* rather than quoted strings when the filter is inlined into a mutation document.
|
|
10
|
+
* Descends through the logical `and`/`or`/`not` operators and leaves every other
|
|
11
|
+
* field untouched.
|
|
12
|
+
*
|
|
13
|
+
* @param filter the (PostGraphile) filter object to transform.
|
|
14
|
+
* @param enumFields names of the filter fields whose values are enum-backed.
|
|
15
|
+
*/
|
|
16
|
+
export function wrapFilterEnums(
|
|
17
|
+
filter: unknown,
|
|
18
|
+
enumFields: string[],
|
|
19
|
+
): unknown {
|
|
20
|
+
if (Array.isArray(filter)) {
|
|
21
|
+
return filter.map((item) => wrapFilterEnums(item, enumFields));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (filter === null || typeof filter !== 'object') {
|
|
25
|
+
return filter;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return Object.entries(filter as Record<string, unknown>).reduce(
|
|
29
|
+
(acc, [key, value]) => {
|
|
30
|
+
if (key === 'and' || key === 'or' || key === 'not') {
|
|
31
|
+
acc[key] = wrapFilterEnums(value, enumFields);
|
|
32
|
+
} else if (enumFields.includes(key)) {
|
|
33
|
+
acc[key] = wrapEnumCondition(value);
|
|
34
|
+
} else {
|
|
35
|
+
acc[key] = value;
|
|
36
|
+
}
|
|
37
|
+
return acc;
|
|
38
|
+
},
|
|
39
|
+
{} as Record<string, unknown>,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Wraps each operator value of a filter condition (e.g. `{ in: ['ACTIVE'] }`,
|
|
45
|
+
* `{ equalTo: 'ACTIVE' }`) with `EnumType`. The boolean `isNull` operator is
|
|
46
|
+
* left as-is since it is not an enum value.
|
|
47
|
+
*/
|
|
48
|
+
function wrapEnumCondition(condition: unknown): unknown {
|
|
49
|
+
if (condition === null || typeof condition !== 'object') {
|
|
50
|
+
return condition;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return Object.entries(condition as Record<string, unknown>).reduce(
|
|
54
|
+
(acc, [op, value]) => {
|
|
55
|
+
if (op === 'isNull') {
|
|
56
|
+
acc[op] = value;
|
|
57
|
+
} else {
|
|
58
|
+
acc[op] = Array.isArray(value)
|
|
59
|
+
? value.map((item) => toEnumLiteral(item))
|
|
60
|
+
: toEnumLiteral(value);
|
|
61
|
+
}
|
|
62
|
+
return acc;
|
|
63
|
+
},
|
|
64
|
+
{} as Record<string, unknown>,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Wraps a single enum value with `EnumType` so it is emitted as a bare GraphQL
|
|
70
|
+
* enum literal. `null`/`undefined` are left untouched (e.g. `equalTo: null`),
|
|
71
|
+
* and any value that is not a valid enum literal token throws instead of being
|
|
72
|
+
* inlined unquoted — guarding against an invalid (or injectable) document.
|
|
73
|
+
*/
|
|
74
|
+
function toEnumLiteral(value: unknown): unknown {
|
|
75
|
+
if (value === null || value === undefined) {
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const token = String(value);
|
|
80
|
+
if (!ENUM_LITERAL_PATTERN.test(token)) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Invalid enum value "${token}" in bulk edit filter: enum values must match ${ENUM_LITERAL_PATTERN}.`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return new EnumType(token);
|
|
87
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { EnumType as BulkEditEnumType } from 'json-to-graphql-query';
|
|
2
2
|
export type * from './BulkEdit.model';
|
|
3
|
-
export { BulkEditContext
|
|
3
|
+
export { BulkEditContext } from './BulkEditContext';
|
|
4
|
+
export type { BulkEditContextType } from './BulkEditContext';
|
|
4
5
|
export {
|
|
5
6
|
BulkEditFormFieldsConfigConverter,
|
|
6
7
|
defaultComponentMap,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import { Field } from 'formik';
|
|
5
4
|
import React, { useContext } from 'react';
|
|
6
5
|
import { MemoryRouter } from 'react-router';
|
|
6
|
+
import { action } from 'storybook/actions';
|
|
7
7
|
import * as Yup from 'yup';
|
|
8
8
|
import {
|
|
9
9
|
createGroups,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import React, { useMemo } from 'react';
|
|
5
4
|
import { MemoryRouter } from 'react-router-dom';
|
|
5
|
+
import { action } from 'storybook/actions';
|
|
6
6
|
import {
|
|
7
7
|
createGroups,
|
|
8
8
|
generateItemArray,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import { noop } from 'lodash';
|
|
5
4
|
import React, { useMemo } from 'react';
|
|
6
5
|
import { MemoryRouter } from 'react-router';
|
|
6
|
+
import { action } from 'storybook/actions';
|
|
7
7
|
import {
|
|
8
8
|
createGroups,
|
|
9
9
|
generateItemArray,
|
|
@@ -1,37 +1,34 @@
|
|
|
1
1
|
export {
|
|
2
|
-
BulkEditConfig,
|
|
3
2
|
BulkEditContext,
|
|
4
|
-
BulkEditContextType,
|
|
5
3
|
BulkEditEnumType,
|
|
6
|
-
BulkEditFieldConfig,
|
|
7
|
-
BulkEditFieldConfigMap,
|
|
8
4
|
BulkEditFormFieldsConfigConverter,
|
|
9
5
|
defaultComponentMap,
|
|
10
6
|
generateBulkEditMutation,
|
|
11
7
|
} from './BulkEdit';
|
|
12
|
-
export {
|
|
13
|
-
|
|
8
|
+
export type {
|
|
9
|
+
BulkEditConfig,
|
|
10
|
+
BulkEditContextType,
|
|
11
|
+
BulkEditFieldConfig,
|
|
12
|
+
BulkEditFieldConfigMap,
|
|
13
|
+
} from './BulkEdit';
|
|
14
|
+
export { Explorer } from './Explorer';
|
|
15
|
+
export type { ExplorerProps } from './Explorer';
|
|
16
|
+
export type {
|
|
14
17
|
ExplorerBulkAction,
|
|
15
18
|
ExplorerDataProvider,
|
|
16
19
|
ExplorerDataProviderConnection,
|
|
17
20
|
ItemSelection,
|
|
18
21
|
PageIdentifier,
|
|
19
22
|
} from './Explorer.model';
|
|
20
|
-
export {
|
|
21
|
-
NavigationExplorer,
|
|
22
|
-
NavigationExplorerProps,
|
|
23
|
-
} from './NavigationExplorer/NavigationExplorer';
|
|
24
|
-
export {
|
|
25
|
-
QuickEditContext,
|
|
26
|
-
QuickEditContextType,
|
|
27
|
-
} from './QuickEdit/QuickEditContext';
|
|
28
|
-
export {
|
|
29
|
-
SelectionExplorer,
|
|
30
|
-
SelectionExplorerProps,
|
|
31
|
-
} from './SelectionExplorer/SelectionExplorer';
|
|
32
23
|
export {
|
|
33
24
|
createInMemoryDataProvider,
|
|
34
25
|
findAnywhereInString,
|
|
35
26
|
findAnywhereInStringCaseInsensitive,
|
|
36
27
|
findExact,
|
|
37
28
|
} from './helpers/InMemoryDataProvider';
|
|
29
|
+
export { NavigationExplorer } from './NavigationExplorer/NavigationExplorer';
|
|
30
|
+
export type { NavigationExplorerProps } from './NavigationExplorer/NavigationExplorer';
|
|
31
|
+
export { QuickEditContext } from './QuickEdit/QuickEditContext';
|
|
32
|
+
export type { QuickEditContextType } from './QuickEdit/QuickEditContext';
|
|
33
|
+
export { SelectionExplorer } from './SelectionExplorer/SelectionExplorer';
|
|
34
|
+
export type { SelectionExplorerProps } from './SelectionExplorer/SelectionExplorer';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import React, { useState } from 'react';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
5
|
import { generateItemArray } from '../../helpers/storybook';
|
|
6
6
|
import { TextButton } from '../Buttons/TextButton/TextButton';
|
|
7
7
|
import { Checkbox } from '../FormElements';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import React, { useState } from 'react';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
5
|
import { createGroups } from '../../../helpers/storybook';
|
|
6
6
|
import { Checkbox } from './Checkbox';
|
|
7
7
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { action } from '@storybook/addon-actions';
|
|
2
1
|
import { Meta, StoryObj } from '@storybook/react';
|
|
3
2
|
import React, { useState } from 'react';
|
|
3
|
+
import { action } from 'storybook/actions';
|
|
4
4
|
import { createGroups } from '../../../helpers/storybook';
|
|
5
5
|
import { CustomTags } from './CustomTags';
|
|
6
6
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import React, { useState } from 'react';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
5
|
import { createGroups } from '../../../helpers/storybook';
|
|
6
6
|
import { DateTimeText } from './DateTimeText';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import React, { useState } from 'react';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
5
|
import { createGroups } from '../../../helpers/storybook';
|
|
6
6
|
import { noop } from '../../../helpers/utils';
|
|
7
7
|
import { FileUploadControl } from './FileUploadControl';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { action } from '@storybook/addon-actions';
|
|
2
1
|
import { Meta, StoryObj } from '@storybook/react';
|
|
3
2
|
import React, { useState } from 'react';
|
|
3
|
+
import { action } from 'storybook/actions';
|
|
4
4
|
import { createGroups } from '../../../helpers/storybook';
|
|
5
5
|
import { MaskedSingleLineText } from './MaskedSingleLineText';
|
|
6
6
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import React, { useState } from 'react';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
5
|
import { createGroups } from '../../../helpers/storybook';
|
|
6
6
|
import { Radio } from './Radio';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import React, { useState } from 'react';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
5
|
import { createGroups } from '../../../helpers/storybook';
|
|
6
6
|
import { Select } from './Select';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import React, { useState } from 'react';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
5
|
import { createGroups } from '../../../helpers/storybook';
|
|
6
6
|
import { SingleLineText } from './SingleLineText';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import React, { useState } from 'react';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
5
|
import { createGroups, generateItemArray } from '../../../helpers/storybook';
|
|
6
6
|
import { Tags } from './Tags';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import React, { useState } from 'react';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
5
|
import { createGroups } from '../../../helpers/storybook';
|
|
6
6
|
import { TextArea } from './TextArea';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { faker } from '@faker-js/faker';
|
|
2
|
-
import { action } from '@storybook/addon-actions';
|
|
3
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
3
|
import React, { useState } from 'react';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
5
|
import { createGroups } from '../../../helpers/storybook';
|
|
6
6
|
import { ToggleButton } from './ToggleButton';
|
|
7
7
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Meta } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import { SampleForm } from './formStoryHelper';
|
|
3
|
+
|
|
4
|
+
<Meta title="Primary Components/Form Elements" />
|
|
5
|
+
|
|
6
|
+
# Form Components
|
|
7
|
+
|
|
8
|
+
This is a sample form, using the form components of this library.
|
|
9
|
+
For a more detailed description of the single elements, please refer to the
|
|
10
|
+
other stories in this folder.
|
|
11
|
+
|
|
12
|
+
<SampleForm />
|