@axinom/mosaic-ui 0.43.0-rc.0 → 0.43.0-rc.10

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.
Files changed (56) hide show
  1. package/dist/components/FormStation/FormGrid/FormGrid.d.ts +7 -0
  2. package/dist/components/FormStation/FormGrid/FormGrid.d.ts.map +1 -0
  3. package/dist/components/FormStation/FormGrid/index.d.ts +2 -0
  4. package/dist/components/FormStation/FormGrid/index.d.ts.map +1 -0
  5. package/dist/components/FormStation/FormStation.d.ts +3 -12
  6. package/dist/components/FormStation/FormStation.d.ts.map +1 -1
  7. package/dist/components/FormStation/FormStation.models.d.ts +16 -2
  8. package/dist/components/FormStation/FormStation.models.d.ts.map +1 -1
  9. package/dist/components/FormStation/FormStationActions/FormStationActions.d.ts +21 -0
  10. package/dist/components/FormStation/FormStationActions/FormStationActions.d.ts.map +1 -0
  11. package/dist/components/FormStation/FormStationActions/index.d.ts +2 -0
  12. package/dist/components/FormStation/FormStationActions/index.d.ts.map +1 -0
  13. package/dist/components/FormStation/FormStationContentWrapper/FormStationContentWrapper.d.ts +11 -0
  14. package/dist/components/FormStation/FormStationContentWrapper/FormStationContentWrapper.d.ts.map +1 -0
  15. package/dist/components/FormStation/FormStationContentWrapper/index.d.ts +2 -0
  16. package/dist/components/FormStation/FormStationContentWrapper/index.d.ts.map +1 -0
  17. package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts +11 -0
  18. package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -0
  19. package/dist/components/FormStation/FormStationHeader/index.d.ts +2 -0
  20. package/dist/components/FormStation/FormStationHeader/index.d.ts.map +1 -0
  21. package/dist/components/FormStation/helpers/useDataProvider.d.ts +14 -0
  22. package/dist/components/FormStation/helpers/useDataProvider.d.ts.map +1 -0
  23. package/dist/components/FormStation/{useValidationError.d.ts → helpers/useValidationError.d.ts} +1 -1
  24. package/dist/components/FormStation/helpers/useValidationError.d.ts.map +1 -0
  25. package/dist/components/FormStation/index.d.ts +2 -1
  26. package/dist/components/FormStation/index.d.ts.map +1 -1
  27. package/dist/index.es.js +4 -4
  28. package/dist/index.es.js.map +1 -1
  29. package/dist/index.js +4 -4
  30. package/dist/index.js.map +1 -1
  31. package/package.json +3 -3
  32. package/src/components/FormStation/FormGrid/FormGrid.scss +10 -0
  33. package/src/components/FormStation/FormGrid/FormGrid.tsx +25 -0
  34. package/src/components/FormStation/FormGrid/index.ts +1 -0
  35. package/src/components/FormStation/FormStation.models.ts +28 -2
  36. package/src/components/FormStation/FormStation.scss +1 -117
  37. package/src/components/FormStation/FormStation.stories.tsx +4 -11
  38. package/src/components/FormStation/FormStation.tsx +39 -388
  39. package/src/components/FormStation/FormStationActions/FormStationActions.tsx +130 -0
  40. package/src/components/FormStation/FormStationActions/index.ts +1 -0
  41. package/src/components/FormStation/FormStationContentWrapper/FormStationContentWrapper.scss +66 -0
  42. package/src/components/FormStation/FormStationContentWrapper/FormStationContentWrapper.tsx +76 -0
  43. package/src/components/FormStation/FormStationContentWrapper/index.ts +1 -0
  44. package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +88 -0
  45. package/src/components/FormStation/FormStationHeader/index.ts +1 -0
  46. package/src/components/FormStation/helpers/useDataProvider.ts +136 -0
  47. package/src/components/FormStation/{useValidationError.tsx → helpers/useValidationError.tsx} +2 -1
  48. package/src/components/FormStation/index.ts +2 -5
  49. package/dist/common/assertError.d.ts +0 -13
  50. package/dist/common/assertError.d.ts.map +0 -1
  51. package/dist/components/FormStation/StationErrorStateType.d.ts +0 -5
  52. package/dist/components/FormStation/StationErrorStateType.d.ts.map +0 -1
  53. package/dist/components/FormStation/useValidationError.d.ts.map +0 -1
  54. package/src/common/assert-error.spec.ts +0 -61
  55. package/src/common/assertError.ts +0 -33
  56. package/src/components/FormStation/StationErrorStateType.tsx +0 -5
@@ -0,0 +1,76 @@
1
+ import clsx from 'clsx';
2
+ import { Form } from 'formik';
3
+ import React from 'react';
4
+ import { Data } from '../../../types';
5
+ import { MessageBar } from '../../MessageBar';
6
+ import { FormGrid } from '../FormGrid';
7
+ import { FormStationProps } from '../FormStation';
8
+ import { StationErrorStateType } from '../FormStation.models';
9
+ import classes from './FormStationContentWrapper.scss';
10
+
11
+ interface FormStationContentWrapperProps
12
+ extends Pick<
13
+ FormStationProps<Data>,
14
+ 'stationMessage' | 'edgeToEdgeContent' | 'infoPanel' | 'initialData'
15
+ > {
16
+ stationError?: StationErrorStateType;
17
+ setStationError: React.Dispatch<
18
+ React.SetStateAction<StationErrorStateType | undefined>
19
+ >;
20
+ }
21
+
22
+ export const FormStationContentWrapper: React.FC<
23
+ FormStationContentWrapperProps
24
+ > = ({
25
+ stationMessage,
26
+ edgeToEdgeContent,
27
+ infoPanel,
28
+ initialData,
29
+ stationError,
30
+ setStationError,
31
+ children,
32
+ }) => (
33
+ <>
34
+ {stationError && (
35
+ <div className={classes.errorMessage}>
36
+ <MessageBar
37
+ type="error"
38
+ title={String(stationError.title)}
39
+ onClose={() => setStationError(undefined)}
40
+ >
41
+ {stationError?.body}
42
+ </MessageBar>
43
+ </div>
44
+ )}
45
+ {initialData.loading ? (
46
+ // TODO: Loading skeleton of the page
47
+ <></>
48
+ ) : initialData.error ||
49
+ initialData.data === null ||
50
+ initialData.entityNotFound ? (
51
+ // Error on loading - we can't show the form
52
+ <div className={classes.loadingError}></div>
53
+ ) : (
54
+ <div className={classes.children}>
55
+ <div className={classes.main}>
56
+ <div
57
+ className={clsx(classes.formWrapper, {
58
+ [classes.hasMessage]: stationMessage,
59
+ })}
60
+ >
61
+ {stationMessage && <MessageBar {...stationMessage} />}
62
+ <Form>
63
+ <FormGrid edgeToEdgeContent={edgeToEdgeContent}>
64
+ {children}
65
+ {/* Adding a invisible text input here to prevent the browser from submitting on "Enter" when there is only a single text input field in the form
66
+ See: https://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2 */}
67
+ <input type="text" style={{ display: 'none' }} />
68
+ </FormGrid>
69
+ </Form>
70
+ </div>
71
+ {infoPanel}
72
+ </div>
73
+ </div>
74
+ )}
75
+ </>
76
+ );
@@ -0,0 +1 @@
1
+ export * from './FormStationContentWrapper';
@@ -0,0 +1,88 @@
1
+ import { FormikValues, useFormikContext } from 'formik';
2
+ import React, { useEffect } from 'react';
3
+ import { useHistory } from 'react-router-dom';
4
+ import { SaveIndicatorType, setSaveIndicator } from '../../../initialize';
5
+ import { IconName } from '../../Icons';
6
+ import {
7
+ PageHeader,
8
+ PageHeaderActionType,
9
+ PageHeaderProps,
10
+ } from '../../PageHeader';
11
+
12
+ /**
13
+ * Handles showRefresh and cancel buttons based on form states
14
+ */
15
+ export const FormStationHeader: React.FC<
16
+ Omit<PageHeaderProps, 'title'> & {
17
+ titleProperty?: string;
18
+ defaultTitle?: string;
19
+ cancelNavigationUrl?: string;
20
+ }
21
+ > = ({
22
+ titleProperty,
23
+ defaultTitle,
24
+ subtitle,
25
+ cancelNavigationUrl,
26
+ className,
27
+ }) => {
28
+ const { dirty, resetForm, values } = useFormikContext<FormikValues>();
29
+
30
+ useEffect(() => {
31
+ // Set the save indicator to dirty depending on the form state
32
+ if (dirty) {
33
+ setSaveIndicator(SaveIndicatorType.Dirty);
34
+ } else {
35
+ setSaveIndicator(SaveIndicatorType.Inactive);
36
+ }
37
+ return () => {
38
+ // The form is not always considered "not dirty" after the save
39
+ // so this code will make sure that the indicator is set to inactive
40
+ // when the station is left.
41
+ setSaveIndicator(SaveIndicatorType.Inactive);
42
+ };
43
+ }, [dirty]);
44
+
45
+ const history = useHistory();
46
+
47
+ const title =
48
+ titleProperty && values[titleProperty] !== ''
49
+ ? values[titleProperty]
50
+ : defaultTitle ?? '';
51
+
52
+ return (
53
+ <PageHeader
54
+ title={title}
55
+ subtitle={subtitle}
56
+ className={className}
57
+ actions={[
58
+ ...(dirty === true // add undo action if form as been altered
59
+ ? [
60
+ {
61
+ label: 'Undo Changes',
62
+ icon: IconName.Undo,
63
+ actionType: PageHeaderActionType.Context,
64
+ onClick: () => {
65
+ resetForm();
66
+ },
67
+ },
68
+ ]
69
+ : []),
70
+ ...(cancelNavigationUrl // add cancel action if applicable
71
+ ? [
72
+ {
73
+ label: 'Cancel',
74
+ icon: IconName.X,
75
+ onClick: () => {
76
+ resetForm();
77
+ // If the form has errors, Navigation needs to be wrapped in a promise or timeout.
78
+ Promise.resolve().then(() =>
79
+ history.push(cancelNavigationUrl),
80
+ );
81
+ },
82
+ },
83
+ ]
84
+ : []),
85
+ ]}
86
+ />
87
+ );
88
+ };
@@ -0,0 +1 @@
1
+ export * from './FormStationHeader';
@@ -0,0 +1,136 @@
1
+ import { FormikHelpers } from 'formik';
2
+ import {
3
+ Dispatch,
4
+ SetStateAction,
5
+ useCallback,
6
+ useEffect,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+ import {
11
+ SaveIndicatorType,
12
+ setSaveIndicator,
13
+ showNotification,
14
+ } from '../../../initialize';
15
+ import { Data } from '../../../types';
16
+ import { ErrorTypeToStationError } from '../../../utils/ErrorTypeToStationError';
17
+ import { ErrorType } from '../../models';
18
+ import {
19
+ InitialFormData,
20
+ SaveDataFunction,
21
+ StationErrorStateType,
22
+ } from '../FormStation.models';
23
+
24
+ export type FormStationDataProvider = <TValues extends Data, TSubmitResponse>(
25
+ initialData: InitialFormData<TValues>,
26
+ saveData: SaveDataFunction<TValues, TSubmitResponse>,
27
+ ) => {
28
+ onSubmit: (
29
+ values: TValues,
30
+ formikHelpers: FormikHelpers<TValues>,
31
+ ) => Promise<void>;
32
+ stationError?: StationErrorStateType;
33
+ setStationError: Dispatch<SetStateAction<StationErrorStateType | undefined>>;
34
+ isFormSubmitting: boolean;
35
+ lastSubmittedResponse: React.MutableRefObject<TSubmitResponse | undefined>;
36
+ initialValues: TValues | null | undefined;
37
+ };
38
+
39
+ export const useDataProvider: FormStationDataProvider = <
40
+ TValues extends Data,
41
+ TSubmitResponse,
42
+ >(
43
+ initialData: InitialFormData<TValues>,
44
+ saveData: SaveDataFunction<TValues, TSubmitResponse>,
45
+ ) => {
46
+ const [stationError, setStationError] = useState<StationErrorStateType>();
47
+ const [isFormSubmitting, setIsFormSubmitting] = useState<boolean>(false);
48
+ const lastSubmittedResponse = useRef<TSubmitResponse>();
49
+
50
+ useEffect(() => {
51
+ if (
52
+ initialData.error ||
53
+ (initialData.data === null && !initialData.loading) ||
54
+ initialData.entityNotFound
55
+ ) {
56
+ const stationError = {
57
+ ...ErrorTypeToStationError(
58
+ initialData.error,
59
+ 'An error occurred when trying to load data.',
60
+ 'Entity not found',
61
+ ),
62
+ type: 'loading',
63
+ };
64
+
65
+ setStationError(stationError);
66
+ } else {
67
+ if (stationError?.type === 'loading') {
68
+ // Only clear the error if it is a loading error, which now seems to be cleared.
69
+ setStationError(undefined);
70
+ }
71
+ }
72
+ }, [
73
+ initialData.loading,
74
+ initialData.error,
75
+ initialData.entityNotFound,
76
+ initialData.data,
77
+ stationError?.type,
78
+ ]);
79
+
80
+ const onSubmit = useCallback(
81
+ async (
82
+ values: TValues,
83
+ formikHelpers: FormikHelpers<TValues>,
84
+ ): Promise<void> => {
85
+ if (isFormSubmitting) {
86
+ return;
87
+ }
88
+
89
+ try {
90
+ setIsFormSubmitting(true);
91
+ setSaveIndicator(SaveIndicatorType.Saving);
92
+ setStationError(undefined);
93
+ if (!initialData.loading && saveData) {
94
+ const response = await saveData(values, initialData, formikHelpers);
95
+ if (response) {
96
+ lastSubmittedResponse.current = response;
97
+ }
98
+
99
+ showNotification({
100
+ title: 'Your changes were saved successfully.',
101
+ options: {
102
+ type: 'success',
103
+ autoClose: 1500,
104
+ },
105
+ });
106
+ }
107
+ } catch (error) {
108
+ setStationError(
109
+ ErrorTypeToStationError(
110
+ error as ErrorType,
111
+ 'An error occurred when trying to save data.',
112
+ ),
113
+ );
114
+
115
+ setSaveIndicator(SaveIndicatorType.Dirty);
116
+
117
+ // We still throw the error, to make sure that navigation or action execution
118
+ // will not continue after a failed save.
119
+ throw error;
120
+ } finally {
121
+ formikHelpers.setSubmitting(false);
122
+ setIsFormSubmitting(false);
123
+ }
124
+ },
125
+ [isFormSubmitting, initialData, saveData, setStationError],
126
+ );
127
+
128
+ return {
129
+ onSubmit,
130
+ stationError,
131
+ setStationError,
132
+ isFormSubmitting,
133
+ lastSubmittedResponse,
134
+ initialValues: initialData.data,
135
+ };
136
+ };
@@ -1,6 +1,6 @@
1
1
  import { useFormikContext } from 'formik';
2
2
  import React, { useCallback, useEffect, useState } from 'react';
3
- import { StationErrorStateType } from './StationErrorStateType';
3
+ import { StationErrorStateType } from '../FormStation.models';
4
4
 
5
5
  /**
6
6
  * Component that watches for changes in the form validation state
@@ -15,6 +15,7 @@ const ValidationWatcher: React.FC<{
15
15
  }, [formik.isValid, isValid]);
16
16
  return null;
17
17
  };
18
+
18
19
  /**
19
20
  * Cares for showing (and removing) validation errors.
20
21
  * @param stationError the currently showing error
@@ -1,8 +1,5 @@
1
1
  export { Create, CreateProps } from './Create/Create';
2
2
  export { Details, DetailsProps } from './Details/Details';
3
- export {
4
- FormStation,
5
- FormStationProps,
6
- ObjectSchemaDefinition,
7
- } from './FormStation';
3
+ export { FormGrid } from './FormGrid';
4
+ export { FormStation, FormStationProps } from './FormStation';
8
5
  export * from './FormStation.models';
@@ -1,13 +0,0 @@
1
- import { ErrorType } from '../components/models';
2
- /**
3
- * Type assertion function that throws an error if provided parameter is not an instance of an Error, and asserts value to an Error type if no error is thrown.
4
- * This is a copy of assertError() in @axinom/service-common to avoid dependencies.
5
- */
6
- export declare const assertError: (error: unknown) => asserts error is Error;
7
- /**
8
- * Asserts if the passed error is of ErrorType.
9
- *
10
- * @param error error object of type unknown
11
- */
12
- export declare const assertErrorType: (error: unknown) => asserts error is ErrorType;
13
- //# sourceMappingURL=assertError.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"assertError.d.ts","sourceRoot":"","sources":["../../src/common/assertError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAgB,MAAM,sBAAsB,CAAC;AAE/D;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,KAAK,IAAI,KAM9D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,KAAK,IAAI,SAalE,CAAC"}
@@ -1,5 +0,0 @@
1
- import { StationError } from '../models';
2
- export interface StationErrorStateType extends StationError {
3
- type?: string;
4
- }
5
- //# sourceMappingURL=StationErrorStateType.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"StationErrorStateType.d.ts","sourceRoot":"","sources":["../../../src/components/FormStation/StationErrorStateType.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,WAAW,qBAAsB,SAAQ,YAAY;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useValidationError.d.ts","sourceRoot":"","sources":["../../../src/components/FormStation/useValidationError.tsx"],"names":[],"mappings":"AACA,OAAO,KAA2C,MAAM,OAAO,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAehE;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,qBAAqB,GAAG,SAAS,EAC/C,eAAe,EAAE,KAAK,CAAC,QAAQ,CAC7B,KAAK,CAAC,cAAc,CAAC,qBAAqB,GAAG,SAAS,CAAC,CACxD,GACA;IACD,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,iBAAiB,EAAE,GAAG,CAAC,OAAO,CAAC;CAChC,CAyBA"}
@@ -1,61 +0,0 @@
1
- /* eslint-disable jest/no-conditional-expect */
2
- import 'jest-extended';
3
- import { StationError } from '../components/models';
4
- import { assertError, assertErrorType } from './assertError';
5
-
6
- describe('assertError', () => {
7
- it('StationError type error is thrown -> ErrorType asserted', async () => {
8
- try {
9
- throw {
10
- title: 'test title',
11
- body: 'test body',
12
- };
13
- } catch (error) {
14
- assertErrorType(error);
15
- expect((error as StationError).title).toBe('test title');
16
- expect((error as StationError).body).toBe('test body');
17
- }
18
- });
19
-
20
- it('Error is thrown -> ErrorType asserted', async () => {
21
- try {
22
- throw new Error('test error');
23
- } catch (error) {
24
- assertErrorType(error);
25
- expect((error as Error).message).toBe('test error');
26
- }
27
- });
28
-
29
- it('string is thrown -> ErrorType asserted', async () => {
30
- try {
31
- throw 'test error';
32
- } catch (error) {
33
- assertErrorType(error);
34
- expect(error as string).toBe('test error');
35
- }
36
- });
37
-
38
- it('error-like object is thrown -> ErrorType asserted', async () => {
39
- try {
40
- throw { message: 'test error' };
41
- } catch (error) {
42
- assertErrorType(error);
43
- expect(Object(error).message).toBe('test error');
44
- }
45
- });
46
-
47
- it('number is thrown -> ErrorType assertion failed', async () => {
48
- try {
49
- throw 123;
50
- } catch (error) {
51
- try {
52
- assertErrorType(error);
53
- } catch (assertionError) {
54
- assertError(assertionError);
55
- expect(assertionError.message).toBe(
56
- 'The caught error is not an instance of ErrorType.',
57
- );
58
- }
59
- }
60
- });
61
- });
@@ -1,33 +0,0 @@
1
- import { ErrorType, StationError } from '../components/models';
2
-
3
- /**
4
- * Type assertion function that throws an error if provided parameter is not an instance of an Error, and asserts value to an Error type if no error is thrown.
5
- * This is a copy of assertError() in @axinom/service-common to avoid dependencies.
6
- */
7
- export const assertError: (error: unknown) => asserts error is Error = (
8
- error: unknown,
9
- ): asserts error is Error => {
10
- if (!(error instanceof Error)) {
11
- throw new Error('A caught error is not an instance of an Error class.');
12
- }
13
- };
14
-
15
- /**
16
- * Asserts if the passed error is of ErrorType.
17
- *
18
- * @param error error object of type unknown
19
- */
20
- export const assertErrorType: (error: unknown) => asserts error is ErrorType = (
21
- error: unknown,
22
- ): asserts error is ErrorType => {
23
- if (
24
- !(
25
- error instanceof Error ||
26
- typeof error === 'string' ||
27
- (error as StationError).title !== undefined ||
28
- error instanceof Object
29
- )
30
- ) {
31
- throw new Error('The caught error is not an instance of ErrorType.');
32
- }
33
- };
@@ -1,5 +0,0 @@
1
- import { StationError } from '../models';
2
-
3
- export interface StationErrorStateType extends StationError {
4
- type?: string;
5
- }