@jobber/components-native 0.39.0 → 0.40.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 (136) hide show
  1. package/dist/src/Form/Form.js +187 -0
  2. package/dist/src/Form/Form.style.js +33 -0
  3. package/dist/src/Form/components/FormActionBar/FormActionBar.js +21 -0
  4. package/dist/src/Form/components/FormActionBar/FormActionBar.style.js +5 -0
  5. package/dist/src/Form/components/FormActionBar/index.js +1 -0
  6. package/dist/src/Form/components/FormBody/FormBody.js +20 -0
  7. package/dist/src/Form/components/FormBody/FormBody.style.js +26 -0
  8. package/dist/src/Form/components/FormBody/index.js +1 -0
  9. package/dist/src/Form/components/FormCache/FormCache.js +34 -0
  10. package/dist/src/Form/components/FormErrorBanner/FormErrorBanner.js +21 -0
  11. package/dist/src/Form/components/FormErrorBanner/index.js +1 -0
  12. package/dist/src/Form/components/FormErrorBanner/messages.js +13 -0
  13. package/dist/src/Form/components/FormMask/FormMask.js +11 -0
  14. package/dist/src/Form/components/FormMask/FormMask.style.js +15 -0
  15. package/dist/src/Form/components/FormMask/index.js +1 -0
  16. package/dist/src/Form/components/FormMessage/FormMessage.js +48 -0
  17. package/dist/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.js +28 -0
  18. package/dist/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.style.js +17 -0
  19. package/dist/src/Form/components/FormMessage/components/InternalFormMessage/index.js +1 -0
  20. package/dist/src/Form/components/FormMessage/components/InternalFormMessage/messages.js +8 -0
  21. package/dist/src/Form/components/FormMessage/index.js +1 -0
  22. package/dist/src/Form/components/FormMessageBanner/FormMessageBanner.js +15 -0
  23. package/dist/src/Form/components/FormMessageBanner/index.js +1 -0
  24. package/dist/src/Form/components/FormSaveButton/FormSaveButton.js +69 -0
  25. package/dist/src/Form/components/FormSaveButton/index.js +1 -0
  26. package/dist/src/Form/components/FormSaveButton/messages.js +8 -0
  27. package/dist/src/Form/constants.js +2 -0
  28. package/dist/src/Form/context/AtlantisFormContext.js +16 -0
  29. package/dist/src/Form/context/index.js +1 -0
  30. package/dist/src/Form/context/types.js +1 -0
  31. package/dist/src/Form/hooks/useFormViewRefs.js +14 -0
  32. package/dist/src/Form/hooks/useInternalForm.js +37 -0
  33. package/dist/src/Form/hooks/useOfflineHandler.js +24 -0
  34. package/dist/src/Form/hooks/useSaveButtonPosition.js +25 -0
  35. package/dist/src/Form/hooks/useScreenInformation.js +15 -0
  36. package/dist/src/Form/hooks/useScrollToError/index.js +1 -0
  37. package/dist/src/Form/hooks/useScrollToError/useScrollToError.js +63 -0
  38. package/dist/src/Form/index.js +4 -0
  39. package/dist/src/Form/messages.js +28 -0
  40. package/dist/src/Form/types.js +10 -0
  41. package/dist/src/InputText/InputText.js +8 -1
  42. package/dist/src/index.js +1 -0
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/dist/types/src/Form/Form.d.ts +4 -0
  45. package/dist/types/src/Form/Form.style.d.ts +31 -0
  46. package/dist/types/src/Form/components/FormActionBar/FormActionBar.d.ts +13 -0
  47. package/dist/types/src/Form/components/FormActionBar/FormActionBar.style.d.ts +15 -0
  48. package/dist/types/src/Form/components/FormActionBar/index.d.ts +2 -0
  49. package/dist/types/src/Form/components/FormBody/FormBody.d.ts +10 -0
  50. package/dist/types/src/Form/components/FormBody/FormBody.style.d.ts +24 -0
  51. package/dist/types/src/Form/components/FormBody/index.d.ts +1 -0
  52. package/dist/types/src/Form/components/FormCache/FormCache.d.ts +10 -0
  53. package/dist/types/src/Form/components/FormErrorBanner/FormErrorBanner.d.ts +3 -0
  54. package/dist/types/src/Form/components/FormErrorBanner/index.d.ts +1 -0
  55. package/dist/types/src/Form/components/FormErrorBanner/messages.d.ts +12 -0
  56. package/dist/types/src/Form/components/FormMask/FormMask.d.ts +2 -0
  57. package/dist/types/src/Form/components/FormMask/FormMask.style.d.ts +13 -0
  58. package/dist/types/src/Form/components/FormMask/index.d.ts +1 -0
  59. package/dist/types/src/Form/components/FormMessage/FormMessage.d.ts +19 -0
  60. package/dist/types/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.d.ts +8 -0
  61. package/dist/types/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.style.d.ts +20 -0
  62. package/dist/types/src/Form/components/FormMessage/components/InternalFormMessage/index.d.ts +1 -0
  63. package/dist/types/src/Form/components/FormMessage/components/InternalFormMessage/messages.d.ts +7 -0
  64. package/dist/types/src/Form/components/FormMessage/index.d.ts +1 -0
  65. package/dist/types/src/Form/components/FormMessageBanner/FormMessageBanner.d.ts +7 -0
  66. package/dist/types/src/Form/components/FormMessageBanner/index.d.ts +1 -0
  67. package/dist/types/src/Form/components/FormSaveButton/FormSaveButton.d.ts +3 -0
  68. package/dist/types/src/Form/components/FormSaveButton/index.d.ts +1 -0
  69. package/dist/types/src/Form/components/FormSaveButton/messages.d.ts +7 -0
  70. package/dist/types/src/Form/constants.d.ts +2 -0
  71. package/dist/types/src/Form/context/AtlantisFormContext.d.ts +12 -0
  72. package/dist/types/src/Form/context/index.d.ts +2 -0
  73. package/dist/types/src/Form/context/types.d.ts +26 -0
  74. package/dist/types/src/Form/hooks/useFormViewRefs.d.ts +10 -0
  75. package/dist/types/src/Form/hooks/useInternalForm.d.ts +19 -0
  76. package/dist/types/src/Form/hooks/useOfflineHandler.d.ts +1 -0
  77. package/dist/types/src/Form/hooks/useSaveButtonPosition.d.ts +12 -0
  78. package/dist/types/src/Form/hooks/useScreenInformation.d.ts +8 -0
  79. package/dist/types/src/Form/hooks/useScrollToError/index.d.ts +1 -0
  80. package/dist/types/src/Form/hooks/useScrollToError/useScrollToError.d.ts +10 -0
  81. package/dist/types/src/Form/index.d.ts +5 -0
  82. package/dist/types/src/Form/messages.d.ts +27 -0
  83. package/dist/types/src/Form/types.d.ts +199 -0
  84. package/dist/types/src/InputNumber/InputNumber.d.ts +1 -1
  85. package/dist/types/src/index.d.ts +1 -0
  86. package/package.json +3 -2
  87. package/src/Form/Form.style.ts +34 -0
  88. package/src/Form/Form.test.tsx +588 -0
  89. package/src/Form/Form.tsx +296 -0
  90. package/src/Form/components/FormActionBar/FormActionBar.style.ts +11 -0
  91. package/src/Form/components/FormActionBar/FormActionBar.tsx +63 -0
  92. package/src/Form/components/FormActionBar/index.ts +2 -0
  93. package/src/Form/components/FormBody/FormBody.style.ts +27 -0
  94. package/src/Form/components/FormBody/FormBody.tsx +62 -0
  95. package/src/Form/components/FormBody/index.ts +1 -0
  96. package/src/Form/components/FormCache/FormCache.tsx +50 -0
  97. package/src/Form/components/FormErrorBanner/FormErrorBanner.test.tsx +124 -0
  98. package/src/Form/components/FormErrorBanner/FormErrorBanner.tsx +34 -0
  99. package/src/Form/components/FormErrorBanner/index.ts +1 -0
  100. package/src/Form/components/FormErrorBanner/messages.ts +14 -0
  101. package/src/Form/components/FormMask/FormMask.style.tsx +16 -0
  102. package/src/Form/components/FormMask/FormMask.tsx +19 -0
  103. package/src/Form/components/FormMask/index.ts +1 -0
  104. package/src/Form/components/FormMessage/FormMessage.test.tsx +72 -0
  105. package/src/Form/components/FormMessage/FormMessage.tsx +63 -0
  106. package/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.style.ts +18 -0
  107. package/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.tsx +55 -0
  108. package/src/Form/components/FormMessage/components/InternalFormMessage/index.ts +1 -0
  109. package/src/Form/components/FormMessage/components/InternalFormMessage/messages.ts +10 -0
  110. package/src/Form/components/FormMessage/index.ts +1 -0
  111. package/src/Form/components/FormMessageBanner/FormMessageBanner.test.tsx +27 -0
  112. package/src/Form/components/FormMessageBanner/FormMessageBanner.tsx +33 -0
  113. package/src/Form/components/FormMessageBanner/index.ts +1 -0
  114. package/src/Form/components/FormSaveButton/FormSaveButton.test.tsx +159 -0
  115. package/src/Form/components/FormSaveButton/FormSaveButton.tsx +103 -0
  116. package/src/Form/components/FormSaveButton/index.ts +1 -0
  117. package/src/Form/components/FormSaveButton/messages.ts +9 -0
  118. package/src/Form/constants.ts +2 -0
  119. package/src/Form/context/AtlantisFormContext.test.tsx +45 -0
  120. package/src/Form/context/AtlantisFormContext.tsx +21 -0
  121. package/src/Form/context/index.ts +5 -0
  122. package/src/Form/context/types.ts +34 -0
  123. package/src/Form/hooks/useFormViewRefs.ts +23 -0
  124. package/src/Form/hooks/useInternalForm.ts +99 -0
  125. package/src/Form/hooks/useOfflineHandler.ts +36 -0
  126. package/src/Form/hooks/useSaveButtonPosition.ts +52 -0
  127. package/src/Form/hooks/useScreenInformation.ts +25 -0
  128. package/src/Form/hooks/useScrollToError/index.ts +1 -0
  129. package/src/Form/hooks/useScrollToError/useScrollToError.test.tsx +103 -0
  130. package/src/Form/hooks/useScrollToError/useScrollToError.ts +102 -0
  131. package/src/Form/index.ts +13 -0
  132. package/src/Form/messages.ts +33 -0
  133. package/src/Form/types.ts +255 -0
  134. package/src/InputNumber/InputNumber.tsx +1 -1
  135. package/src/InputText/InputText.tsx +8 -1
  136. package/src/index.ts +1 -0
@@ -0,0 +1,187 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __rest = (this && this.__rest) || function (s, e) {
11
+ var t = {};
12
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
13
+ t[p] = s[p];
14
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
15
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
16
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
17
+ t[p[i]] = s[p[i]];
18
+ }
19
+ return t;
20
+ };
21
+ import React, { useState } from "react";
22
+ import { FormProvider } from "react-hook-form";
23
+ import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
24
+ import { Keyboard, Platform, View, findNodeHandle, } from "react-native";
25
+ import { styles } from "./Form.style";
26
+ import { FormErrorBanner } from "./components/FormErrorBanner";
27
+ import { KEYBOARD_SAVE_BUTTON_DISTANCE } from "./constants";
28
+ import { FormMessageBanner } from "./components/FormMessageBanner";
29
+ import { FormSubmitErrorType, } from "./types";
30
+ import { FormMask } from "./components/FormMask";
31
+ import { useInternalForm } from "./hooks/useInternalForm";
32
+ import { useFormViewRefs } from "./hooks/useFormViewRefs";
33
+ import { useScreenInformation } from "./hooks/useScreenInformation";
34
+ import { FormMessage } from "./components/FormMessage";
35
+ import { FormBody, useBottomPadding } from "./components/FormBody";
36
+ import { useOfflineHandler } from "./hooks/useOfflineHandler";
37
+ import { useScrollToError } from "./hooks/useScrollToError";
38
+ import { FormSaveButton } from "./components/FormSaveButton";
39
+ import { useSaveButtonPosition } from "./hooks/useSaveButtonPosition";
40
+ import { FormCache } from "./components/FormCache/FormCache";
41
+ import { InputAccessoriesProvider } from "../InputText";
42
+ import { tokens } from "../utils/design";
43
+ import { ErrorMessageProvider } from "../ErrorMessageWrapper";
44
+ export function Form(_a) {
45
+ var { initialLoading } = _a, rest = __rest(_a, ["initialLoading"]);
46
+ const child = initialLoading ? React.createElement(FormMask, null) : React.createElement(InternalForm, Object.assign({}, rest));
47
+ return (React.createElement(InputAccessoriesProvider, null,
48
+ React.createElement(ErrorMessageProvider, null, child)));
49
+ }
50
+ // eslint-disable-next-line max-statements
51
+ function InternalForm({ children, onBeforeSubmit, onSubmit, onSubmitError, onSubmitSuccess, bannerErrors, bannerMessages, initialValues, mode = "onTouched", reValidateMode = "onChange", formRef, saveButtonLabel, renderStickySection, localCacheKey, localCacheExclude, localCacheId, secondaryActions, saveButtonOffset, showStickySaveButton = false, renderFooter, }) {
52
+ var _a;
53
+ const { scrollViewRef, bottomViewRef, scrollToTop } = useFormViewRefs();
54
+ const [saveButtonHeight, setSaveButtonHeight] = useState(0);
55
+ const [messageBannerHeight, setMessageBannerHeight] = useState(0);
56
+ const { formMethods, handleSubmit, isSubmitting, removeListenerRef, setLocalCache, } = useInternalForm({
57
+ mode,
58
+ reValidateMode,
59
+ initialValues,
60
+ formRef,
61
+ localCacheKey,
62
+ localCacheExclude,
63
+ localCacheId,
64
+ scrollViewRef,
65
+ saveButtonHeight,
66
+ messageBannerHeight,
67
+ });
68
+ const { windowHeight, headerHeight } = useScreenInformation();
69
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
70
+ const [keyboardScreenY, setKeyboardScreenY] = useState(0);
71
+ const [formContentHeight, setFormContentHeight] = useState(0);
72
+ const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false);
73
+ const paddingBottom = useBottomPadding();
74
+ const { saveButtonPosition } = useSaveButtonPosition({
75
+ formContentHeight,
76
+ isBottomSheetOpen,
77
+ showStickySaveButton,
78
+ keyboardHeight,
79
+ keyboardScreenY,
80
+ });
81
+ const [isSecondaryActionLoading, setIsSecondaryActionLoading] = useState(false);
82
+ const extraViewHeight = paddingBottom + KEYBOARD_SAVE_BUTTON_DISTANCE;
83
+ const calculatedKeyboardHeight = keyboardHeight - extraViewHeight;
84
+ useScrollToError({
85
+ formState: formMethods.formState,
86
+ refNode: findNodeHandle(scrollViewRef.current),
87
+ setFocus: formMethods.setFocus,
88
+ scrollToPosition: (_a = scrollViewRef.current) === null || _a === void 0 ? void 0 : _a.scrollToPosition,
89
+ });
90
+ const handleOfflineSubmit = useOfflineHandler();
91
+ const keyboardProps = Platform.select({
92
+ ios: {
93
+ onKeyboardWillHide: handleKeyboardHide,
94
+ onKeyboardWillShow: handleKeyboardShow,
95
+ },
96
+ android: {
97
+ onKeyboardDidHide: handleKeyboardHide,
98
+ onKeyboardDidShow: handleKeyboardShow,
99
+ },
100
+ });
101
+ const onLayout = (event) => {
102
+ setMessageBannerHeight(event.nativeEvent.layout.height);
103
+ };
104
+ return (React.createElement(FormProvider, Object.assign({}, formMethods),
105
+ React.createElement(React.Fragment, null,
106
+ (isSubmitting || isSecondaryActionLoading) && React.createElement(FormMask, null),
107
+ React.createElement(FormCache, { localCacheKey: localCacheKey, localCacheExclude: localCacheExclude, setLocalCache: setLocalCache }),
108
+ React.createElement(FormBody, { keyboardHeight: calculateSaveButtonOffset(), submit: handleSubmit(internalSubmit), isFormSubmitting: isSubmitting, saveButtonLabel: saveButtonLabel, shouldRenderActionBar: saveButtonPosition === "sticky", renderStickySection: renderStickySection, secondaryActions: secondaryActions, setSecondaryActionLoading: setIsSecondaryActionLoading, setSaveButtonHeight: setSaveButtonHeight, saveButtonOffset: saveButtonOffset },
109
+ React.createElement(KeyboardAwareScrollView, Object.assign({ enableResetScrollToCoords: false, enableAutomaticScroll: true, keyboardOpeningTime: Platform.OS === "ios" ? tokens["timing-slowest"] : 0, keyboardShouldPersistTaps: "handled", ref: scrollViewRef }, keyboardProps, { extraHeight: headerHeight, contentContainerStyle: !keyboardHeight && styles.scrollContentContainer }),
110
+ React.createElement(View, { onLayout: ({ nativeEvent }) => {
111
+ setFormContentHeight(nativeEvent.layout.height);
112
+ } },
113
+ React.createElement(View, { onLayout: onLayout },
114
+ React.createElement(FormMessageBanner, { bannerMessages: bannerMessages }),
115
+ React.createElement(FormErrorBanner, { networkError: bannerErrors === null || bannerErrors === void 0 ? void 0 : bannerErrors.networkError, bannerError: bannerErrors === null || bannerErrors === void 0 ? void 0 : bannerErrors.bannerError })),
116
+ React.createElement(View, { style: styles.formChildContainer },
117
+ React.createElement(React.Fragment, null,
118
+ React.createElement(View, { style: styles.formContent }, children),
119
+ saveButtonPosition === "inline" && (React.createElement(View, { style: styles.fixedSaveButton }, renderStickySection ? (renderStickySection(handleSubmit(internalSubmit), saveButtonLabel, isSubmitting)) : (React.createElement(FormSaveButton, { primaryAction: handleSubmit(internalSubmit), label: saveButtonLabel, loading: isSubmitting, secondaryActions: secondaryActions, setSecondaryActionLoading: setIsSecondaryActionLoading, onOpenBottomSheet: () => setIsBottomSheetOpen(true), onCloseBottomSheet: () => setIsBottomSheetOpen(false) })))),
120
+ renderFooter))),
121
+ React.createElement(View, { style: styles.safeArea, ref: bottomViewRef })))),
122
+ React.createElement(FormMessage, null)));
123
+ function handleKeyboardShow(frames) {
124
+ setKeyboardScreenY(frames.endCoordinates.screenY);
125
+ setKeyboardHeight(frames.endCoordinates.height);
126
+ }
127
+ function handleKeyboardHide() {
128
+ var _a;
129
+ (_a = bottomViewRef === null || bottomViewRef === void 0 ? void 0 : bottomViewRef.current) === null || _a === void 0 ? void 0 : _a.measureInWindow(
130
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
131
+ (_x, y, _width, _height) => {
132
+ var _a;
133
+ // This fixes extra whitespace below the form if it was scrolled down while the keyboard was open
134
+ // i.e. a View below the form is higher than the bottom of the window
135
+ if (y < windowHeight) {
136
+ (_a = scrollViewRef === null || scrollViewRef === void 0 ? void 0 : scrollViewRef.current) === null || _a === void 0 ? void 0 : _a.scrollToEnd();
137
+ }
138
+ });
139
+ setKeyboardHeight(0);
140
+ setKeyboardScreenY(0);
141
+ }
142
+ function internalSubmit(data) {
143
+ return __awaiter(this, void 0, void 0, function* () {
144
+ let performSubmit = true;
145
+ if (onBeforeSubmit) {
146
+ performSubmit = yield onBeforeSubmit(data);
147
+ }
148
+ if (performSubmit) {
149
+ Keyboard.dismiss();
150
+ return onSubmit(data)
151
+ .then((result) => {
152
+ var _a;
153
+ (_a = removeListenerRef.current) === null || _a === void 0 ? void 0 : _a.call(removeListenerRef);
154
+ onSubmitSuccess(result);
155
+ })
156
+ .catch(handleSubmitCatch);
157
+ }
158
+ });
159
+ }
160
+ function handleSubmitCatch(error) {
161
+ // Scroll to top of form to see error
162
+ scrollToTop();
163
+ onSubmitError(error);
164
+ if ((error === null || error === void 0 ? void 0 : error.errorType) === FormSubmitErrorType.NetworkError) {
165
+ // @ts-expect-error We are making the form submission fail so that we can
166
+ // prevent the isSubmitSuccess to be true
167
+ formMethods.setError("offline", "Error saving form.");
168
+ handleOfflineSubmit(handleRetry, clearFormErrors)();
169
+ }
170
+ }
171
+ function clearFormErrors() {
172
+ // @ts-expect-error We are clearing the error that we previously set
173
+ // when the form had no internet connection
174
+ formMethods.clearErrors("offline");
175
+ }
176
+ function handleRetry() {
177
+ clearFormErrors();
178
+ return handleSubmit(internalSubmit)();
179
+ }
180
+ function calculateSaveButtonOffset() {
181
+ if (saveButtonOffset) {
182
+ // Included the space-base because it's the padding of the FormActionBar
183
+ return calculatedKeyboardHeight - saveButtonOffset + tokens["space-base"];
184
+ }
185
+ return calculatedKeyboardHeight;
186
+ }
187
+ }
@@ -0,0 +1,33 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { tokens } from "../utils/design";
3
+ export const styles = StyleSheet.create({
4
+ container: {
5
+ flex: 1,
6
+ flexGrow: 1,
7
+ width: "100%",
8
+ },
9
+ safeArea: {
10
+ backgroundColor: tokens["color-surface"],
11
+ },
12
+ scrollContentContainer: {
13
+ flexGrow: 1,
14
+ },
15
+ scrollView: {
16
+ flexGrow: 1,
17
+ },
18
+ formChildContainer: {
19
+ flexGrow: 1,
20
+ justifyContent: "flex-start",
21
+ },
22
+ formContent: {
23
+ paddingVertical: tokens["space-small"],
24
+ },
25
+ fixedSaveButton: {
26
+ padding: tokens["space-base"],
27
+ backgroundColor: tokens["color-surface"],
28
+ },
29
+ activityIndicator: {
30
+ marginVertical: tokens["space-base"],
31
+ flex: 1,
32
+ },
33
+ });
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { StyleSheet, View } from "react-native";
3
+ import Reanimated from "react-native-reanimated";
4
+ import { styles } from "./FormActionBar.style";
5
+ import { FormSaveButton } from "../FormSaveButton";
6
+ const ReanimatedView = Reanimated.createAnimatedComponent(View);
7
+ export function FormActionBar({ keyboardHeight, submit, isFormSubmitting, saveButtonLabel, renderStickySection, setSaveButtonHeight, secondaryActions, setSecondaryActionLoading, }) {
8
+ const buttonStyle = StyleSheet.flatten([
9
+ styles.saveButton,
10
+ {
11
+ position: keyboardHeight > 0 ? "absolute" : "relative",
12
+ bottom: 0,
13
+ },
14
+ ]);
15
+ const onLayout = (event) => {
16
+ setSaveButtonHeight && setSaveButtonHeight(event.nativeEvent.layout.height);
17
+ };
18
+ return (
19
+ //@ts-expect-error tsc-ci
20
+ React.createElement(ReanimatedView, { style: buttonStyle, onLayout: onLayout }, renderStickySection ? (renderStickySection(submit, saveButtonLabel, isFormSubmitting)) : (React.createElement(FormSaveButton, { setSecondaryActionLoading: setSecondaryActionLoading, primaryAction: submit, loading: isFormSubmitting, label: saveButtonLabel, secondaryActions: secondaryActions }))));
21
+ }
@@ -0,0 +1,5 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { tokens } from "../../../utils/design";
3
+ export const styles = StyleSheet.create({
4
+ saveButton: Object.assign({ padding: tokens["space-base"], backgroundColor: tokens["color-surface"], width: "100%" }, tokens["shadow-high"]),
5
+ });
@@ -0,0 +1 @@
1
+ export { FormActionBar } from "./FormActionBar";
@@ -0,0 +1,20 @@
1
+ import React, { useMemo } from "react";
2
+ import { View } from "react-native";
3
+ import { styles } from "./FormBody.style";
4
+ import { useScreenInformation } from "../../hooks/useScreenInformation";
5
+ import { FormActionBar } from "../FormActionBar";
6
+ import { tokens } from "../../../utils/design";
7
+ export function FormBody({ isFormSubmitting, submit, keyboardHeight, children, saveButtonLabel, renderStickySection, shouldRenderActionBar = true, secondaryActions, setSecondaryActionLoading, setSaveButtonHeight, saveButtonOffset, }) {
8
+ const paddingBottom = useBottomPadding();
9
+ const fullViewPadding = useMemo(() => ({ paddingBottom }), [paddingBottom]);
10
+ return (React.createElement(React.Fragment, null,
11
+ React.createElement(View, { style: [styles.container] },
12
+ children,
13
+ shouldRenderActionBar && (React.createElement(FormActionBar, { setSecondaryActionLoading: setSecondaryActionLoading, keyboardHeight: keyboardHeight, submit: submit, isFormSubmitting: isFormSubmitting, saveButtonLabel: saveButtonLabel, renderStickySection: renderStickySection, secondaryActions: secondaryActions, setSaveButtonHeight: setSaveButtonHeight }))),
14
+ shouldRenderActionBar && !saveButtonOffset && (React.createElement(View, { style: [fullViewPadding, styles.safeArea], testID: "ATL-FormSafeArea" }))));
15
+ }
16
+ export function useBottomPadding() {
17
+ const { insets } = useScreenInformation();
18
+ const extraBottomSpace = insets.bottom - tokens["space-base"];
19
+ return extraBottomSpace >= 0 ? extraBottomSpace : 0;
20
+ }
@@ -0,0 +1,26 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { tokens } from "../../../utils/design";
3
+ export const styles = StyleSheet.create({
4
+ container: {
5
+ flex: 1,
6
+ flexGrow: 1,
7
+ width: "100%",
8
+ },
9
+ safeArea: {
10
+ backgroundColor: tokens["color-surface"],
11
+ },
12
+ scrollContentContainer: {
13
+ flexGrow: 1,
14
+ },
15
+ scrollView: {
16
+ flexGrow: 1,
17
+ },
18
+ formChildContainer: {
19
+ flexGrow: 1,
20
+ justifyContent: "flex-start",
21
+ },
22
+ activityIndicator: {
23
+ marginVertical: tokens["space-base"],
24
+ flex: 1,
25
+ },
26
+ });
@@ -0,0 +1 @@
1
+ export { FormBody, useBottomPadding } from "./FormBody";
@@ -0,0 +1,34 @@
1
+ import React, { useEffect, useMemo } from "react";
2
+ import { useFormContext, useWatch } from "react-hook-form";
3
+ import omit from "lodash/omit";
4
+ export function FormCache({ localCacheExclude, localCacheKey, setLocalCache, }) {
5
+ const { control, formState } = useFormContext();
6
+ const { isDirty } = formState;
7
+ const formData = useWatch({ control });
8
+ const shouldExclude = useMemo(() => {
9
+ return Array.isArray(localCacheExclude) && localCacheExclude.length > 0;
10
+ }, [localCacheExclude]);
11
+ useEffect(() => {
12
+ !localCacheKey &&
13
+ console.log("No `localCacheKey` specified on Form. Local copy of form data is now disabled.");
14
+ }, [localCacheKey]);
15
+ /**
16
+ * Automatically save form data locally
17
+ */
18
+ useEffect(() => {
19
+ if (!isDirty)
20
+ return;
21
+ if (shouldExclude) {
22
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
23
+ // @ts-ignore The type below is not working. It seems to be an issue with react-hook-form
24
+ // https://github.com/react-hook-form/react-hook-form/issues/2978
25
+ setLocalCache(omit(formData, localCacheExclude));
26
+ }
27
+ else {
28
+ // @ts-expect-error Typescript thinks that the FieldValues defined in useWatch is different
29
+ // from the one in useFormContext
30
+ setLocalCache(formData);
31
+ }
32
+ }, [formData, isDirty, localCacheExclude, setLocalCache, shouldExclude]);
33
+ return React.createElement(React.Fragment, null);
34
+ }
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { useIntl } from "react-intl";
3
+ import { messages } from "./messages";
4
+ import { useAtlantisContext } from "../../../AtlantisContext";
5
+ import { Banner } from "../../../Banner";
6
+ export function FormErrorBanner({ networkError, bannerError, }) {
7
+ const { formatMessage } = useIntl();
8
+ const { isOnline } = useAtlantisContext();
9
+ if (!isOnline) {
10
+ return (React.createElement(Banner, { text: formatMessage(messages.offlineError), type: "error" }));
11
+ }
12
+ else if (networkError) {
13
+ return (React.createElement(Banner, { text: formatMessage(messages.networkError), type: "error" }));
14
+ }
15
+ else if (bannerError) {
16
+ return (React.createElement(Banner, { text: bannerError.title, details: bannerError.messages, type: "error" }));
17
+ }
18
+ else {
19
+ return React.createElement(React.Fragment, null);
20
+ }
21
+ }
@@ -0,0 +1 @@
1
+ export { FormErrorBanner } from "./FormErrorBanner";
@@ -0,0 +1,13 @@
1
+ import { defineMessages } from "react-intl";
2
+ export const messages = defineMessages({
3
+ networkError: {
4
+ id: "networkError",
5
+ defaultMessage: "Could not save changes",
6
+ description: "Displayed when a general server error occurs during save",
7
+ },
8
+ offlineError: {
9
+ id: "offlineError",
10
+ defaultMessage: "Currently offline. Check your internet connection.",
11
+ description: "Error message to be shown when the app is offline",
12
+ },
13
+ });
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import { useIntl } from "react-intl";
4
+ import { styles } from "./FormMask.style";
5
+ import { ActivityIndicator } from "../../../ActivityIndicator";
6
+ import { messages } from "../../messages";
7
+ export function FormMask() {
8
+ const { formatMessage } = useIntl();
9
+ return (React.createElement(View, { style: styles.mask, accessibilityLabel: formatMessage(messages.loadingA11YLabel) },
10
+ React.createElement(ActivityIndicator, null)));
11
+ }
@@ -0,0 +1,15 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { tokens } from "../../../utils/design";
3
+ export const styles = StyleSheet.create({
4
+ mask: {
5
+ zIndex: tokens["elevation-modal"],
6
+ width: "100%",
7
+ height: "100%",
8
+ position: "absolute",
9
+ top: 0,
10
+ left: 0,
11
+ backgroundColor: tokens["color-overlay--dimmed"],
12
+ padding: tokens["space-base"],
13
+ justifyContent: "center",
14
+ },
15
+ });
@@ -0,0 +1 @@
1
+ export { FormMask } from "./FormMask";
@@ -0,0 +1,48 @@
1
+ import React, { useCallback, useState } from "react";
2
+ import { InternalFormMessage } from "./components/InternalFormMessage";
3
+ let open;
4
+ let close;
5
+ /**
6
+ * Show a message that takes over the whole screen to the user. This provides a
7
+ * more urgent feedback when the user does an action that requires attention
8
+ * their full attention.
9
+ *
10
+ * By default, rendering `<FormMessage />` on a screen won't show any messages
11
+ * because it's only a container. Use `FormMessage.open(...)`. to show a
12
+ * message to the user. Use `FormMessage.close()` to close the most
13
+ * recent message.
14
+ */
15
+ export const FormMessage = () => {
16
+ const [data, setData] = useState([]);
17
+ open = useCallback((messageData) => {
18
+ setData([...data, messageData]);
19
+ }, [data]);
20
+ close = useCallback(() => {
21
+ const newValue = data.slice(0, -1);
22
+ setData(newValue);
23
+ }, [data]);
24
+ if (data.length === 0) {
25
+ return React.createElement(React.Fragment, null);
26
+ }
27
+ const lastMessage = data[data.length - 1];
28
+ return React.createElement(InternalFormMessage, { data: lastMessage, onRequestClose: close });
29
+ };
30
+ FormMessage.show = (messageData) => {
31
+ if (open) {
32
+ open(messageData);
33
+ }
34
+ else {
35
+ warnOnUndefinedFunctions("show");
36
+ }
37
+ };
38
+ FormMessage.close = () => {
39
+ if (close) {
40
+ close();
41
+ }
42
+ else {
43
+ warnOnUndefinedFunctions("close");
44
+ }
45
+ };
46
+ function warnOnUndefinedFunctions(method) {
47
+ console.warn(`Could not ${method} "FormMessage". Either you're calling this method before the component mounts or you're using this without the "<Form />" component. If you're using "FormMessage" without the "Form", include "<FormMessage />" on your component to start using it.`);
48
+ }
@@ -0,0 +1,28 @@
1
+ import React, { useMemo } from "react";
2
+ import { SafeAreaView } from "react-native-safe-area-context";
3
+ import { Modal, StatusBar, View } from "react-native";
4
+ import { ScrollView } from "react-native-gesture-handler";
5
+ import { useIntl } from "react-intl";
6
+ import { styles } from "./InternalFormMessage.style";
7
+ import { messages } from "./messages";
8
+ import { EmptyState } from "../../../../../EmptyState";
9
+ export function InternalFormMessage({ data, onRequestClose, }) {
10
+ const { formatMessage } = useIntl();
11
+ const emptyStateData = useMemo(() => {
12
+ if (data.secondaryAction) {
13
+ return data;
14
+ }
15
+ else {
16
+ return Object.assign(Object.assign({}, data), { secondaryAction: {
17
+ label: formatMessage(messages.goBackButton),
18
+ onPress: onRequestClose,
19
+ } });
20
+ }
21
+ }, [data, formatMessage, onRequestClose]);
22
+ return (React.createElement(Modal, { animationType: "fade", transparent: true, visible: true, onRequestClose: onRequestClose },
23
+ React.createElement(View, { style: styles.wrapper },
24
+ React.createElement(StatusBar, { barStyle: "dark-content" }),
25
+ React.createElement(SafeAreaView, null,
26
+ React.createElement(ScrollView, { style: styles.scrollWrapper, contentContainerStyle: styles.scrollWrapperContent, centerContent: true },
27
+ React.createElement(EmptyState, Object.assign({}, emptyStateData)))))));
28
+ }
@@ -0,0 +1,17 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { tokens } from "../../../../../utils/design";
3
+ export const styles = StyleSheet.create({
4
+ wrapper: {
5
+ backgroundColor: tokens["color-surface"],
6
+ flex: 1,
7
+ justifyContent: "center",
8
+ },
9
+ closeAction: {
10
+ position: "absolute",
11
+ top: 0,
12
+ right: 0,
13
+ zIndex: 1,
14
+ },
15
+ scrollWrapper: { height: "100%" },
16
+ scrollWrapperContent: { flexGrow: 1, justifyContent: "center" },
17
+ });
@@ -0,0 +1 @@
1
+ export { InternalFormMessage } from "./InternalFormMessage";
@@ -0,0 +1,8 @@
1
+ import { defineMessages } from "react-intl";
2
+ export const messages = defineMessages({
3
+ goBackButton: {
4
+ id: "goBackButton",
5
+ defaultMessage: "Go Back",
6
+ description: "The label for the fallback secondary button when it doesn't exist",
7
+ },
8
+ });
@@ -0,0 +1 @@
1
+ export { FormMessage } from "./FormMessage";
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import { FormBannerMessageType } from "../../types";
3
+ import { Banner } from "../../../Banner";
4
+ export function FormMessageBanner({ bannerMessages, }) {
5
+ return (React.createElement(React.Fragment, null, bannerMessages === null || bannerMessages === void 0 ? void 0 : bannerMessages.map((message, index) => (React.createElement(Banner, { key: index, text: message.message, type: getBannerType(message.messageType) })))));
6
+ }
7
+ function getBannerType(messageType) {
8
+ switch (messageType) {
9
+ case FormBannerMessageType.WarningMessage:
10
+ return "warning";
11
+ case FormBannerMessageType.NoticeMessage:
12
+ default:
13
+ return "notice";
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ export { FormMessageBanner } from "./FormMessageBanner";
@@ -0,0 +1,69 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import React from "react";
11
+ import { useIntl } from "react-intl";
12
+ import { useFormContext } from "react-hook-form";
13
+ import { messages } from "./messages";
14
+ import { ButtonGroup, } from "../../../ButtonGroup";
15
+ export function FormSaveButton({ primaryAction, loading, label, secondaryActions, setSecondaryActionLoading, onOpenBottomSheet, onCloseBottomSheet, }) {
16
+ const { formatMessage } = useIntl();
17
+ const formContext = useFormContext();
18
+ const buttonActions = useButtonGroupAction(secondaryActions);
19
+ return (React.createElement(React.Fragment, null,
20
+ React.createElement(ButtonGroup, { onOpenBottomSheet: onOpenBottomSheet, onCloseBottomSheet: onCloseBottomSheet, allowTapWhenOffline: true }, buttonActions.map((action, index) => {
21
+ if (index === 0) {
22
+ return (React.createElement(ButtonGroup.PrimaryAction, { key: index, onPress: primaryAction, label: label !== null && label !== void 0 ? label : formatMessage(messages.saveButton), loading: loading }));
23
+ }
24
+ else {
25
+ return (React.createElement(ButtonGroup.SecondaryAction, { key: index, label: action.label, icon: action.icon, onPress: action.onPress, destructive: action.destructive }));
26
+ }
27
+ }))));
28
+ function useButtonGroupAction(array) {
29
+ const buttonGroupActionProps = array
30
+ ? array.map(arr => {
31
+ return {
32
+ label: arr.label,
33
+ onPress: () => internalOnPress(arr.handleAction),
34
+ destructive: arr.destructive,
35
+ icon: arr.icon,
36
+ };
37
+ })
38
+ : [];
39
+ buttonGroupActionProps.unshift({
40
+ label: label !== null && label !== void 0 ? label : formatMessage(messages.saveButton),
41
+ onPress: primaryAction,
42
+ loading: loading,
43
+ icon: undefined,
44
+ });
45
+ return buttonGroupActionProps;
46
+ }
47
+ function internalOnPress(handleAction) {
48
+ return __awaiter(this, void 0, void 0, function* () {
49
+ let performSubmit = true;
50
+ if (handleAction.onBeforeSubmit) {
51
+ performSubmit = yield handleAction.onBeforeSubmit();
52
+ }
53
+ if (performSubmit) {
54
+ setSecondaryActionLoading === null || setSecondaryActionLoading === void 0 ? void 0 : setSecondaryActionLoading(true);
55
+ handleAction
56
+ .onSubmit(primaryAction)
57
+ .then(() => {
58
+ var _a;
59
+ handleAction.resetFormOnSubmit && formContext.reset();
60
+ (_a = handleAction.onSubmitSuccess) === null || _a === void 0 ? void 0 : _a.call(handleAction);
61
+ })
62
+ .catch(handleAction.onSubmitError)
63
+ .finally(() => {
64
+ setSecondaryActionLoading === null || setSecondaryActionLoading === void 0 ? void 0 : setSecondaryActionLoading(false);
65
+ });
66
+ }
67
+ });
68
+ }
69
+ }
@@ -0,0 +1 @@
1
+ export { FormSaveButton } from "./FormSaveButton";
@@ -0,0 +1,8 @@
1
+ import { defineMessages } from "react-intl";
2
+ export const messages = defineMessages({
3
+ saveButton: {
4
+ id: "saveButton",
5
+ defaultMessage: "Save",
6
+ description: "The label for the save button",
7
+ },
8
+ });
@@ -0,0 +1,2 @@
1
+ export const KEYBOARD_TOP_PADDING_AUTO_SCROLL = 20;
2
+ export const KEYBOARD_SAVE_BUTTON_DISTANCE = 1;
@@ -0,0 +1,16 @@
1
+ import { createContext, useContext, useRef } from "react";
2
+ export const defaultValues = {
3
+ useConfirmBeforeBack: () => {
4
+ const ref = useRef(() => undefined);
5
+ return ref;
6
+ },
7
+ useInternalFormLocalCache: () => ({
8
+ setLocalCache: () => undefined,
9
+ removeLocalCache: () => undefined,
10
+ }),
11
+ headerHeight: 0,
12
+ };
13
+ export const AtlantisFormContext = createContext(defaultValues);
14
+ export function useAtlantisFormContext() {
15
+ return useContext(AtlantisFormContext);
16
+ }