@jobber/components-native 0.38.0 → 0.40.0

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 (184) hide show
  1. package/dist/src/AtlantisContext/AtlantisContext.js +2 -0
  2. package/dist/src/Form/Form.js +187 -0
  3. package/dist/src/Form/Form.style.js +33 -0
  4. package/dist/src/Form/components/FormActionBar/FormActionBar.js +21 -0
  5. package/dist/src/Form/components/FormActionBar/FormActionBar.style.js +5 -0
  6. package/dist/src/Form/components/FormActionBar/index.js +1 -0
  7. package/dist/src/Form/components/FormBody/FormBody.js +20 -0
  8. package/dist/src/Form/components/FormBody/FormBody.style.js +26 -0
  9. package/dist/src/Form/components/FormBody/index.js +1 -0
  10. package/dist/src/Form/components/FormCache/FormCache.js +34 -0
  11. package/dist/src/Form/components/FormErrorBanner/FormErrorBanner.js +21 -0
  12. package/dist/src/Form/components/FormErrorBanner/index.js +1 -0
  13. package/dist/src/Form/components/FormErrorBanner/messages.js +13 -0
  14. package/dist/src/Form/components/FormMask/FormMask.js +11 -0
  15. package/dist/src/Form/components/FormMask/FormMask.style.js +15 -0
  16. package/dist/src/Form/components/FormMask/index.js +1 -0
  17. package/dist/src/Form/components/FormMessage/FormMessage.js +48 -0
  18. package/dist/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.js +28 -0
  19. package/dist/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.style.js +17 -0
  20. package/dist/src/Form/components/FormMessage/components/InternalFormMessage/index.js +1 -0
  21. package/dist/src/Form/components/FormMessage/components/InternalFormMessage/messages.js +8 -0
  22. package/dist/src/Form/components/FormMessage/index.js +1 -0
  23. package/dist/src/Form/components/FormMessageBanner/FormMessageBanner.js +15 -0
  24. package/dist/src/Form/components/FormMessageBanner/index.js +1 -0
  25. package/dist/src/Form/components/FormSaveButton/FormSaveButton.js +69 -0
  26. package/dist/src/Form/components/FormSaveButton/index.js +1 -0
  27. package/dist/src/Form/components/FormSaveButton/messages.js +8 -0
  28. package/dist/src/Form/constants.js +2 -0
  29. package/dist/src/Form/context/AtlantisFormContext.js +16 -0
  30. package/dist/src/Form/context/index.js +1 -0
  31. package/dist/src/Form/context/types.js +1 -0
  32. package/dist/src/Form/hooks/useFormViewRefs.js +14 -0
  33. package/dist/src/Form/hooks/useInternalForm.js +37 -0
  34. package/dist/src/Form/hooks/useOfflineHandler.js +24 -0
  35. package/dist/src/Form/hooks/useSaveButtonPosition.js +25 -0
  36. package/dist/src/Form/hooks/useScreenInformation.js +15 -0
  37. package/dist/src/Form/hooks/useScrollToError/index.js +1 -0
  38. package/dist/src/Form/hooks/useScrollToError/useScrollToError.js +63 -0
  39. package/dist/src/Form/index.js +4 -0
  40. package/dist/src/Form/messages.js +28 -0
  41. package/dist/src/Form/types.js +10 -0
  42. package/dist/src/InputDate/InputDate.js +76 -0
  43. package/dist/src/InputDate/index.js +1 -0
  44. package/dist/src/InputDate/messages.js +8 -0
  45. package/dist/src/Menu/Menu.js +67 -0
  46. package/dist/src/Menu/Menu.style.js +6 -0
  47. package/dist/src/Menu/components/MenuOption/MenuOption.js +25 -0
  48. package/dist/src/Menu/components/MenuOption/MenuOption.style.js +10 -0
  49. package/dist/src/Menu/components/MenuOption/index.js +1 -0
  50. package/dist/src/Menu/components/Overlay/Overlay.js +9 -0
  51. package/dist/src/Menu/components/Overlay/Overlay.style.js +6 -0
  52. package/dist/src/Menu/components/Overlay/index.js +1 -0
  53. package/dist/src/Menu/index.js +1 -0
  54. package/dist/src/Menu/messages.js +8 -0
  55. package/dist/src/Menu/types.js +1 -0
  56. package/dist/src/Menu/utils.js +84 -0
  57. package/dist/src/index.js +3 -0
  58. package/dist/tsconfig.tsbuildinfo +1 -1
  59. package/dist/types/src/AtlantisContext/AtlantisContext.d.ts +7 -1
  60. package/dist/types/src/Form/Form.d.ts +4 -0
  61. package/dist/types/src/Form/Form.style.d.ts +31 -0
  62. package/dist/types/src/Form/components/FormActionBar/FormActionBar.d.ts +13 -0
  63. package/dist/types/src/Form/components/FormActionBar/FormActionBar.style.d.ts +15 -0
  64. package/dist/types/src/Form/components/FormActionBar/index.d.ts +2 -0
  65. package/dist/types/src/Form/components/FormBody/FormBody.d.ts +10 -0
  66. package/dist/types/src/Form/components/FormBody/FormBody.style.d.ts +24 -0
  67. package/dist/types/src/Form/components/FormBody/index.d.ts +1 -0
  68. package/dist/types/src/Form/components/FormCache/FormCache.d.ts +10 -0
  69. package/dist/types/src/Form/components/FormErrorBanner/FormErrorBanner.d.ts +3 -0
  70. package/dist/types/src/Form/components/FormErrorBanner/index.d.ts +1 -0
  71. package/dist/types/src/Form/components/FormErrorBanner/messages.d.ts +12 -0
  72. package/dist/types/src/Form/components/FormMask/FormMask.d.ts +2 -0
  73. package/dist/types/src/Form/components/FormMask/FormMask.style.d.ts +13 -0
  74. package/dist/types/src/Form/components/FormMask/index.d.ts +1 -0
  75. package/dist/types/src/Form/components/FormMessage/FormMessage.d.ts +19 -0
  76. package/dist/types/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.d.ts +8 -0
  77. package/dist/types/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.style.d.ts +20 -0
  78. package/dist/types/src/Form/components/FormMessage/components/InternalFormMessage/index.d.ts +1 -0
  79. package/dist/types/src/Form/components/FormMessage/components/InternalFormMessage/messages.d.ts +7 -0
  80. package/dist/types/src/Form/components/FormMessage/index.d.ts +1 -0
  81. package/dist/types/src/Form/components/FormMessageBanner/FormMessageBanner.d.ts +7 -0
  82. package/dist/types/src/Form/components/FormMessageBanner/index.d.ts +1 -0
  83. package/dist/types/src/Form/components/FormSaveButton/FormSaveButton.d.ts +3 -0
  84. package/dist/types/src/Form/components/FormSaveButton/index.d.ts +1 -0
  85. package/dist/types/src/Form/components/FormSaveButton/messages.d.ts +7 -0
  86. package/dist/types/src/Form/constants.d.ts +2 -0
  87. package/dist/types/src/Form/context/AtlantisFormContext.d.ts +12 -0
  88. package/dist/types/src/Form/context/index.d.ts +2 -0
  89. package/dist/types/src/Form/context/types.d.ts +26 -0
  90. package/dist/types/src/Form/hooks/useFormViewRefs.d.ts +10 -0
  91. package/dist/types/src/Form/hooks/useInternalForm.d.ts +19 -0
  92. package/dist/types/src/Form/hooks/useOfflineHandler.d.ts +1 -0
  93. package/dist/types/src/Form/hooks/useSaveButtonPosition.d.ts +12 -0
  94. package/dist/types/src/Form/hooks/useScreenInformation.d.ts +8 -0
  95. package/dist/types/src/Form/hooks/useScrollToError/index.d.ts +1 -0
  96. package/dist/types/src/Form/hooks/useScrollToError/useScrollToError.d.ts +10 -0
  97. package/dist/types/src/Form/index.d.ts +5 -0
  98. package/dist/types/src/Form/messages.d.ts +27 -0
  99. package/dist/types/src/Form/types.d.ts +199 -0
  100. package/dist/types/src/InputDate/InputDate.d.ts +74 -0
  101. package/dist/types/src/InputDate/index.d.ts +1 -0
  102. package/dist/types/src/InputDate/messages.d.ts +7 -0
  103. package/dist/types/src/InputNumber/InputNumber.d.ts +1 -1
  104. package/dist/types/src/Menu/Menu.d.ts +3 -0
  105. package/dist/types/src/Menu/Menu.style.d.ts +18 -0
  106. package/dist/types/src/Menu/components/MenuOption/MenuOption.d.ts +3 -0
  107. package/dist/types/src/Menu/components/MenuOption/MenuOption.style.d.ts +8 -0
  108. package/dist/types/src/Menu/components/MenuOption/index.d.ts +1 -0
  109. package/dist/types/src/Menu/components/Overlay/Overlay.d.ts +3 -0
  110. package/dist/types/src/Menu/components/Overlay/Overlay.style.d.ts +12 -0
  111. package/dist/types/src/Menu/components/Overlay/index.d.ts +1 -0
  112. package/dist/types/src/Menu/index.d.ts +2 -0
  113. package/dist/types/src/Menu/messages.d.ts +7 -0
  114. package/dist/types/src/Menu/types.d.ts +22 -0
  115. package/dist/types/src/Menu/utils.d.ts +10 -0
  116. package/dist/types/src/index.d.ts +3 -0
  117. package/package.json +3 -2
  118. package/src/AtlantisContext/AtlantisContext.tsx +10 -1
  119. package/src/Form/Form.style.ts +34 -0
  120. package/src/Form/Form.test.tsx +588 -0
  121. package/src/Form/Form.tsx +296 -0
  122. package/src/Form/components/FormActionBar/FormActionBar.style.ts +11 -0
  123. package/src/Form/components/FormActionBar/FormActionBar.tsx +63 -0
  124. package/src/Form/components/FormActionBar/index.ts +2 -0
  125. package/src/Form/components/FormBody/FormBody.style.ts +27 -0
  126. package/src/Form/components/FormBody/FormBody.tsx +62 -0
  127. package/src/Form/components/FormBody/index.ts +1 -0
  128. package/src/Form/components/FormCache/FormCache.tsx +50 -0
  129. package/src/Form/components/FormErrorBanner/FormErrorBanner.test.tsx +124 -0
  130. package/src/Form/components/FormErrorBanner/FormErrorBanner.tsx +34 -0
  131. package/src/Form/components/FormErrorBanner/index.ts +1 -0
  132. package/src/Form/components/FormErrorBanner/messages.ts +14 -0
  133. package/src/Form/components/FormMask/FormMask.style.tsx +16 -0
  134. package/src/Form/components/FormMask/FormMask.tsx +19 -0
  135. package/src/Form/components/FormMask/index.ts +1 -0
  136. package/src/Form/components/FormMessage/FormMessage.test.tsx +72 -0
  137. package/src/Form/components/FormMessage/FormMessage.tsx +63 -0
  138. package/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.style.ts +18 -0
  139. package/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.tsx +55 -0
  140. package/src/Form/components/FormMessage/components/InternalFormMessage/index.ts +1 -0
  141. package/src/Form/components/FormMessage/components/InternalFormMessage/messages.ts +10 -0
  142. package/src/Form/components/FormMessage/index.ts +1 -0
  143. package/src/Form/components/FormMessageBanner/FormMessageBanner.test.tsx +27 -0
  144. package/src/Form/components/FormMessageBanner/FormMessageBanner.tsx +33 -0
  145. package/src/Form/components/FormMessageBanner/index.ts +1 -0
  146. package/src/Form/components/FormSaveButton/FormSaveButton.test.tsx +159 -0
  147. package/src/Form/components/FormSaveButton/FormSaveButton.tsx +103 -0
  148. package/src/Form/components/FormSaveButton/index.ts +1 -0
  149. package/src/Form/components/FormSaveButton/messages.ts +9 -0
  150. package/src/Form/constants.ts +2 -0
  151. package/src/Form/context/AtlantisFormContext.test.tsx +45 -0
  152. package/src/Form/context/AtlantisFormContext.tsx +21 -0
  153. package/src/Form/context/index.ts +5 -0
  154. package/src/Form/context/types.ts +34 -0
  155. package/src/Form/hooks/useFormViewRefs.ts +23 -0
  156. package/src/Form/hooks/useInternalForm.ts +99 -0
  157. package/src/Form/hooks/useOfflineHandler.ts +36 -0
  158. package/src/Form/hooks/useSaveButtonPosition.ts +52 -0
  159. package/src/Form/hooks/useScreenInformation.ts +25 -0
  160. package/src/Form/hooks/useScrollToError/index.ts +1 -0
  161. package/src/Form/hooks/useScrollToError/useScrollToError.test.tsx +103 -0
  162. package/src/Form/hooks/useScrollToError/useScrollToError.ts +102 -0
  163. package/src/Form/index.ts +13 -0
  164. package/src/Form/messages.ts +33 -0
  165. package/src/Form/types.ts +255 -0
  166. package/src/InputDate/InputDate.test.tsx +295 -0
  167. package/src/InputDate/InputDate.tsx +231 -0
  168. package/src/InputDate/index.ts +1 -0
  169. package/src/InputDate/messages.ts +9 -0
  170. package/src/InputNumber/InputNumber.tsx +1 -1
  171. package/src/Menu/Menu.style.ts +16 -0
  172. package/src/Menu/Menu.test.tsx +201 -0
  173. package/src/Menu/Menu.tsx +116 -0
  174. package/src/Menu/components/MenuOption/MenuOption.style.tsx +11 -0
  175. package/src/Menu/components/MenuOption/MenuOption.tsx +63 -0
  176. package/src/Menu/components/MenuOption/index.ts +1 -0
  177. package/src/Menu/components/Overlay/Overlay.style.ts +13 -0
  178. package/src/Menu/components/Overlay/Overlay.tsx +16 -0
  179. package/src/Menu/components/Overlay/index.ts +1 -0
  180. package/src/Menu/index.ts +6 -0
  181. package/src/Menu/messages.ts +9 -0
  182. package/src/Menu/types.ts +25 -0
  183. package/src/Menu/utils.ts +151 -0
  184. package/src/index.ts +3 -0
@@ -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
+ }
@@ -0,0 +1 @@
1
+ export { AtlantisFormContext, useAtlantisFormContext, } from "./AtlantisFormContext";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { useCallback, useRef } from "react";
2
+ export function useFormViewRefs() {
3
+ const scrollViewRef = useRef(null);
4
+ const bottomViewRef = useRef(null);
5
+ const scrollToTop = useCallback(() => {
6
+ var _a;
7
+ (_a = scrollViewRef.current) === null || _a === void 0 ? void 0 : _a.scrollToPosition(0, 0);
8
+ }, [scrollViewRef]);
9
+ return {
10
+ scrollViewRef: scrollViewRef,
11
+ bottomViewRef,
12
+ scrollToTop,
13
+ };
14
+ }
@@ -0,0 +1,37 @@
1
+ import { useForm, } from "react-hook-form";
2
+ import { useAtlantisContext } from "../../AtlantisContext";
3
+ import { useAtlantisFormContext } from "../context/AtlantisFormContext";
4
+ export function useInternalForm({ mode, reValidateMode, initialValues, formRef, localCacheKey, localCacheId, scrollViewRef, saveButtonHeight, messageBannerHeight, }) {
5
+ const { useConfirmBeforeBack, useInternalFormLocalCache } = useAtlantisFormContext();
6
+ const { isOnline } = useAtlantisContext();
7
+ const formMethods = useForm({
8
+ mode,
9
+ reValidateMode,
10
+ defaultValues: initialValues,
11
+ shouldFocusError: false,
12
+ });
13
+ const clientSideSaveOn = localCacheKey && localCacheKey !== "INVALID";
14
+ const { setLocalCache, removeLocalCache } = useInternalFormLocalCache(formMethods, localCacheKey, {
15
+ id: localCacheId,
16
+ });
17
+ const { handleSubmit, formState: { isSubmitting, isDirty }, } = formMethods;
18
+ if (formRef) {
19
+ formRef.current = Object.assign(Object.assign({}, formMethods), { saveButtonHeight,
20
+ messageBannerHeight,
21
+ scrollViewRef });
22
+ }
23
+ const removeListenerRef = useConfirmBeforeBack({
24
+ alwaysPreventBack: isSubmitting,
25
+ shouldShowAlert: isDirty,
26
+ onAcceptEvent: isOnline ? removeLocalCache : undefined,
27
+ showLostProgressMessage: isOnline || !clientSideSaveOn ? true : false,
28
+ });
29
+ return {
30
+ setLocalCache,
31
+ formMethods,
32
+ handleSubmit,
33
+ isSubmitting,
34
+ isDirty,
35
+ removeListenerRef,
36
+ };
37
+ }
@@ -0,0 +1,24 @@
1
+ import { useCallback } from "react";
2
+ import { useIntl } from "react-intl";
3
+ import { Alert } from "react-native";
4
+ import { messages } from "../messages";
5
+ export function useOfflineHandler() {
6
+ const { formatMessage } = useIntl();
7
+ const handleOfflineSubmit = useCallback((callback, dismiss) => {
8
+ return () => {
9
+ Alert.alert(formatMessage(messages.unavailableNetworkTitle), formatMessage(messages.unavailableNetworkMessage), [
10
+ {
11
+ text: formatMessage(messages.dismissAlertButton),
12
+ style: "cancel",
13
+ onPress: dismiss,
14
+ },
15
+ {
16
+ text: formatMessage(messages.retryAlertButton),
17
+ style: "default",
18
+ onPress: callback,
19
+ },
20
+ ]);
21
+ };
22
+ }, [formatMessage]);
23
+ return handleOfflineSubmit;
24
+ }
@@ -0,0 +1,25 @@
1
+ import { Platform } from "react-native";
2
+ import { useScreenInformation } from "./useScreenInformation";
3
+ import { tokens } from "../../utils/design";
4
+ export function useSaveButtonPosition({ formContentHeight, isBottomSheetOpen, showStickySaveButton, keyboardHeight, keyboardScreenY, }) {
5
+ const { headerHeight } = useScreenInformation();
6
+ if (showStickySaveButton) {
7
+ return {
8
+ saveButtonPosition: "sticky",
9
+ };
10
+ }
11
+ if (Platform.OS === "android" || isBottomSheetOpen) {
12
+ return {
13
+ saveButtonPosition: "inline",
14
+ };
15
+ }
16
+ const isKeyboardOpen = Boolean(keyboardHeight);
17
+ // tokens["space-large"] acts like a safe space below the form to avoid
18
+ // rendering the sticky button above the form elements.
19
+ const calculatedFormContentHeight = formContentHeight + headerHeight + tokens["space-large"];
20
+ const isKeyboardOverForm = calculatedFormContentHeight > keyboardScreenY;
21
+ const showInlineSaveButton = isKeyboardOpen && isKeyboardOverForm;
22
+ return {
23
+ saveButtonPosition: showInlineSaveButton ? "inline" : "sticky",
24
+ };
25
+ }
@@ -0,0 +1,15 @@
1
+ import { useWindowDimensions } from "react-native";
2
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
3
+ import { KEYBOARD_TOP_PADDING_AUTO_SCROLL } from "../constants";
4
+ import { useAtlantisFormContext } from "../context";
5
+ export function useScreenInformation() {
6
+ const { headerHeight } = useAtlantisFormContext();
7
+ const windowHeight = useWindowDimensions().height;
8
+ const headerHeightWithPadding = headerHeight + KEYBOARD_TOP_PADDING_AUTO_SCROLL;
9
+ const insets = useSafeAreaInsets();
10
+ return {
11
+ windowHeight,
12
+ headerHeight: headerHeightWithPadding,
13
+ insets,
14
+ };
15
+ }
@@ -0,0 +1 @@
1
+ export * from "./useScrollToError";
@@ -0,0 +1,63 @@
1
+ import { useCallback, useState } from "react";
2
+ import { Keyboard, Platform, } from "react-native";
3
+ import { useIsScreenReaderEnabled } from "../../../hooks/useIsScreenReaderEnabled";
4
+ import { useErrorMessageContext } from "../../../ErrorMessageWrapper";
5
+ export function useScrollToError({ formState: { errors, isValid, submitCount }, refNode, setFocus, scrollToPosition, }) {
6
+ const [submitCounter, setSubmitCounter] = useState(submitCount);
7
+ const isScreenReaderEnabled = useIsScreenReaderEnabled();
8
+ const manuallyScrollToElement = useManuallyScrollToElement(handleScroll, refNode);
9
+ // Determine if the form has been submitted by checking if the submit count
10
+ // went up.
11
+ const hasBeenSubmitted = submitCounter < submitCount;
12
+ if (!hasBeenSubmitted)
13
+ return;
14
+ if (isScreenReaderEnabled) {
15
+ manuallyScrollToElement();
16
+ Keyboard.dismiss();
17
+ }
18
+ else {
19
+ defaultAutoScroll();
20
+ }
21
+ setSubmitCounter(submitCount);
22
+ function defaultAutoScroll() {
23
+ if (isValid)
24
+ return;
25
+ try {
26
+ focusInputWithRHF(errors, setFocus);
27
+ }
28
+ catch (_a) {
29
+ manuallyScrollToElement();
30
+ }
31
+ }
32
+ function handleScroll(...[x, y]) {
33
+ /**
34
+ * Disable scroll animation on android when screen reader is active since it
35
+ * can't accessibility-focus on an offscreen component.
36
+ */
37
+ const isAndroidWithScreenReader = isScreenReaderEnabled && Platform.OS === "android";
38
+ const shouldAnimateScroll = !isAndroidWithScreenReader;
39
+ scrollToPosition === null || scrollToPosition === void 0 ? void 0 : scrollToPosition(x, y, shouldAnimateScroll);
40
+ }
41
+ }
42
+ function focusInputWithRHF(errors, setFocus) {
43
+ const errorMessages = Object.keys(errors);
44
+ setFocus(errorMessages[0]);
45
+ }
46
+ /**
47
+ * Manually scroll to the element by checking which elements has an error from
48
+ * the Error Message Context
49
+ */
50
+ function useManuallyScrollToElement(callback, refNode) {
51
+ const { elements } = useErrorMessageContext();
52
+ return useCallback(() => {
53
+ const elementWithError = Object.keys(elements).find(el => elements[el].hasErrorMessage);
54
+ if (elementWithError) {
55
+ const element = elements[elementWithError];
56
+ refNode && element.measure(refNode, callback, handleError);
57
+ element.accessibilityFocus();
58
+ }
59
+ }, [callback, elements, refNode]);
60
+ }
61
+ function handleError() {
62
+ return Error("Couldn't scroll to error");
63
+ }
@@ -0,0 +1,4 @@
1
+ export { Form } from "./Form";
2
+ export { FormMessage } from "./components/FormMessage";
3
+ export { FormSubmitErrorType, FormBannerMessageType } from "./types";
4
+ export * from "./context";
@@ -0,0 +1,28 @@
1
+ import { defineMessages } from "react-intl";
2
+ export const messages = defineMessages({
3
+ loadingA11YLabel: {
4
+ id: "loadingA11yLabel",
5
+ defaultMessage: "Loading",
6
+ description: "Accessibility label for the loading indicator",
7
+ },
8
+ dismissAlertButton: {
9
+ id: "dismiss",
10
+ defaultMessage: "Dismiss",
11
+ description: "The label for the button to dismiss the alert ",
12
+ },
13
+ retryAlertButton: {
14
+ id: "retry",
15
+ defaultMessage: "Try Again",
16
+ description: "The label for the alert button to try action again",
17
+ },
18
+ unavailableNetworkTitle: {
19
+ id: "unavailableNetworkTitle",
20
+ defaultMessage: "Network unavailable",
21
+ description: "The title for alert about network unavailable",
22
+ },
23
+ unavailableNetworkMessage: {
24
+ id: "unavailableNetworkMessage",
25
+ defaultMessage: "Check your internet connection and try again later.",
26
+ description: "The message for alert about network unavailable",
27
+ },
28
+ });
@@ -0,0 +1,10 @@
1
+ export var FormSubmitErrorType;
2
+ (function (FormSubmitErrorType) {
3
+ FormSubmitErrorType["NetworkError"] = "NetworkError";
4
+ FormSubmitErrorType["UserError"] = "UserErrors";
5
+ })(FormSubmitErrorType || (FormSubmitErrorType = {}));
6
+ export var FormBannerMessageType;
7
+ (function (FormBannerMessageType) {
8
+ FormBannerMessageType["WarningMessage"] = "WarningMessage";
9
+ FormBannerMessageType["NoticeMessage"] = "NoticeMessage";
10
+ })(FormBannerMessageType || (FormBannerMessageType = {}));
@@ -0,0 +1,76 @@
1
+ import React, { useMemo, useState } from "react";
2
+ import DateTimePicker from "react-native-modal-datetime-picker";
3
+ import { Platform } from "react-native";
4
+ import { useIntl } from "react-intl";
5
+ import { utcToZonedTime } from "date-fns-tz";
6
+ import { format as formatDate } from "date-fns";
7
+ import { messages } from "./messages";
8
+ import { FormField } from "../FormField";
9
+ import { InputPressable } from "../InputPressable";
10
+ import { useAtlantisContext } from "../AtlantisContext";
11
+ function formatInvalidState(error, invalid) {
12
+ if (invalid)
13
+ return invalid;
14
+ if (error && error.message) {
15
+ return error.message;
16
+ }
17
+ return Boolean(error);
18
+ }
19
+ const display = Platform.OS === "ios" ? "inline" : "default";
20
+ /**
21
+ * Allow users to select a date using the device date picker.
22
+ */
23
+ export function InputDate(props) {
24
+ if (props.name) {
25
+ return (React.createElement(FormField, { name: props.name, defaultValue: props.defaultValue, validations: props.validations }, ({ value, onChange, onBlur }, error) => (React.createElement(InternalInputDate, Object.assign({}, props, { value: value, onChange: (newValue) => {
26
+ var _a;
27
+ onChange(newValue);
28
+ onBlur();
29
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, newValue);
30
+ }, invalid: formatInvalidState(error, props.invalid) })))));
31
+ }
32
+ return React.createElement(InternalInputDate, Object.assign({}, props));
33
+ }
34
+ function InternalInputDate({ clearable = "always", disabled, emptyValueLabel, invalid, maxDate, minDate, placeholder, value, name, onChange, accessibilityLabel, accessibilityHint, }) {
35
+ const [showPicker, setShowPicker] = useState(false);
36
+ const { formatMessage } = useIntl();
37
+ const { timeZone, dateFormat } = useAtlantisContext();
38
+ const date = useMemo(() => {
39
+ if (typeof value === "string")
40
+ return new Date(value);
41
+ return value;
42
+ }, [value]);
43
+ const formattedDate = useMemo(() => {
44
+ if (date) {
45
+ const zonedTime = utcToZonedTime(date, timeZone);
46
+ return formatDate(zonedTime, dateFormat);
47
+ }
48
+ return emptyValueLabel;
49
+ }, [date, emptyValueLabel, timeZone, dateFormat]);
50
+ const canClearDate = formattedDate === emptyValueLabel ? "never" : clearable;
51
+ const placeholderLabel = placeholder !== null && placeholder !== void 0 ? placeholder : formatMessage(messages.datePlaceholder);
52
+ return (React.createElement(React.Fragment, null,
53
+ React.createElement(InputPressable, { clearable: canClearDate, disabled: disabled, invalid: invalid, placeholder: placeholderLabel, prefix: { icon: "calendar" }, value: formattedDate, onClear: handleClear, onPress: showDatePicker, accessibilityLabel: accessibilityLabel, accessibilityHint: accessibilityHint }),
54
+ React.createElement(DateTimePicker, { testID: "inputDate-datePicker", date: date || undefined, display: display, isVisible: showPicker, maximumDate: maxDate, minimumDate: minDate, mode: "date", onCancel: handleCancel, onConfirm: handleConfirm })));
55
+ function showDatePicker() {
56
+ setShowPicker(true);
57
+ }
58
+ function handleConfirm(newVal) {
59
+ setShowPicker(false);
60
+ onChange === null || onChange === void 0 ? void 0 : onChange(newVal);
61
+ }
62
+ function handleCancel() {
63
+ setShowPicker(false);
64
+ // Ensure a change happens so we trigger the validation of one exists
65
+ onChange === null || onChange === void 0 ? void 0 : onChange(date);
66
+ }
67
+ function handleClear() {
68
+ // Returns null only for Form controlled scenarios due to a limitation of react-hook-form that doesn't allow passing undefined to form values.
69
+ if (name) {
70
+ onChange === null || onChange === void 0 ? void 0 : onChange(null);
71
+ }
72
+ else {
73
+ onChange === null || onChange === void 0 ? void 0 : onChange(undefined);
74
+ }
75
+ }
76
+ }
@@ -0,0 +1 @@
1
+ export { InputDate } from "./InputDate";
@@ -0,0 +1,8 @@
1
+ import { defineMessages } from "react-intl";
2
+ export const messages = defineMessages({
3
+ datePlaceholder: {
4
+ id: "datePlaceholder",
5
+ defaultMessage: "Date",
6
+ description: "Default input label for the InputDate component",
7
+ },
8
+ });
@@ -0,0 +1,67 @@
1
+ import React, { useCallback, useRef, useState } from "react";
2
+ import { Pressable, View, useWindowDimensions, } from "react-native";
3
+ import { Portal } from "react-native-portalize";
4
+ import { useIntl } from "react-intl";
5
+ import { useSafeAreaFrame } from "react-native-safe-area-context";
6
+ import { styles } from "./Menu.style";
7
+ import { messages } from "./messages";
8
+ import { findViewpoint } from "./utils";
9
+ import { MenuOption } from "./components/MenuOption";
10
+ import { Overlay } from "./components/Overlay";
11
+ import { tokens } from "../utils/design";
12
+ import { Button } from "../Button";
13
+ import { Content } from "../Content";
14
+ import { useAtlantisContext } from "../AtlantisContext";
15
+ export function Menu({ menuOptions, customActivator }) {
16
+ const [open, setOpen] = useState(false);
17
+ const [menuPosition, setMenuPosition] = useState();
18
+ const activatorLayout = useRef();
19
+ const menuButtonRef = useRef();
20
+ const screenInfo = useScreenInformation();
21
+ const { formatMessage } = useIntl();
22
+ const findMenuLayout = useCallback(() => {
23
+ if (activatorLayout.current) {
24
+ setMenuPosition(findViewpoint(screenInfo, activatorLayout.current));
25
+ }
26
+ }, [screenInfo, activatorLayout]);
27
+ const activatorOnPress = (onPress) => {
28
+ var _a;
29
+ (_a = menuButtonRef.current) === null || _a === void 0 ? void 0 : _a.measureInWindow((x, y, width, height) => {
30
+ activatorLayout.current = {
31
+ x,
32
+ y,
33
+ width,
34
+ height,
35
+ };
36
+ findMenuLayout();
37
+ setOpen(true);
38
+ onPress && onPress();
39
+ });
40
+ };
41
+ return (React.createElement(React.Fragment, null,
42
+ React.createElement(View, { ref: ref => {
43
+ menuButtonRef.current = ref;
44
+ }, collapsable: false },
45
+ customActivator && (React.createElement(Pressable, { style: ({ pressed }) => [
46
+ {
47
+ opacity: pressed ? tokens["opacity-pressed"] : 1,
48
+ },
49
+ ], pointerEvents: "box-only", onPress: () => {
50
+ activatorOnPress(customActivator.props.onPress);
51
+ }, onLongPress: customActivator.props.onLongPress }, customActivator)),
52
+ !customActivator && (React.createElement(Button, { icon: "more", accessibilityLabel: formatMessage(messages.more), variation: "cancel", type: "tertiary", onPress: () => {
53
+ activatorOnPress();
54
+ } }))),
55
+ React.createElement(Portal, null, open && (React.createElement(React.Fragment, null,
56
+ React.createElement(Overlay, { setOpen: setOpen }),
57
+ React.createElement(View, { style: [open && menuPosition, styles.menu] },
58
+ React.createElement(Content, { spacing: "none", childSpacing: "small" }, menuOptions === null || menuOptions === void 0 ? void 0 : menuOptions.map((menuOption, index) => {
59
+ return (React.createElement(MenuOption, Object.assign({}, menuOption, { key: index, setOpen: setOpen })));
60
+ }))))))));
61
+ }
62
+ function useScreenInformation() {
63
+ const { headerHeight } = useAtlantisContext();
64
+ const windowWidth = useWindowDimensions().width;
65
+ const { height: windowHeight } = useSafeAreaFrame();
66
+ return { headerHeight, windowWidth, windowHeight };
67
+ }
@@ -0,0 +1,6 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { tokens } from "../utils/design";
3
+ const menuWidth = 208;
4
+ export const styles = StyleSheet.create({
5
+ menu: Object.assign({ position: "absolute", backgroundColor: tokens["color-surface"], paddingHorizontal: tokens["space-small"], paddingVertical: tokens["space-small"] + tokens["space-smallest"], borderRadius: tokens["radius-larger"], width: menuWidth }, tokens["shadow-high"]),
6
+ });
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import { Pressable, View } from "react-native";
3
+ import capitalize from "lodash/capitalize";
4
+ import { styles } from "./MenuOption.style";
5
+ import { tokens } from "../../../utils/design";
6
+ import { Flex } from "../../../Flex";
7
+ import { Typography } from "../../../Typography";
8
+ import { Icon } from "../../../Icon";
9
+ export function MenuOption({ label, icon, iconColor = "heading", textAlign, destructive, textTransform = "capitalize", onPress, setOpen, }) {
10
+ const destructiveColor = "critical";
11
+ const textVariation = destructive ? destructiveColor : "heading";
12
+ return (React.createElement(View, { testID: "ATL-MENU-OPTIONS" },
13
+ React.createElement(Pressable, { style: ({ pressed }) => [
14
+ styles.menuOption,
15
+ { opacity: pressed ? tokens["opacity-pressed"] : 1 },
16
+ ], onPress: () => {
17
+ onPress();
18
+ setOpen(false);
19
+ }, accessibilityLabel: label },
20
+ React.createElement(Flex, { template: ["grow", "shrink"], align: "flex-start", gap: "smaller" },
21
+ React.createElement(Typography, { selectable: false, color: textVariation, fontWeight: "semiBold", lineHeight: "large", align: textAlign }, textTransform === "capitalize"
22
+ ? capitalize(label.toLocaleLowerCase())
23
+ : label),
24
+ icon && (React.createElement(Icon, { name: icon, color: destructive ? destructiveColor : iconColor }))))));
25
+ }
@@ -0,0 +1,10 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { tokens } from "../../../utils/design";
3
+ export const styles = StyleSheet.create({
4
+ menuOption: {
5
+ display: "flex",
6
+ paddingHorizontal: tokens["space-base"],
7
+ paddingVertical: tokens["space-small"],
8
+ borderRadius: tokens["radius-large"],
9
+ },
10
+ });
@@ -0,0 +1 @@
1
+ export { MenuOption } from "./MenuOption";
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { Pressable, View } from "react-native";
3
+ import { styles } from "./Overlay.style";
4
+ export function Overlay({ setOpen }) {
5
+ return (React.createElement(Pressable, { onPressIn: () => {
6
+ setOpen(false);
7
+ } },
8
+ React.createElement(View, { style: styles.overlay })));
9
+ }
@@ -0,0 +1,6 @@
1
+ import { Dimensions, StyleSheet } from "react-native";
2
+ import { tokens } from "../../../utils/design";
3
+ const { height } = Dimensions.get("window");
4
+ export const styles = StyleSheet.create({
5
+ overlay: Object.assign(Object.assign({}, StyleSheet.absoluteFillObject), { backgroundColor: tokens["color-overlay"], opacity: 0, height }),
6
+ });
@@ -0,0 +1 @@
1
+ export { Overlay } from "./Overlay";
@@ -0,0 +1 @@
1
+ export { Menu } from "./Menu";
@@ -0,0 +1,8 @@
1
+ import { defineMessages } from "react-intl";
2
+ export const messages = defineMessages({
3
+ more: {
4
+ id: "more",
5
+ defaultMessage: "Menu",
6
+ description: "Accessibility label to open the menu",
7
+ },
8
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,84 @@
1
+ import { styles } from "./Menu.style";
2
+ export function findViewpoint(screenInfo, activatorLayout) {
3
+ const { windowHeight, windowWidth, headerHeight } = screenInfo;
4
+ const pos = {};
5
+ const menuWidth = styles.menu.width;
6
+ const windowHalf = (windowHeight - headerHeight) / 2 + headerHeight;
7
+ const menuPositionVertical = activatorLayout.y + activatorLayout.height > windowHalf
8
+ ? "menuAbove"
9
+ : "menuBelow";
10
+ const menuPositionHorizontal = windowWidth / 2 > activatorLayout.width / 2 + activatorLayout.x
11
+ ? "menuRight"
12
+ : "menuLeft";
13
+ const menuPadding = 36;
14
+ getVerticalPosition(pos, windowHeight, activatorLayout, menuPositionVertical);
15
+ getHorizontalPosition(pos, activatorLayout, windowWidth, menuPadding, menuWidth, menuPositionHorizontal);
16
+ return pos;
17
+ }
18
+ function getVerticalPosition(pos, windowHeight, activatorLayout, menuPositionVertical) {
19
+ if (menuPositionVertical === "menuAbove") {
20
+ getAbovePosition(pos, windowHeight, activatorLayout);
21
+ }
22
+ if (menuPositionVertical === "menuBelow") {
23
+ getBelowPosition(pos, activatorLayout);
24
+ }
25
+ }
26
+ function getBelowPosition(pos, activatorLayout) {
27
+ pos.top = activatorLayout.y + activatorLayout.height;
28
+ }
29
+ function getAbovePosition(pos, windowHeight, activatorLayout) {
30
+ pos.bottom = windowHeight - activatorLayout.y;
31
+ }
32
+ function getHorizontalPosition(pos, activatorLayout, windowWidth, menuPadding, menuWidth, menuPositionHorizontal) {
33
+ if (menuPositionHorizontal === "menuRight") {
34
+ getRightPosition(pos, activatorLayout, windowWidth, menuPadding, menuWidth);
35
+ }
36
+ if (menuPositionHorizontal === "menuLeft") {
37
+ getLeftPosition(pos, activatorLayout, windowWidth, menuPadding, menuWidth);
38
+ }
39
+ }
40
+ function getLeftPosition(pos, activatorLayout, windowWidth, menuHorizontalPadding, menuWidth) {
41
+ const overflowLeft = windowWidth -
42
+ activatorLayout.x -
43
+ activatorLayout.width +
44
+ activatorLayout.width / 2 -
45
+ menuHorizontalPadding +
46
+ menuWidth >
47
+ windowWidth;
48
+ const overflowRight = windowWidth -
49
+ activatorLayout.x -
50
+ activatorLayout.width +
51
+ activatorLayout.width / 2 -
52
+ menuHorizontalPadding <
53
+ 0;
54
+ if (overflowLeft) {
55
+ pos.right = undefined;
56
+ pos.left = 0;
57
+ }
58
+ else if (overflowRight) {
59
+ pos.right = 0;
60
+ }
61
+ else {
62
+ pos.right =
63
+ windowWidth -
64
+ activatorLayout.x -
65
+ activatorLayout.width +
66
+ activatorLayout.width / 2 -
67
+ menuHorizontalPadding;
68
+ }
69
+ }
70
+ function getRightPosition(pos, activatorLayout, windowWidth, menuPadding, menuWidth) {
71
+ const overflowRight = activatorLayout.x + activatorLayout.width / 2 - menuPadding + menuWidth >
72
+ windowWidth;
73
+ const overflowLeft = activatorLayout.x + activatorLayout.width / 2 - menuPadding < 0;
74
+ if (overflowRight) {
75
+ pos.left = undefined;
76
+ pos.right = 0;
77
+ }
78
+ else if (overflowLeft) {
79
+ pos.left = 0;
80
+ }
81
+ else {
82
+ pos.left = activatorLayout.x + activatorLayout.width / 2 - menuPadding;
83
+ }
84
+ }
package/dist/src/index.js CHANGED
@@ -16,18 +16,21 @@ export * from "./Divider";
16
16
  export * from "./EmptyState";
17
17
  export * from "./ErrorMessageWrapper";
18
18
  export * from "./Flex";
19
+ export * from "./Form";
19
20
  export * from "./FormField";
20
21
  export * from "./Heading";
21
22
  export * from "./Icon";
22
23
  export * from "./IconButton";
23
24
  export * from "./InputFieldWrapper";
24
25
  export * from "./InputCurrency";
26
+ export * from "./InputDate";
25
27
  export * from "./InputNumber";
26
28
  export * from "./InputPassword";
27
29
  export * from "./InputPressable";
28
30
  export * from "./InputSearch";
29
31
  export * from "./InputTime";
30
32
  export * from "./InputText";
33
+ export * from "./Menu";
31
34
  export * from "./TextList";
32
35
  export * from "./ProgressBar";
33
36
  export * from "./Select";