@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.
- package/dist/src/AtlantisContext/AtlantisContext.js +2 -0
- package/dist/src/Form/Form.js +187 -0
- package/dist/src/Form/Form.style.js +33 -0
- package/dist/src/Form/components/FormActionBar/FormActionBar.js +21 -0
- package/dist/src/Form/components/FormActionBar/FormActionBar.style.js +5 -0
- package/dist/src/Form/components/FormActionBar/index.js +1 -0
- package/dist/src/Form/components/FormBody/FormBody.js +20 -0
- package/dist/src/Form/components/FormBody/FormBody.style.js +26 -0
- package/dist/src/Form/components/FormBody/index.js +1 -0
- package/dist/src/Form/components/FormCache/FormCache.js +34 -0
- package/dist/src/Form/components/FormErrorBanner/FormErrorBanner.js +21 -0
- package/dist/src/Form/components/FormErrorBanner/index.js +1 -0
- package/dist/src/Form/components/FormErrorBanner/messages.js +13 -0
- package/dist/src/Form/components/FormMask/FormMask.js +11 -0
- package/dist/src/Form/components/FormMask/FormMask.style.js +15 -0
- package/dist/src/Form/components/FormMask/index.js +1 -0
- package/dist/src/Form/components/FormMessage/FormMessage.js +48 -0
- package/dist/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.js +28 -0
- package/dist/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.style.js +17 -0
- package/dist/src/Form/components/FormMessage/components/InternalFormMessage/index.js +1 -0
- package/dist/src/Form/components/FormMessage/components/InternalFormMessage/messages.js +8 -0
- package/dist/src/Form/components/FormMessage/index.js +1 -0
- package/dist/src/Form/components/FormMessageBanner/FormMessageBanner.js +15 -0
- package/dist/src/Form/components/FormMessageBanner/index.js +1 -0
- package/dist/src/Form/components/FormSaveButton/FormSaveButton.js +69 -0
- package/dist/src/Form/components/FormSaveButton/index.js +1 -0
- package/dist/src/Form/components/FormSaveButton/messages.js +8 -0
- package/dist/src/Form/constants.js +2 -0
- package/dist/src/Form/context/AtlantisFormContext.js +16 -0
- package/dist/src/Form/context/index.js +1 -0
- package/dist/src/Form/context/types.js +1 -0
- package/dist/src/Form/hooks/useFormViewRefs.js +14 -0
- package/dist/src/Form/hooks/useInternalForm.js +37 -0
- package/dist/src/Form/hooks/useOfflineHandler.js +24 -0
- package/dist/src/Form/hooks/useSaveButtonPosition.js +25 -0
- package/dist/src/Form/hooks/useScreenInformation.js +15 -0
- package/dist/src/Form/hooks/useScrollToError/index.js +1 -0
- package/dist/src/Form/hooks/useScrollToError/useScrollToError.js +63 -0
- package/dist/src/Form/index.js +4 -0
- package/dist/src/Form/messages.js +28 -0
- package/dist/src/Form/types.js +10 -0
- package/dist/src/InputDate/InputDate.js +76 -0
- package/dist/src/InputDate/index.js +1 -0
- package/dist/src/InputDate/messages.js +8 -0
- package/dist/src/Menu/Menu.js +67 -0
- package/dist/src/Menu/Menu.style.js +6 -0
- package/dist/src/Menu/components/MenuOption/MenuOption.js +25 -0
- package/dist/src/Menu/components/MenuOption/MenuOption.style.js +10 -0
- package/dist/src/Menu/components/MenuOption/index.js +1 -0
- package/dist/src/Menu/components/Overlay/Overlay.js +9 -0
- package/dist/src/Menu/components/Overlay/Overlay.style.js +6 -0
- package/dist/src/Menu/components/Overlay/index.js +1 -0
- package/dist/src/Menu/index.js +1 -0
- package/dist/src/Menu/messages.js +8 -0
- package/dist/src/Menu/types.js +1 -0
- package/dist/src/Menu/utils.js +84 -0
- package/dist/src/index.js +3 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/src/AtlantisContext/AtlantisContext.d.ts +7 -1
- package/dist/types/src/Form/Form.d.ts +4 -0
- package/dist/types/src/Form/Form.style.d.ts +31 -0
- package/dist/types/src/Form/components/FormActionBar/FormActionBar.d.ts +13 -0
- package/dist/types/src/Form/components/FormActionBar/FormActionBar.style.d.ts +15 -0
- package/dist/types/src/Form/components/FormActionBar/index.d.ts +2 -0
- package/dist/types/src/Form/components/FormBody/FormBody.d.ts +10 -0
- package/dist/types/src/Form/components/FormBody/FormBody.style.d.ts +24 -0
- package/dist/types/src/Form/components/FormBody/index.d.ts +1 -0
- package/dist/types/src/Form/components/FormCache/FormCache.d.ts +10 -0
- package/dist/types/src/Form/components/FormErrorBanner/FormErrorBanner.d.ts +3 -0
- package/dist/types/src/Form/components/FormErrorBanner/index.d.ts +1 -0
- package/dist/types/src/Form/components/FormErrorBanner/messages.d.ts +12 -0
- package/dist/types/src/Form/components/FormMask/FormMask.d.ts +2 -0
- package/dist/types/src/Form/components/FormMask/FormMask.style.d.ts +13 -0
- package/dist/types/src/Form/components/FormMask/index.d.ts +1 -0
- package/dist/types/src/Form/components/FormMessage/FormMessage.d.ts +19 -0
- package/dist/types/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.d.ts +8 -0
- package/dist/types/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.style.d.ts +20 -0
- package/dist/types/src/Form/components/FormMessage/components/InternalFormMessage/index.d.ts +1 -0
- package/dist/types/src/Form/components/FormMessage/components/InternalFormMessage/messages.d.ts +7 -0
- package/dist/types/src/Form/components/FormMessage/index.d.ts +1 -0
- package/dist/types/src/Form/components/FormMessageBanner/FormMessageBanner.d.ts +7 -0
- package/dist/types/src/Form/components/FormMessageBanner/index.d.ts +1 -0
- package/dist/types/src/Form/components/FormSaveButton/FormSaveButton.d.ts +3 -0
- package/dist/types/src/Form/components/FormSaveButton/index.d.ts +1 -0
- package/dist/types/src/Form/components/FormSaveButton/messages.d.ts +7 -0
- package/dist/types/src/Form/constants.d.ts +2 -0
- package/dist/types/src/Form/context/AtlantisFormContext.d.ts +12 -0
- package/dist/types/src/Form/context/index.d.ts +2 -0
- package/dist/types/src/Form/context/types.d.ts +26 -0
- package/dist/types/src/Form/hooks/useFormViewRefs.d.ts +10 -0
- package/dist/types/src/Form/hooks/useInternalForm.d.ts +19 -0
- package/dist/types/src/Form/hooks/useOfflineHandler.d.ts +1 -0
- package/dist/types/src/Form/hooks/useSaveButtonPosition.d.ts +12 -0
- package/dist/types/src/Form/hooks/useScreenInformation.d.ts +8 -0
- package/dist/types/src/Form/hooks/useScrollToError/index.d.ts +1 -0
- package/dist/types/src/Form/hooks/useScrollToError/useScrollToError.d.ts +10 -0
- package/dist/types/src/Form/index.d.ts +5 -0
- package/dist/types/src/Form/messages.d.ts +27 -0
- package/dist/types/src/Form/types.d.ts +199 -0
- package/dist/types/src/InputDate/InputDate.d.ts +74 -0
- package/dist/types/src/InputDate/index.d.ts +1 -0
- package/dist/types/src/InputDate/messages.d.ts +7 -0
- package/dist/types/src/InputNumber/InputNumber.d.ts +1 -1
- package/dist/types/src/Menu/Menu.d.ts +3 -0
- package/dist/types/src/Menu/Menu.style.d.ts +18 -0
- package/dist/types/src/Menu/components/MenuOption/MenuOption.d.ts +3 -0
- package/dist/types/src/Menu/components/MenuOption/MenuOption.style.d.ts +8 -0
- package/dist/types/src/Menu/components/MenuOption/index.d.ts +1 -0
- package/dist/types/src/Menu/components/Overlay/Overlay.d.ts +3 -0
- package/dist/types/src/Menu/components/Overlay/Overlay.style.d.ts +12 -0
- package/dist/types/src/Menu/components/Overlay/index.d.ts +1 -0
- package/dist/types/src/Menu/index.d.ts +2 -0
- package/dist/types/src/Menu/messages.d.ts +7 -0
- package/dist/types/src/Menu/types.d.ts +22 -0
- package/dist/types/src/Menu/utils.d.ts +10 -0
- package/dist/types/src/index.d.ts +3 -0
- package/package.json +3 -2
- package/src/AtlantisContext/AtlantisContext.tsx +10 -1
- package/src/Form/Form.style.ts +34 -0
- package/src/Form/Form.test.tsx +588 -0
- package/src/Form/Form.tsx +296 -0
- package/src/Form/components/FormActionBar/FormActionBar.style.ts +11 -0
- package/src/Form/components/FormActionBar/FormActionBar.tsx +63 -0
- package/src/Form/components/FormActionBar/index.ts +2 -0
- package/src/Form/components/FormBody/FormBody.style.ts +27 -0
- package/src/Form/components/FormBody/FormBody.tsx +62 -0
- package/src/Form/components/FormBody/index.ts +1 -0
- package/src/Form/components/FormCache/FormCache.tsx +50 -0
- package/src/Form/components/FormErrorBanner/FormErrorBanner.test.tsx +124 -0
- package/src/Form/components/FormErrorBanner/FormErrorBanner.tsx +34 -0
- package/src/Form/components/FormErrorBanner/index.ts +1 -0
- package/src/Form/components/FormErrorBanner/messages.ts +14 -0
- package/src/Form/components/FormMask/FormMask.style.tsx +16 -0
- package/src/Form/components/FormMask/FormMask.tsx +19 -0
- package/src/Form/components/FormMask/index.ts +1 -0
- package/src/Form/components/FormMessage/FormMessage.test.tsx +72 -0
- package/src/Form/components/FormMessage/FormMessage.tsx +63 -0
- package/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.style.ts +18 -0
- package/src/Form/components/FormMessage/components/InternalFormMessage/InternalFormMessage.tsx +55 -0
- package/src/Form/components/FormMessage/components/InternalFormMessage/index.ts +1 -0
- package/src/Form/components/FormMessage/components/InternalFormMessage/messages.ts +10 -0
- package/src/Form/components/FormMessage/index.ts +1 -0
- package/src/Form/components/FormMessageBanner/FormMessageBanner.test.tsx +27 -0
- package/src/Form/components/FormMessageBanner/FormMessageBanner.tsx +33 -0
- package/src/Form/components/FormMessageBanner/index.ts +1 -0
- package/src/Form/components/FormSaveButton/FormSaveButton.test.tsx +159 -0
- package/src/Form/components/FormSaveButton/FormSaveButton.tsx +103 -0
- package/src/Form/components/FormSaveButton/index.ts +1 -0
- package/src/Form/components/FormSaveButton/messages.ts +9 -0
- package/src/Form/constants.ts +2 -0
- package/src/Form/context/AtlantisFormContext.test.tsx +45 -0
- package/src/Form/context/AtlantisFormContext.tsx +21 -0
- package/src/Form/context/index.ts +5 -0
- package/src/Form/context/types.ts +34 -0
- package/src/Form/hooks/useFormViewRefs.ts +23 -0
- package/src/Form/hooks/useInternalForm.ts +99 -0
- package/src/Form/hooks/useOfflineHandler.ts +36 -0
- package/src/Form/hooks/useSaveButtonPosition.ts +52 -0
- package/src/Form/hooks/useScreenInformation.ts +25 -0
- package/src/Form/hooks/useScrollToError/index.ts +1 -0
- package/src/Form/hooks/useScrollToError/useScrollToError.test.tsx +103 -0
- package/src/Form/hooks/useScrollToError/useScrollToError.ts +102 -0
- package/src/Form/index.ts +13 -0
- package/src/Form/messages.ts +33 -0
- package/src/Form/types.ts +255 -0
- package/src/InputDate/InputDate.test.tsx +295 -0
- package/src/InputDate/InputDate.tsx +231 -0
- package/src/InputDate/index.ts +1 -0
- package/src/InputDate/messages.ts +9 -0
- package/src/InputNumber/InputNumber.tsx +1 -1
- package/src/Menu/Menu.style.ts +16 -0
- package/src/Menu/Menu.test.tsx +201 -0
- package/src/Menu/Menu.tsx +116 -0
- package/src/Menu/components/MenuOption/MenuOption.style.tsx +11 -0
- package/src/Menu/components/MenuOption/MenuOption.tsx +63 -0
- package/src/Menu/components/MenuOption/index.ts +1 -0
- package/src/Menu/components/Overlay/Overlay.style.ts +13 -0
- package/src/Menu/components/Overlay/Overlay.tsx +16 -0
- package/src/Menu/components/Overlay/index.ts +1 -0
- package/src/Menu/index.ts +6 -0
- package/src/Menu/messages.ts +9 -0
- package/src/Menu/types.ts +25 -0
- package/src/Menu/utils.ts +151 -0
- package/src/index.ts +3 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { MutableRefObject, RefObject } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ControllerProps,
|
|
4
|
+
DeepPartial,
|
|
5
|
+
FieldPath,
|
|
6
|
+
FieldValues,
|
|
7
|
+
Mode,
|
|
8
|
+
UnpackNestedValue,
|
|
9
|
+
UseFormReturn,
|
|
10
|
+
} from "react-hook-form";
|
|
11
|
+
import { IconNames } from "@jobber/design";
|
|
12
|
+
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
|
13
|
+
|
|
14
|
+
export type FormValues<T> = UnpackNestedValue<T>;
|
|
15
|
+
export type FormErrors = FormNetworkErrors | FormUserErrors;
|
|
16
|
+
export type FormBannerMessage = FormWarningMessage | FormNoticeMessage;
|
|
17
|
+
|
|
18
|
+
export enum FormSubmitErrorType {
|
|
19
|
+
NetworkError = "NetworkError",
|
|
20
|
+
UserError = "UserErrors",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export enum FormBannerMessageType {
|
|
24
|
+
WarningMessage = "WarningMessage",
|
|
25
|
+
NoticeMessage = "NoticeMessage",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FormBannerErrors {
|
|
29
|
+
networkError?: string | undefined;
|
|
30
|
+
bannerError?: {
|
|
31
|
+
title: string;
|
|
32
|
+
messages?: string[];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface FormNetworkErrors {
|
|
37
|
+
errorType: FormSubmitErrorType.NetworkError;
|
|
38
|
+
networkErrors: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface FormUserErrors {
|
|
42
|
+
errorType: FormSubmitErrorType.UserError;
|
|
43
|
+
userErrors: Record<string, string>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface FormWarningMessage {
|
|
47
|
+
messageType: FormBannerMessageType.WarningMessage;
|
|
48
|
+
message: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface FormNoticeMessage {
|
|
52
|
+
messageType: FormBannerMessageType.NoticeMessage;
|
|
53
|
+
message: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type FormRef<T extends FieldValues = FieldValues> =
|
|
57
|
+
| (UseFormReturn<T> & {
|
|
58
|
+
scrollViewRef?: RefObject<KeyboardAwareScrollView>;
|
|
59
|
+
saveButtonHeight?: number;
|
|
60
|
+
messageBannerHeight?: number;
|
|
61
|
+
})
|
|
62
|
+
| undefined;
|
|
63
|
+
|
|
64
|
+
export interface FormProps<T extends FieldValues, SubmitResponseType> {
|
|
65
|
+
/**
|
|
66
|
+
* Content to be passed into the form
|
|
67
|
+
*/
|
|
68
|
+
children: React.ReactNode;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A callback function that is run before invoking onSubmit. Form submission is canceled if the promise resolves to false.
|
|
72
|
+
*/
|
|
73
|
+
onBeforeSubmit?: (data: FormValues<T>) => Promise<boolean>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A callback function that handles the submission of form data
|
|
77
|
+
*/
|
|
78
|
+
onSubmit: (data: FormValues<T>) => Promise<SubmitResponseType>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A callback function that handles any error that occurs during "onSubmit"
|
|
82
|
+
*/
|
|
83
|
+
onSubmitError: (error: FormErrors) => void;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A callback function that handles a successful form submission from "onSubmit"
|
|
87
|
+
*/
|
|
88
|
+
onSubmitSuccess: (data: SubmitResponseType) => void;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Network or user errors to be displayed as a banner at the top of the form
|
|
92
|
+
*/
|
|
93
|
+
bannerErrors?: FormBannerErrors;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Status messages to be displayed as a banner at the top of the form
|
|
97
|
+
*/
|
|
98
|
+
bannerMessages?: FormBannerMessage[];
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Loading when the initial form data is being fetched
|
|
102
|
+
*/
|
|
103
|
+
initialLoading?: boolean;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The initial values of the form inputs
|
|
107
|
+
* This should be available as soon as initialLoading is set to false
|
|
108
|
+
*/
|
|
109
|
+
initialValues?: FormValues<DeepPartial<T>>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* When the validation should happen.
|
|
113
|
+
* Possible values are "onBlur", "onChange", "onSubmit", "onTouched", and "all".
|
|
114
|
+
* The default value is "onTouched"
|
|
115
|
+
*/
|
|
116
|
+
mode?: Mode;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* When the validation after submission should happen.
|
|
120
|
+
* Possible values are "onBlur", "onChange", and "onSubmit".
|
|
121
|
+
* The default value is "onChange"
|
|
122
|
+
*/
|
|
123
|
+
reValidateMode?: Exclude<Mode, "onTouched" | "all">;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* ref object to access react hook form methods and state
|
|
127
|
+
*/
|
|
128
|
+
formRef?: MutableRefObject<FormRef<T> | undefined>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Label to be displayed for the save button
|
|
132
|
+
*/
|
|
133
|
+
saveButtonLabel?: string;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @deprecated use `secondaryAction` instead.
|
|
137
|
+
* Override default save button in the sticky section of the form with another element.
|
|
138
|
+
*/
|
|
139
|
+
renderStickySection?: (
|
|
140
|
+
onSubmit: () => void,
|
|
141
|
+
label: string | undefined,
|
|
142
|
+
isSubmitting: boolean,
|
|
143
|
+
) => JSX.Element;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Adding a key will save a local copy of the form data that will be used to
|
|
147
|
+
* recover values when the app is backgrounded or has crashed.
|
|
148
|
+
*/
|
|
149
|
+
localCacheKey?: string;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Forms field names that will not be considered for caching.
|
|
153
|
+
* Useful for omitting sensitive data.
|
|
154
|
+
*/
|
|
155
|
+
localCacheExclude?: string[];
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* A string or array of strings that can be used to identify the pre-filled
|
|
159
|
+
* data on the form. This can be used to support local caching for forms that
|
|
160
|
+
* prefill data without inadvertently applying the cache at the wrong time.
|
|
161
|
+
*
|
|
162
|
+
* For example this can be used to when an object is based on data from
|
|
163
|
+
* another object (Quote being converted into a Job). This will allow
|
|
164
|
+
* the user to retrieve data from the cache when trying to create the
|
|
165
|
+
* same object (same Quote being converted into a Job) following an app crash.
|
|
166
|
+
*
|
|
167
|
+
* There is still only one copy of data for each `localCacheKey`.
|
|
168
|
+
* If a user opens the same form the data will only be loaded if the `localCacheId` matches
|
|
169
|
+
*/
|
|
170
|
+
localCacheId?: string | string[];
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Secondary Action for ButtonGroup
|
|
174
|
+
*/
|
|
175
|
+
secondaryActions?: SecondaryActionProp[];
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* A number that will pull down the save button when the position is sticky.
|
|
179
|
+
* Useful when there's a footer or content below the form that is pulling
|
|
180
|
+
* the button up.
|
|
181
|
+
*/
|
|
182
|
+
saveButtonOffset?: number;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Forces to render the sticky save button instead of the inline.
|
|
186
|
+
* The sticky save button is default for iOS but not for Android due to
|
|
187
|
+
* limitations. Use this prop with caution on Android.
|
|
188
|
+
*/
|
|
189
|
+
showStickySaveButton?: boolean;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Renders a footer below the save button.
|
|
193
|
+
*/
|
|
194
|
+
renderFooter?: React.ReactNode;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export type InternalFormProps<T extends FieldValues, SubmitResponseType> = Omit<
|
|
198
|
+
FormProps<T, SubmitResponseType>,
|
|
199
|
+
"initialLoading"
|
|
200
|
+
>;
|
|
201
|
+
|
|
202
|
+
export type ValidationRulesByFieldPath<T extends FieldValues> = {
|
|
203
|
+
[p in FieldPath<T>]: ControllerProps<T, p>["rules"];
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export interface FormSaveButtonProps {
|
|
207
|
+
/**
|
|
208
|
+
* Press handler
|
|
209
|
+
*/
|
|
210
|
+
primaryAction: () => Promise<void> | void;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Primary Button is loading
|
|
214
|
+
*/
|
|
215
|
+
loading: boolean;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Label for the save button
|
|
219
|
+
*/
|
|
220
|
+
label?: string;
|
|
221
|
+
/**
|
|
222
|
+
* Props and information regarding the secondary Action button(s)
|
|
223
|
+
*/
|
|
224
|
+
secondaryActions?: SecondaryActionProp[];
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Set whether secondary Button is loading
|
|
228
|
+
*/
|
|
229
|
+
setSecondaryActionLoading?: (bool: boolean) => void;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Callback that is called when the secondary actions bottom sheet is opened.
|
|
233
|
+
*/
|
|
234
|
+
onOpenBottomSheet?: () => void;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Callback that is called when the secondary actions bottom sheet is closed.
|
|
238
|
+
*/
|
|
239
|
+
onCloseBottomSheet?: () => void;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
interface SecondaryActionOnPress {
|
|
243
|
+
onBeforeSubmit?: () => Promise<boolean>;
|
|
244
|
+
onSubmit: (formSubmit: FormSaveButtonProps["primaryAction"]) => Promise<void>;
|
|
245
|
+
onSubmitSuccess?: () => void;
|
|
246
|
+
onSubmitError?: (error: FormErrors) => void;
|
|
247
|
+
resetFormOnSubmit?: boolean;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface SecondaryActionProp {
|
|
251
|
+
label: string;
|
|
252
|
+
icon?: IconNames | undefined;
|
|
253
|
+
handleAction: SecondaryActionOnPress;
|
|
254
|
+
destructive?: boolean;
|
|
255
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { fireEvent, render, waitFor } from "@testing-library/react-native";
|
|
3
|
+
import { Host } from "react-native-portalize";
|
|
4
|
+
import { FormProvider, useForm } from "react-hook-form";
|
|
5
|
+
import { InputDate } from "./InputDate";
|
|
6
|
+
import { Button } from "../Button";
|
|
7
|
+
import * as atlantisContext from "../AtlantisContext/AtlantisContext";
|
|
8
|
+
|
|
9
|
+
describe("InputDate", () => {
|
|
10
|
+
describe("Visuals", () => {
|
|
11
|
+
const placeholder = "Start time";
|
|
12
|
+
const expectedDate = "May 30, 2022";
|
|
13
|
+
const value = new Date(2022, 4, 30);
|
|
14
|
+
const handleChange = jest.fn();
|
|
15
|
+
|
|
16
|
+
const setup = () =>
|
|
17
|
+
render(
|
|
18
|
+
<InputDate
|
|
19
|
+
placeholder={placeholder}
|
|
20
|
+
value={value}
|
|
21
|
+
onChange={handleChange}
|
|
22
|
+
/>,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
it("should show a calendar prefix icon", () => {
|
|
26
|
+
const screen = setup();
|
|
27
|
+
|
|
28
|
+
const calendarIcon = screen.getByTestId("calendar");
|
|
29
|
+
expect(calendarIcon).toBeDefined();
|
|
30
|
+
expect(calendarIcon.type).toBe("RNSVGSvgView");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should show a formatted date", () => {
|
|
34
|
+
const screen = setup();
|
|
35
|
+
expect(
|
|
36
|
+
screen.getByText(expectedDate, { includeHiddenElements: true }),
|
|
37
|
+
).toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should be clearable when there's a value", () => {
|
|
41
|
+
const screen = setup();
|
|
42
|
+
const clearAction = screen.getByLabelText("Clear input");
|
|
43
|
+
expect(clearAction).toBeDefined();
|
|
44
|
+
|
|
45
|
+
fireEvent.press(clearAction);
|
|
46
|
+
expect(handleChange).toHaveBeenCalledWith(undefined);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("String value", () => {
|
|
51
|
+
it("should show a formatted date", () => {
|
|
52
|
+
const expectedDate = "May 31, 2022";
|
|
53
|
+
const value = new Date(2022, 4, 31).toISOString();
|
|
54
|
+
const screen = render(<InputDate value={value} onChange={jest.fn()} />);
|
|
55
|
+
|
|
56
|
+
expect(
|
|
57
|
+
screen.getByText(expectedDate, { includeHiddenElements: true }),
|
|
58
|
+
).toBeDefined();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("With emptyValueLabel", () => {
|
|
63
|
+
it("should show the emptyValueLabel when there's no value", () => {
|
|
64
|
+
const label = "Unscheduled";
|
|
65
|
+
const screen = render(<InputDate name="test" emptyValueLabel={label} />);
|
|
66
|
+
|
|
67
|
+
expect(
|
|
68
|
+
screen.getByText(label, { includeHiddenElements: true }),
|
|
69
|
+
).toBeDefined();
|
|
70
|
+
expect(screen.queryByLabelText("Clear input")).toBeNull();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should not show the emptyValueLabel when there's a value", () => {
|
|
74
|
+
const label = "Unscheduled";
|
|
75
|
+
const screen = render(
|
|
76
|
+
<InputDate
|
|
77
|
+
emptyValueLabel={label}
|
|
78
|
+
value={new Date()}
|
|
79
|
+
onChange={jest.fn()}
|
|
80
|
+
/>,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(screen.queryByText(label)).toBeNull();
|
|
84
|
+
expect(screen.getByLabelText("Clear input")).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("with a defaultValue", () => {
|
|
89
|
+
it("renders the supplied value", () => {
|
|
90
|
+
const { getByText } = render(
|
|
91
|
+
<InputDate name="test" defaultValue={new Date("2022-01-17")} />,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(
|
|
95
|
+
getByText("Jan 17, 2022", { includeHiddenElements: true }),
|
|
96
|
+
).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("Date picker", () => {
|
|
101
|
+
const placeholder = "Tap me";
|
|
102
|
+
const handleChange = jest.fn();
|
|
103
|
+
|
|
104
|
+
function renderDatePicker() {
|
|
105
|
+
const screen = render(
|
|
106
|
+
<InputDate
|
|
107
|
+
placeholder={placeholder}
|
|
108
|
+
value={undefined}
|
|
109
|
+
onChange={handleChange}
|
|
110
|
+
/>,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
fireEvent.press(screen.getByLabelText(placeholder));
|
|
114
|
+
expect(screen.getByTestId("inputDate-datePicker")).toBeDefined();
|
|
115
|
+
|
|
116
|
+
return screen;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
it("should not show a date picker", () => {
|
|
120
|
+
const screen = render(<InputDate name="test" />);
|
|
121
|
+
expect(screen.queryByTestId("inputDate-datePicker")).toBeNull();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should fire the onChange with the current value after canceling a date selection", () => {
|
|
125
|
+
const screen = renderDatePicker();
|
|
126
|
+
|
|
127
|
+
fireEvent.press(screen.getByLabelText("Cancel"));
|
|
128
|
+
expect(handleChange).toHaveBeenCalledWith(undefined);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should fire the onChange after confirming a date selection", () => {
|
|
132
|
+
const screen = renderDatePicker();
|
|
133
|
+
|
|
134
|
+
fireEvent.press(screen.getByLabelText("Confirm"));
|
|
135
|
+
expect(handleChange).toHaveBeenCalledWith(expect.any(Date));
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
const mockOnSubmit = jest.fn();
|
|
139
|
+
const saveButtonText = "Submit";
|
|
140
|
+
|
|
141
|
+
const requiredError = "This is required";
|
|
142
|
+
function SimpleFormWithProvider({ children, defaultValues }) {
|
|
143
|
+
const formMethods = useForm({
|
|
144
|
+
reValidateMode: "onChange",
|
|
145
|
+
defaultValues,
|
|
146
|
+
mode: "onTouched",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<FormProvider {...formMethods}>
|
|
151
|
+
{children}
|
|
152
|
+
<Button
|
|
153
|
+
onPress={formMethods.handleSubmit(values => mockOnSubmit(values))}
|
|
154
|
+
label={saveButtonText}
|
|
155
|
+
accessibilityLabel={saveButtonText}
|
|
156
|
+
/>
|
|
157
|
+
</FormProvider>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
describe("Form controlled", () => {
|
|
161
|
+
const pickerName = "datePicker";
|
|
162
|
+
const expectedDate = "May 29, 2022";
|
|
163
|
+
const value = new Date(2022, 4, 29);
|
|
164
|
+
const handleChange = jest.fn();
|
|
165
|
+
|
|
166
|
+
const setup = () =>
|
|
167
|
+
render(
|
|
168
|
+
<SimpleFormWithProvider defaultValues={{ [pickerName]: value }}>
|
|
169
|
+
<Host>
|
|
170
|
+
<InputDate
|
|
171
|
+
name={pickerName}
|
|
172
|
+
onChange={handleChange}
|
|
173
|
+
validations={{ required: requiredError }}
|
|
174
|
+
/>
|
|
175
|
+
</Host>
|
|
176
|
+
</SimpleFormWithProvider>,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
it("should show the initial value", async () => {
|
|
180
|
+
const screen = setup();
|
|
181
|
+
expect(
|
|
182
|
+
screen.getByText(expectedDate, {
|
|
183
|
+
includeHiddenElements: true,
|
|
184
|
+
}),
|
|
185
|
+
).toBeDefined();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should update the value", async () => {
|
|
189
|
+
const screen = await setup();
|
|
190
|
+
|
|
191
|
+
fireEvent.press(
|
|
192
|
+
screen.getByText(expectedDate, { includeHiddenElements: true }),
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const expectedNewDate = "Jun 17, 2022";
|
|
196
|
+
const newSelectedDate = new Date(2022, 5, 17);
|
|
197
|
+
fireEvent(
|
|
198
|
+
screen.getByTestId("inputDate-datePicker"),
|
|
199
|
+
"onConfirm",
|
|
200
|
+
newSelectedDate,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
expect(
|
|
204
|
+
screen.getByText(expectedNewDate, { includeHiddenElements: true }),
|
|
205
|
+
).toBeDefined();
|
|
206
|
+
expect(handleChange).toHaveBeenCalledWith(newSelectedDate);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should show the client side validation error", async () => {
|
|
210
|
+
const screen = setup();
|
|
211
|
+
|
|
212
|
+
const clearAction = screen.getByLabelText("Clear input");
|
|
213
|
+
expect(clearAction).toBeDefined();
|
|
214
|
+
|
|
215
|
+
fireEvent.press(clearAction);
|
|
216
|
+
|
|
217
|
+
await waitFor(() => {
|
|
218
|
+
expect(
|
|
219
|
+
screen.getAllByText(requiredError, { includeHiddenElements: true }),
|
|
220
|
+
).toHaveLength(1);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should clear the input with null value when it is in a form", async () => {
|
|
225
|
+
const screen = setup();
|
|
226
|
+
const clearAction = screen.getByLabelText("Clear input");
|
|
227
|
+
expect(clearAction).toBeDefined();
|
|
228
|
+
|
|
229
|
+
fireEvent.press(clearAction);
|
|
230
|
+
expect(handleChange).toHaveBeenCalledWith(null);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe("dateFormat pattern", () => {
|
|
235
|
+
afterEach(() => {
|
|
236
|
+
jest.spyOn(atlantisContext, "useAtlantisContext").mockRestore();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("should display MM/DD/YYYY when dateFormat is 'P'", () => {
|
|
240
|
+
jest.spyOn(atlantisContext, "useAtlantisContext").mockReturnValue({
|
|
241
|
+
...atlantisContext.defaultValues,
|
|
242
|
+
dateFormat: "P",
|
|
243
|
+
});
|
|
244
|
+
const expectedDate = "05/24/2023";
|
|
245
|
+
const value = new Date(2023, 4, 24).toISOString();
|
|
246
|
+
const screen = render(<InputDate value={value} onChange={jest.fn()} />);
|
|
247
|
+
|
|
248
|
+
expect(
|
|
249
|
+
screen.getByText(expectedDate, { includeHiddenElements: true }),
|
|
250
|
+
).toBeDefined();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should display mmmm d, yyyy when dateFormat is 'PP'", () => {
|
|
254
|
+
jest.spyOn(atlantisContext, "useAtlantisContext").mockReturnValue({
|
|
255
|
+
...atlantisContext.defaultValues,
|
|
256
|
+
dateFormat: "PP",
|
|
257
|
+
});
|
|
258
|
+
const expectedDate = "Feb 20, 2023";
|
|
259
|
+
const value = new Date(2023, 1, 20).toISOString();
|
|
260
|
+
const screen = render(<InputDate value={value} onChange={jest.fn()} />);
|
|
261
|
+
|
|
262
|
+
expect(
|
|
263
|
+
screen.getByText(expectedDate, { includeHiddenElements: true }),
|
|
264
|
+
).toBeDefined();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("should display mmmmm d, yyyy when dateFormat is 'PPP'", () => {
|
|
268
|
+
jest.spyOn(atlantisContext, "useAtlantisContext").mockReturnValue({
|
|
269
|
+
...atlantisContext.defaultValues,
|
|
270
|
+
dateFormat: "PPP",
|
|
271
|
+
});
|
|
272
|
+
const expectedDate = "July 7th, 2023";
|
|
273
|
+
const value = new Date(2023, 6, 7).toISOString();
|
|
274
|
+
const screen = render(<InputDate value={value} onChange={jest.fn()} />);
|
|
275
|
+
|
|
276
|
+
expect(
|
|
277
|
+
screen.getByText(expectedDate, { includeHiddenElements: true }),
|
|
278
|
+
).toBeDefined();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("should display dddd, mmmmm d, yyyy when dateFormat is 'PPPP'", () => {
|
|
282
|
+
jest.spyOn(atlantisContext, "useAtlantisContext").mockReturnValue({
|
|
283
|
+
...atlantisContext.defaultValues,
|
|
284
|
+
dateFormat: "PPPP",
|
|
285
|
+
});
|
|
286
|
+
const expectedDate = "Thursday, June 22nd, 2023";
|
|
287
|
+
const value = new Date(2023, 5, 22).toISOString();
|
|
288
|
+
const screen = render(<InputDate value={value} onChange={jest.fn()} />);
|
|
289
|
+
|
|
290
|
+
expect(
|
|
291
|
+
screen.getByText(expectedDate, { includeHiddenElements: true }),
|
|
292
|
+
).toBeDefined();
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|