@axinom/mosaic-ui 0.42.0 → 0.43.0-rc.1

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 (88) 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/components/Tabs/Tab/CustomTab.d.ts +3 -0
  28. package/dist/components/Tabs/Tab/CustomTab.d.ts.map +1 -0
  29. package/dist/components/Tabs/Tab/index.d.ts +2 -0
  30. package/dist/components/Tabs/Tab/index.d.ts.map +1 -0
  31. package/dist/components/Tabs/TabList/CustomTabList.d.ts +3 -0
  32. package/dist/components/Tabs/TabList/CustomTabList.d.ts.map +1 -0
  33. package/dist/components/Tabs/TabList/ScrollContainer/ScrollContainer.d.ts +3 -0
  34. package/dist/components/Tabs/TabList/ScrollContainer/ScrollContainer.d.ts.map +1 -0
  35. package/dist/components/Tabs/TabList/ScrollContainer/index.d.ts +2 -0
  36. package/dist/components/Tabs/TabList/ScrollContainer/index.d.ts.map +1 -0
  37. package/dist/components/Tabs/TabList/ScrollContainer/useScroll.d.ts +10 -0
  38. package/dist/components/Tabs/TabList/ScrollContainer/useScroll.d.ts.map +1 -0
  39. package/dist/components/Tabs/TabList/index.d.ts +2 -0
  40. package/dist/components/Tabs/TabList/index.d.ts.map +1 -0
  41. package/dist/components/Tabs/TabPanel/CustomTabPanel.d.ts +3 -0
  42. package/dist/components/Tabs/TabPanel/CustomTabPanel.d.ts.map +1 -0
  43. package/dist/components/Tabs/TabPanel/index.d.ts +2 -0
  44. package/dist/components/Tabs/TabPanel/index.d.ts.map +1 -0
  45. package/dist/components/Tabs/index.d.ts +5 -0
  46. package/dist/components/Tabs/index.d.ts.map +1 -0
  47. package/dist/index.es.js +4 -4
  48. package/dist/index.es.js.map +1 -1
  49. package/dist/index.js +4 -4
  50. package/dist/index.js.map +1 -1
  51. package/package.json +4 -3
  52. package/src/components/FormStation/FormGrid/FormGrid.scss +10 -0
  53. package/src/components/FormStation/FormGrid/FormGrid.tsx +25 -0
  54. package/src/components/FormStation/FormGrid/index.ts +1 -0
  55. package/src/components/FormStation/FormStation.models.ts +28 -2
  56. package/src/components/FormStation/FormStation.scss +1 -117
  57. package/src/components/FormStation/FormStation.stories.tsx +166 -1
  58. package/src/components/FormStation/FormStation.tsx +39 -388
  59. package/src/components/FormStation/FormStationActions/FormStationActions.tsx +130 -0
  60. package/src/components/FormStation/FormStationActions/index.ts +1 -0
  61. package/src/components/FormStation/FormStationContentWrapper/FormStationContentWrapper.scss +66 -0
  62. package/src/components/FormStation/FormStationContentWrapper/FormStationContentWrapper.tsx +76 -0
  63. package/src/components/FormStation/FormStationContentWrapper/index.ts +1 -0
  64. package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +88 -0
  65. package/src/components/FormStation/FormStationHeader/index.ts +1 -0
  66. package/src/components/FormStation/helpers/useDataProvider.ts +124 -0
  67. package/src/components/FormStation/{useValidationError.tsx → helpers/useValidationError.tsx} +2 -1
  68. package/src/components/FormStation/index.ts +2 -5
  69. package/src/components/List/ListRow/Renderers/ExternalLinkRenderer/ExternalLinkRenderer.tsx +1 -1
  70. package/src/components/Tabs/Tab/CustomTab.scss +42 -0
  71. package/src/components/Tabs/Tab/CustomTab.tsx +34 -0
  72. package/src/components/Tabs/Tab/index.ts +1 -0
  73. package/src/components/Tabs/TabList/CustomTabList.scss +7 -0
  74. package/src/components/Tabs/TabList/CustomTabList.tsx +15 -0
  75. package/src/components/Tabs/TabList/ScrollContainer/ScrollContainer.scss +34 -0
  76. package/src/components/Tabs/TabList/ScrollContainer/ScrollContainer.tsx +39 -0
  77. package/src/components/Tabs/TabList/ScrollContainer/index.ts +1 -0
  78. package/src/components/Tabs/TabList/ScrollContainer/useScroll.ts +114 -0
  79. package/src/components/Tabs/TabList/index.ts +1 -0
  80. package/src/components/Tabs/TabPanel/CustomTabPanel.scss +10 -0
  81. package/src/components/Tabs/TabPanel/CustomTabPanel.tsx +26 -0
  82. package/src/components/Tabs/TabPanel/index.ts +1 -0
  83. package/src/components/Tabs/Tabs.stories.tsx +108 -0
  84. package/src/components/Tabs/index.ts +4 -0
  85. package/dist/components/FormStation/StationErrorStateType.d.ts +0 -5
  86. package/dist/components/FormStation/StationErrorStateType.d.ts.map +0 -1
  87. package/dist/components/FormStation/useValidationError.d.ts.map +0 -1
  88. package/src/components/FormStation/StationErrorStateType.tsx +0 -5
@@ -1,51 +1,22 @@
1
1
  import clsx from 'clsx';
2
- import {
3
- Form,
4
- Formik,
5
- FormikHelpers,
6
- FormikValues,
7
- useFormikContext,
8
- } from 'formik';
9
- import React, {
10
- PropsWithChildren,
11
- useCallback,
12
- useEffect,
13
- useMemo,
14
- useRef,
15
- useState,
16
- } from 'react';
17
- import { useHistory } from 'react-router-dom';
2
+ import { Formik, FormikValues } from 'formik';
3
+ import React, { PropsWithChildren } from 'react';
18
4
  import { OptionalObjectSchema } from 'yup/lib/object';
19
- import { SaveIndicatorType, setSaveIndicator } from '../../initialize';
20
5
  import { Data } from '../../types/data';
21
- import { ErrorTypeToStationError } from '../../utils/ErrorTypeToStationError';
22
- import { Actions, ActionsProps } from '../Actions';
23
- import { isNavigationAction } from '../Actions/Action/Action';
24
- import { IconName } from '../Icons';
25
- import { MessageBar } from '../MessageBar';
6
+ import { StationMessage } from '../models';
26
7
  import {
27
- PageHeader,
28
- PageHeaderActionType,
29
- PageHeaderProps,
30
- } from '../PageHeader';
31
- import { ErrorType, StationMessage } from '../models';
32
- import { FormActionData, InitialFormData } from './FormStation.models';
8
+ FormActionData,
9
+ InitialFormData,
10
+ ObjectSchemaDefinition,
11
+ SaveDataFunction,
12
+ } from './FormStation.models';
33
13
  import classes from './FormStation.scss';
14
+ import { FormStationAction } from './FormStationActions';
15
+ import { FormStationContentWrapper } from './FormStationContentWrapper';
16
+ import { FormStationHeader } from './FormStationHeader';
34
17
  import { SaveOnNavigate } from './SaveOnNavigate/SaveOnNavigate';
35
- import { StationErrorStateType } from './StationErrorStateType';
36
- import { useValidationError } from './useValidationError';
37
-
38
- export type ObjectSchemaDefinition<
39
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
- T extends Data | null = any,
41
- > = {
42
- // TODO: Adding 'any' here since there are a couple of open issues regarding generics in the latest version of yup.
43
- // https://github.com/jquense/yup/issues/1159
44
- // https://github.com/jquense/yup/issues/1247
45
- // Consider revisiting once 'yup' has addressed these issues.
46
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
- [field in keyof T]: any;
48
- };
18
+ import { useDataProvider } from './helpers/useDataProvider';
19
+ import { useValidationError } from './helpers/useValidationError';
49
20
 
50
21
  export interface FormStationProps<
51
22
  TValues extends Data = FormikValues,
@@ -89,31 +60,11 @@ export interface FormStationProps<
89
60
  * Called whenever the form needs to be saved.
90
61
  * This method needs to throw an exception in case the saving did not succeed.
91
62
  */
92
- saveData: (
93
- /** The current values of the form */
94
- values: TValues,
95
- /** The initial values of the form */
96
- initialData: InitialFormData<TValues>,
97
- /** The Formik state helpers */
98
- formikHelpers: FormikHelpers<TValues>,
99
- ) => Promise<TSubmitResponse> | void;
63
+ saveData: SaveDataFunction<TValues, TSubmitResponse>;
100
64
  /** CSS Class name for additional styles */
101
65
  className?: string;
102
66
  }
103
67
 
104
- interface FormActionProps<T, Y> extends Omit<ActionsProps, 'actions'> {
105
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
- validationSchema?: OptionalObjectSchema<ObjectSchemaDefinition<any>>;
107
- actions?: FormActionData<T, Y>[];
108
- setStationError: (error: StationErrorStateType) => void;
109
- setValidationError: () => void;
110
- submitResponse?: React.MutableRefObject<Y | undefined>;
111
- alwaysSubmitBeforeAction?: boolean;
112
- className?: string;
113
- isFormSubmitting?: boolean;
114
- }
115
-
116
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
117
68
  export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
118
69
  titleProperty,
119
70
  defaultTitle,
@@ -134,138 +85,19 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
134
85
  }: PropsWithChildren<
135
86
  FormStationProps<TValues, TSubmitResponse>
136
87
  >): JSX.Element => {
137
- const [stationError, setStationError] = useState<StationErrorStateType>();
88
+ const {
89
+ onSubmit,
90
+ stationError,
91
+ setStationError,
92
+ isFormSubmitting,
93
+ lastSubmittedResponse,
94
+ } = useDataProvider(initialData, saveData);
138
95
 
139
96
  const { setValidationError, validationWatcher } = useValidationError(
140
97
  stationError,
141
98
  setStationError,
142
99
  );
143
100
 
144
- const submitResponse = useRef<TSubmitResponse>();
145
- const [isFormSubmitting, setIsFormSubmitting] = useState<boolean>(false);
146
-
147
- const onSubmit = useCallback(
148
- async (
149
- values: TValues,
150
- formikHelpers: FormikHelpers<TValues>,
151
- ): Promise<void> => {
152
- if (isFormSubmitting) {
153
- return;
154
- }
155
-
156
- try {
157
- setIsFormSubmitting(true);
158
- setSaveIndicator(SaveIndicatorType.Saving);
159
- setStationError(undefined);
160
- if (!initialData.loading && saveData) {
161
- const response = await saveData(values, initialData, formikHelpers);
162
- if (response) {
163
- submitResponse.current = response;
164
- }
165
- }
166
- } catch (error) {
167
- setStationError(
168
- ErrorTypeToStationError(
169
- error as ErrorType,
170
- 'An error occurred when trying to save data.',
171
- ),
172
- );
173
-
174
- setSaveIndicator(SaveIndicatorType.Dirty);
175
-
176
- // We still throw the error, to make sure that navigation or action execution
177
- // will not continue after a failed save.
178
- throw error;
179
- } finally {
180
- formikHelpers.setSubmitting(false);
181
- setIsFormSubmitting(false);
182
- }
183
- },
184
- [isFormSubmitting, initialData, saveData, setStationError],
185
- );
186
-
187
- useEffect(() => {
188
- if (
189
- initialData.error ||
190
- (initialData.data === null && !initialData.loading) ||
191
- initialData.entityNotFound
192
- ) {
193
- const stationError = {
194
- ...ErrorTypeToStationError(
195
- initialData.error,
196
- 'An error occurred when trying to load data.',
197
- 'Entity not found',
198
- ),
199
- type: 'loading',
200
- };
201
-
202
- setStationError(stationError);
203
- } else {
204
- if (stationError?.type === 'loading') {
205
- // Only clear the error if it is a loading error, which now seems to be cleared.
206
- setStationError(undefined);
207
- }
208
- }
209
- }, [
210
- initialData.loading,
211
- initialData.error,
212
- initialData.entityNotFound,
213
- initialData.data,
214
- stationError?.type,
215
- ]);
216
-
217
- const getContent = (): JSX.Element | undefined => {
218
- if (initialData.loading) {
219
- // TODO: Loading skeleton of the page
220
- return;
221
- }
222
-
223
- if (
224
- initialData.error ||
225
- initialData.data === null ||
226
- initialData.entityNotFound
227
- ) {
228
- // Error on loading - we can't show the form
229
- return <div className={classes.loadingError}></div>;
230
- }
231
-
232
- // Loading successful
233
- return (
234
- <>
235
- <SaveOnNavigate
236
- isSubmitting={isFormSubmitting}
237
- onNavigationCancelled={setValidationError}
238
- />
239
- <div className={classes.main}>
240
- <div
241
- className={clsx(classes.formWrapper, {
242
- [classes.hasMessage]: stationMessage,
243
- })}
244
- >
245
- {stationMessage && (
246
- <MessageBar {...stationMessage}>{stationMessage.body}</MessageBar>
247
- )}
248
- <div
249
- className={clsx(classes.formContainer, {
250
- [classes.paddedContent]: !edgeToEdgeContent,
251
- })}
252
- >
253
- <Form>
254
- <>
255
- {children}
256
- {/* 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
257
- See: https://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2 */}
258
- <input type="text" style={{ display: 'none' }} />
259
- </>
260
- </Form>
261
- </div>
262
- </div>
263
- {infoPanel}
264
- </div>
265
- </>
266
- );
267
- };
268
-
269
101
  return (
270
102
  <div
271
103
  className={clsx(
@@ -290,217 +122,36 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
290
122
  defaultTitle={defaultTitle}
291
123
  subtitle={subtitle}
292
124
  cancelNavigationUrl={cancelNavigationUrl}
125
+ className={classes.header}
293
126
  />
294
- {stationError && (
295
- <div className={classes.errorMessage}>
296
- <MessageBar
297
- type="error"
298
- title={String(stationError.title)}
299
- onClose={() => setStationError(undefined)}
300
- >
301
- {stationError?.body}
302
- </MessageBar>
303
- </div>
304
- )}
305
- <div className={classes.children}>{getContent()}</div>
306
- {alwaysShowActionsPanel || (actions?.length ?? 0) > 0 ? (
127
+ <SaveOnNavigate
128
+ isSubmitting={isFormSubmitting}
129
+ onNavigationCancelled={setValidationError}
130
+ />
131
+ <FormStationContentWrapper
132
+ stationMessage={stationMessage}
133
+ edgeToEdgeContent={edgeToEdgeContent}
134
+ infoPanel={infoPanel}
135
+ initialData={initialData}
136
+ stationError={stationError}
137
+ setStationError={setStationError}
138
+ >
139
+ {children}
140
+ </FormStationContentWrapper>
141
+ {(alwaysShowActionsPanel || (actions?.length ?? 0) > 0) && (
307
142
  <FormStationAction<TValues, TSubmitResponse>
308
143
  actions={actions}
309
144
  width={actionsWidth}
310
145
  validationSchema={validationSchema}
311
146
  setStationError={setStationError}
312
147
  setValidationError={setValidationError}
313
- submitResponse={submitResponse}
148
+ submitResponse={lastSubmittedResponse}
314
149
  alwaysSubmitBeforeAction={alwaysSubmitBeforeAction}
315
150
  className={classes.actionsPanel}
316
151
  />
317
- ) : null}
152
+ )}
318
153
  </>
319
154
  </Formik>
320
155
  </div>
321
156
  );
322
157
  };
323
-
324
- /**
325
- * Saves the form before the action is performed.
326
- */
327
- const FormStationAction = <T, Y>(
328
- props: PropsWithChildren<FormActionProps<T, Y>>,
329
- ): JSX.Element => {
330
- const {
331
- actions,
332
- validationSchema,
333
- setStationError,
334
- setValidationError,
335
- submitResponse,
336
- alwaysSubmitBeforeAction,
337
- className = '',
338
- } = props;
339
- const {
340
- submitForm,
341
- resetForm,
342
- values,
343
- validateForm,
344
- isValid,
345
- dirty,
346
- isSubmitting,
347
- } = useFormikContext<T>();
348
-
349
- const updatedActions = useMemo(() => {
350
- return actions?.map((action) => {
351
- const { onActionSelected } = action;
352
-
353
- return isNavigationAction(action)
354
- ? action
355
- : {
356
- ...action,
357
- isDisabled: action.isDisabled || isSubmitting,
358
- onActionSelected: () => {
359
- (async () => {
360
- //TODO: Busy indicator (disable form?)
361
- if (dirty || alwaysSubmitBeforeAction) {
362
- // We can't rely on 'isValid' alone, since that will only evaluate if the form is touched or 'submitForm' is called.
363
- // On a create station though the from might not be touched but still being invalid.
364
- // Also the validation on submitForm in here will not help, since the value of isValid is already bound to that method and will not change
365
- // anymore while this method executes - even if it changes on the context!
366
- if (
367
- !isValid ||
368
- (await validationSchema?.isValid(values)) === false
369
- ) {
370
- setValidationError();
371
- // Making sure that the fields will actually show the validation messages (they won't if the from was not touched yet - e.g. on a create station)
372
- validateForm();
373
- return;
374
- }
375
-
376
- try {
377
- await submitForm();
378
-
379
- // Committing the changed values to the form to get the current version as the new "reset" state.
380
- resetForm({ values });
381
- } catch (error) {
382
- // we will abort the action if saving is not successful
383
- // a station error is already set by the "onSubmit"
384
- return;
385
- }
386
- }
387
-
388
- try {
389
- const result =
390
- onActionSelected &&
391
- (await onActionSelected({
392
- values,
393
- submitResponse: submitResponse?.current,
394
- }));
395
- if (result !== undefined) {
396
- setStationError(
397
- ErrorTypeToStationError(
398
- result,
399
- 'An error occurred when trying to execute the action.',
400
- ),
401
- );
402
- }
403
- } catch (error) {
404
- const stationError = ErrorTypeToStationError(
405
- error as ErrorType,
406
- 'An error occurred when trying to execute the action.',
407
- );
408
- setStationError(stationError);
409
- }
410
- })();
411
- },
412
- };
413
- });
414
- }, [
415
- actions,
416
- alwaysSubmitBeforeAction,
417
- dirty,
418
- isSubmitting,
419
- isValid,
420
- resetForm,
421
- setStationError,
422
- setValidationError,
423
- submitForm,
424
- submitResponse,
425
- validateForm,
426
- validationSchema,
427
- values,
428
- ]);
429
-
430
- return (
431
- <div className={className}>
432
- <Actions actions={updatedActions} />
433
- </div>
434
- );
435
- };
436
-
437
- /**
438
- * Handles showRefresh and cancel buttons based on form states
439
- */
440
- const FormStationHeader: React.FC<
441
- Omit<PageHeaderProps, 'title'> & {
442
- titleProperty?: string;
443
- defaultTitle?: string;
444
- cancelNavigationUrl?: string;
445
- }
446
- > = ({ titleProperty, defaultTitle, subtitle, cancelNavigationUrl }) => {
447
- const { dirty, resetForm, values } = useFormikContext<FormikValues>();
448
-
449
- useEffect(() => {
450
- // Set the save indicator to dirty depending on the form state
451
- if (dirty) {
452
- setSaveIndicator(SaveIndicatorType.Dirty);
453
- } else {
454
- setSaveIndicator(SaveIndicatorType.Inactive);
455
- }
456
- return () => {
457
- // The form is not always considered "not dirty" after the save
458
- // so this code will make sure that the indicator is set to inactive
459
- // when the station is left.
460
- setSaveIndicator(SaveIndicatorType.Inactive);
461
- };
462
- }, [dirty]);
463
-
464
- const history = useHistory();
465
-
466
- const title =
467
- titleProperty && values[titleProperty] !== ''
468
- ? values[titleProperty]
469
- : defaultTitle ?? '';
470
-
471
- return (
472
- <PageHeader
473
- title={title}
474
- subtitle={subtitle}
475
- actions={[
476
- ...(dirty === true // add undo action if form as been altered
477
- ? [
478
- {
479
- label: 'Undo Changes',
480
- icon: IconName.Undo,
481
- actionType: PageHeaderActionType.Context,
482
- onClick: () => {
483
- resetForm();
484
- },
485
- },
486
- ]
487
- : []),
488
- ...(cancelNavigationUrl // add cancel action if applicable
489
- ? [
490
- {
491
- label: 'Cancel',
492
- icon: IconName.X,
493
- onClick: () => {
494
- resetForm();
495
- // If the form has errors, Navigation needs to be wrapped in a promise or timeout.
496
- Promise.resolve().then(() =>
497
- history.push(cancelNavigationUrl),
498
- );
499
- },
500
- },
501
- ]
502
- : []),
503
- ]}
504
- />
505
- );
506
- };
@@ -0,0 +1,130 @@
1
+ import { useFormikContext } from 'formik';
2
+ import React, { PropsWithChildren, useMemo } from 'react';
3
+ import { OptionalObjectSchema } from 'yup/lib/object';
4
+ import { ObjectSchemaDefinition } from '..';
5
+ import { ErrorTypeToStationError } from '../../../utils/ErrorTypeToStationError';
6
+ import { Actions, ActionsProps } from '../../Actions';
7
+ import { isNavigationAction } from '../../Actions/Action/Action';
8
+ import { ErrorType } from '../../models';
9
+ import { FormActionData, StationErrorStateType } from '../FormStation.models';
10
+
11
+ interface FormActionProps<T, Y> extends Omit<ActionsProps, 'actions'> {
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ validationSchema?: OptionalObjectSchema<ObjectSchemaDefinition<any>>;
14
+ actions?: FormActionData<T, Y>[];
15
+ setStationError: (error: StationErrorStateType) => void;
16
+ setValidationError: () => void;
17
+ submitResponse?: React.MutableRefObject<Y | undefined>;
18
+ alwaysSubmitBeforeAction?: boolean;
19
+ className?: string;
20
+ isFormSubmitting?: boolean;
21
+ }
22
+
23
+ /**
24
+ * Saves the form before the action is performed.
25
+ */
26
+ export const FormStationAction = <T, Y>(
27
+ props: PropsWithChildren<FormActionProps<T, Y>>,
28
+ ): JSX.Element => {
29
+ const {
30
+ actions,
31
+ validationSchema,
32
+ setStationError,
33
+ setValidationError,
34
+ submitResponse,
35
+ alwaysSubmitBeforeAction,
36
+ className = '',
37
+ } = props;
38
+ const {
39
+ submitForm,
40
+ resetForm,
41
+ values,
42
+ validateForm,
43
+ isValid,
44
+ dirty,
45
+ isSubmitting,
46
+ } = useFormikContext<T>();
47
+
48
+ const updatedActions = useMemo(() => {
49
+ return actions?.map((action) => {
50
+ const { onActionSelected } = action;
51
+
52
+ return isNavigationAction(action)
53
+ ? action
54
+ : {
55
+ ...action,
56
+ isDisabled: action.isDisabled || isSubmitting,
57
+ onActionSelected: () => {
58
+ (async () => {
59
+ //TODO: Busy indicator (disable form?)
60
+ if (dirty || alwaysSubmitBeforeAction) {
61
+ // We can't rely on 'isValid' alone, since that will only evaluate if the form is touched or 'submitForm' is called.
62
+ // On a create station though the from might not be touched but still being invalid.
63
+ // Also the validation on submitForm in here will not help, since the value of isValid is already bound to that method and will not change
64
+ // anymore while this method executes - even if it changes on the context!
65
+ if (
66
+ !isValid ||
67
+ (await validationSchema?.isValid(values)) === false
68
+ ) {
69
+ setValidationError();
70
+ // Making sure that the fields will actually show the validation messages (they won't if the from was not touched yet - e.g. on a create station)
71
+ validateForm();
72
+ return;
73
+ }
74
+
75
+ try {
76
+ await submitForm();
77
+
78
+ // Committing the changed values to the form to get the current version as the new "reset" state.
79
+ resetForm({ values });
80
+ } catch (error) {
81
+ // we will abort the action if saving is not successful
82
+ // a station error is already set by the "onSubmit"
83
+ return;
84
+ }
85
+ }
86
+
87
+ try {
88
+ const result =
89
+ onActionSelected &&
90
+ (await onActionSelected({
91
+ values,
92
+ submitResponse: submitResponse?.current,
93
+ }));
94
+ if (result !== undefined) {
95
+ setStationError(
96
+ ErrorTypeToStationError(
97
+ result,
98
+ 'An error occurred when trying to execute the action.',
99
+ ),
100
+ );
101
+ }
102
+ } catch (error) {
103
+ const stationError = ErrorTypeToStationError(
104
+ error as ErrorType,
105
+ 'An error occurred when trying to execute the action.',
106
+ );
107
+ setStationError(stationError);
108
+ }
109
+ })();
110
+ },
111
+ };
112
+ });
113
+ }, [
114
+ actions,
115
+ alwaysSubmitBeforeAction,
116
+ dirty,
117
+ isSubmitting,
118
+ isValid,
119
+ resetForm,
120
+ setStationError,
121
+ setValidationError,
122
+ submitForm,
123
+ submitResponse,
124
+ validateForm,
125
+ validationSchema,
126
+ values,
127
+ ]);
128
+
129
+ return <Actions actions={updatedActions} className={className} />;
130
+ };
@@ -0,0 +1 @@
1
+ export * from './FormStationActions';
@@ -0,0 +1,66 @@
1
+ @import '../../../styles/common.scss';
2
+
3
+ // Extend all components, except PageHeader, to the bottom
4
+ // Enable scrolling children
5
+ .children {
6
+ overflow-y: auto;
7
+ display: grid;
8
+ grid-template-columns: 1fr auto;
9
+
10
+ overflow: hidden;
11
+
12
+ .main {
13
+ display: grid;
14
+ grid: 1fr / minmax(740px, 1fr) auto;
15
+
16
+ overflow-y: auto;
17
+ scrollbar-width: thin; //for Firefox only
18
+
19
+ .formWrapper {
20
+ display: grid;
21
+ grid: 1fr / 1fr;
22
+
23
+ &.hasMessage {
24
+ grid: min-content 1fr / 1fr;
25
+ }
26
+ }
27
+ }
28
+
29
+ //Custom scrollbar for Chrome, Safari and Edge
30
+ ::-webkit-scrollbar {
31
+ width: var(--scrollbar-size, $scrollbar-size);
32
+ height: var(--scrollbar-size, $scrollbar-size);
33
+ }
34
+ ::-webkit-scrollbar-track {
35
+ background: var(--scrollbar-track-color, $scrollbar-track-color);
36
+ }
37
+
38
+ ::-webkit-scrollbar-thumb {
39
+ background: var(--scrollbar-thumb-color, $scrollbar-thumb-color);
40
+ }
41
+
42
+ ::-webkit-scrollbar-thumb:hover {
43
+ background: var(
44
+ --scrollbar-thumb-hover-color,
45
+ $scrollbar-thumb-hover-color
46
+ );
47
+ }
48
+
49
+ ::-webkit-scrollbar-corner {
50
+ background: var(--scrollbar-corner-color, $scrollbar-corner-color);
51
+ }
52
+
53
+ .formContainer {
54
+ padding: 30px;
55
+ }
56
+
57
+ .loadingError {
58
+ display: grid;
59
+ grid: 1fr / 1fr;
60
+ }
61
+ }
62
+
63
+ .errorMessage {
64
+ grid-row: 2 / 3;
65
+ grid-column: 1 / -1;
66
+ }