@evervault/react-native 2.6.0 → 2.6.2

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 (41) hide show
  1. package/package.json +3 -2
  2. package/src/Card/Cvc.test.tsx +41 -0
  3. package/src/Card/Cvc.tsx +58 -0
  4. package/src/Card/Expiry.tsx +26 -0
  5. package/src/Card/Holder.tsx +27 -0
  6. package/src/Card/Number.test.tsx +76 -0
  7. package/src/Card/Number.tsx +54 -0
  8. package/src/Card/Root.test.tsx +341 -0
  9. package/src/Card/Root.tsx +150 -0
  10. package/src/Card/index.ts +28 -0
  11. package/src/Card/schema.ts +41 -0
  12. package/src/Card/types.ts +57 -0
  13. package/src/Card/utils.test.ts +271 -0
  14. package/src/Card/utils.ts +129 -0
  15. package/src/EvervaultProvider.test.tsx +24 -0
  16. package/src/EvervaultProvider.tsx +43 -0
  17. package/src/Input.test.tsx +420 -0
  18. package/src/Input.tsx +182 -0
  19. package/src/ThreeDSecure/Frame.test.tsx +87 -0
  20. package/src/ThreeDSecure/Frame.tsx +50 -0
  21. package/src/ThreeDSecure/Root.test.tsx +67 -0
  22. package/src/ThreeDSecure/Root.tsx +23 -0
  23. package/src/ThreeDSecure/config.ts +3 -0
  24. package/src/ThreeDSecure/context.ts +6 -0
  25. package/src/ThreeDSecure/event.ts +19 -0
  26. package/src/ThreeDSecure/index.ts +17 -0
  27. package/src/ThreeDSecure/session.test.ts +524 -0
  28. package/src/ThreeDSecure/session.ts +184 -0
  29. package/src/ThreeDSecure/types.ts +80 -0
  30. package/src/ThreeDSecure/useThreeDSecure.test.tsx +244 -0
  31. package/src/ThreeDSecure/useThreeDSecure.ts +64 -0
  32. package/src/__mocks__/NativeEvervault.ts +13 -0
  33. package/src/__mocks__/react-native-webview.tsx +6 -0
  34. package/src/context.ts +14 -0
  35. package/src/index.ts +21 -0
  36. package/src/sdk.test.ts +122 -0
  37. package/src/sdk.ts +71 -0
  38. package/src/specs/NativeEvervault.ts +67 -0
  39. package/src/useEvervault.test.tsx +31 -0
  40. package/src/useEvervault.ts +14 -0
  41. package/src/utils.ts +41 -0
@@ -0,0 +1,43 @@
1
+ import {
2
+ PropsWithChildren,
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useState,
7
+ } from "react";
8
+ import { type EvervaultContextValue, EvervaultContext } from "./context";
9
+ import { Encrypted, sdk } from "./sdk";
10
+
11
+ export interface EvervaultProviderProps extends PropsWithChildren {
12
+ teamId: string;
13
+ appId: string;
14
+ }
15
+
16
+ export function EvervaultProvider({
17
+ teamId,
18
+ appId,
19
+ children,
20
+ }: EvervaultProviderProps) {
21
+ const instanceId = useMemo(
22
+ () => sdk.initialize(teamId, appId),
23
+ [teamId, appId]
24
+ );
25
+
26
+ const encrypt = useCallback(
27
+ function <T>(data: T): Promise<Encrypted<T>> {
28
+ return sdk.encrypt(instanceId, data);
29
+ },
30
+ [instanceId]
31
+ );
32
+
33
+ const context = useMemo<EvervaultContextValue>(
34
+ () => ({ teamId, appId, encrypt }),
35
+ [teamId, appId, encrypt]
36
+ );
37
+
38
+ return (
39
+ <EvervaultContext.Provider value={context}>
40
+ {children}
41
+ </EvervaultContext.Provider>
42
+ );
43
+ }
@@ -0,0 +1,420 @@
1
+ import {
2
+ fireEvent,
3
+ render,
4
+ screen,
5
+ userEvent,
6
+ } from "@testing-library/react-native";
7
+ import { EvervaultInput, EvervaultInputContext, mask } from "./Input";
8
+ import { FieldErrors, FormProvider, Resolver, useForm } from "react-hook-form";
9
+ import { PropsWithChildren } from "react";
10
+ import { zodResolver } from "@hookform/resolvers/zod";
11
+ import { z } from "zod";
12
+
13
+ describe("mask", () => {
14
+ it("should convert a mask to an array of regex", () => {
15
+ expect(mask("999-999-9999")).toEqual([
16
+ /\d/,
17
+ /\d/,
18
+ /\d/,
19
+ "-",
20
+ /\d/,
21
+ /\d/,
22
+ /\d/,
23
+ "-",
24
+ /\d/,
25
+ /\d/,
26
+ /\d/,
27
+ /\d/,
28
+ ]);
29
+ });
30
+
31
+ it("should account for obfuscation", () => {
32
+ expect(mask("[9999 9999] 9999")).toEqual([
33
+ [/\d/],
34
+ [/\d/],
35
+ [/\d/],
36
+ [/\d/],
37
+ " ",
38
+ [/\d/],
39
+ [/\d/],
40
+ [/\d/],
41
+ [/\d/],
42
+ " ",
43
+ /\d/,
44
+ /\d/,
45
+ /\d/,
46
+ /\d/,
47
+ ]);
48
+ });
49
+ });
50
+
51
+ describe("EvervaultInput", () => {
52
+ const methodMocks = {
53
+ setValue: vi.fn(),
54
+ setError: vi.fn(),
55
+ };
56
+
57
+ interface FormProps {
58
+ resolver?: Resolver<any>;
59
+ }
60
+
61
+ function Form({ children, resolver }: PropsWithChildren<FormProps>) {
62
+ const methods = useForm({
63
+ resolver,
64
+ });
65
+ const setValue = (...args: Parameters<typeof methods.setValue>) => {
66
+ methodMocks.setValue(...args);
67
+ methods.setValue(...args);
68
+ };
69
+ const setError = (...args: Parameters<typeof methods.setError>) => {
70
+ methodMocks.setError(...args);
71
+ methods.setError(...args);
72
+ };
73
+ return (
74
+ <FormProvider {...methods} setValue={setValue} setError={setError}>
75
+ {children}
76
+ </FormProvider>
77
+ );
78
+ }
79
+
80
+ function createForm(options: FormProps) {
81
+ return function (props: PropsWithChildren<FormProps>) {
82
+ return <Form {...options} {...props} />;
83
+ };
84
+ }
85
+
86
+ it("should render", async () => {
87
+ render(<EvervaultInput testID="phone" name="phone" />, {
88
+ wrapper: Form,
89
+ });
90
+
91
+ const input = screen.getByTestId("phone");
92
+ expect(input).toBeOnTheScreen();
93
+ expect(input).toHaveProp("id", "phone");
94
+ expect(input).toHaveProp("value", "");
95
+ expect(input).toHaveProp("editable", true);
96
+ });
97
+
98
+ it("uses the mask if provided", async () => {
99
+ render(
100
+ <EvervaultInput
101
+ testID="phone"
102
+ name="phone"
103
+ mask={mask("999-999-9999")}
104
+ />,
105
+ {
106
+ wrapper: Form,
107
+ }
108
+ );
109
+ const input = screen.getByTestId("phone");
110
+ const user = userEvent.setup();
111
+
112
+ expect(input).toHaveProp("value", "");
113
+ await user.type(input, "1234567890");
114
+ expect(input).toHaveProp("value", "123-456-7890");
115
+
116
+ expect(methodMocks.setValue).toHaveBeenCalledWith("phone", "1234567890", {
117
+ shouldDirty: true,
118
+ shouldValidate: false,
119
+ });
120
+ });
121
+
122
+ it("dirties the field when the user types", async () => {
123
+ render(<EvervaultInput testID="phone" name="phone" />, {
124
+ wrapper: Form,
125
+ });
126
+
127
+ const input = screen.getByTestId("phone");
128
+ const user = userEvent.setup();
129
+
130
+ await user.type(input, "1234567890");
131
+ expect(methodMocks.setValue).toHaveBeenCalledWith("phone", "1234567890", {
132
+ shouldDirty: true,
133
+ shouldValidate: false,
134
+ });
135
+ });
136
+
137
+ it("dirties and validates the field when the user types if touched", async () => {
138
+ render(<EvervaultInput testID="phone" name="phone" />, {
139
+ wrapper: Form,
140
+ });
141
+
142
+ const input = screen.getByTestId("phone");
143
+
144
+ // Blur the input to trigger touch
145
+ fireEvent(input, "blur");
146
+ expect(methodMocks.setValue).toHaveBeenCalledWith("phone", undefined, {
147
+ shouldDirty: true,
148
+ shouldTouch: true,
149
+ shouldValidate: true,
150
+ });
151
+
152
+ const user = userEvent.setup();
153
+ await user.type(input, "1234567890");
154
+ expect(input).toHaveProp("value", "1234567890");
155
+ expect(methodMocks.setValue).toHaveBeenCalledWith("phone", "1234567890", {
156
+ shouldDirty: true,
157
+ shouldValidate: true,
158
+ });
159
+ });
160
+
161
+ it("dirties, touches, and validates the field when blurred", async () => {
162
+ render(<EvervaultInput testID="phone" name="phone" />, {
163
+ wrapper: Form,
164
+ });
165
+
166
+ const input = screen.getByTestId("phone");
167
+
168
+ fireEvent(input, "blur");
169
+ expect(methodMocks.setValue).toHaveBeenCalledWith("phone", undefined, {
170
+ shouldDirty: true,
171
+ shouldTouch: true,
172
+ shouldValidate: true,
173
+ });
174
+ });
175
+
176
+ it("only validates the field when blurred if validationMode=onBlur", async () => {
177
+ render(
178
+ <EvervaultInputContext.Provider value={{ validationMode: "onBlur" }}>
179
+ <EvervaultInput testID="phone" name="phone" />
180
+ </EvervaultInputContext.Provider>,
181
+ {
182
+ wrapper: Form,
183
+ }
184
+ );
185
+
186
+ const input = screen.getByTestId("phone");
187
+
188
+ fireEvent(input, "blur");
189
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith("phone", undefined, {
190
+ shouldDirty: true,
191
+ shouldTouch: true,
192
+ shouldValidate: true,
193
+ });
194
+
195
+ const user = userEvent.setup();
196
+ await user.type(input, "1234567890", { skipBlur: true });
197
+ expect(input).toHaveProp("value", "1234567890");
198
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith(
199
+ "phone",
200
+ "1234567890",
201
+ {
202
+ shouldDirty: true,
203
+ shouldValidate: false,
204
+ }
205
+ );
206
+ });
207
+
208
+ it("only validates the field after first touch if validationMode=onTouched", async () => {
209
+ render(
210
+ <EvervaultInputContext.Provider value={{ validationMode: "onTouched" }}>
211
+ <EvervaultInput testID="phone" name="phone" />
212
+ </EvervaultInputContext.Provider>,
213
+ {
214
+ wrapper: Form,
215
+ }
216
+ );
217
+
218
+ const input = screen.getByTestId("phone");
219
+
220
+ const user = userEvent.setup();
221
+ await user.type(input, "1234", { skipBlur: true });
222
+ expect(input).toHaveProp("value", "1234");
223
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith("phone", "1234", {
224
+ shouldDirty: true,
225
+ shouldValidate: false,
226
+ });
227
+
228
+ fireEvent(input, "blur");
229
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith("phone", "1234", {
230
+ shouldDirty: true,
231
+ shouldTouch: true,
232
+ shouldValidate: true,
233
+ });
234
+
235
+ await user.type(input, "567890", { skipBlur: true });
236
+ expect(input).toHaveProp("value", "1234567890");
237
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith(
238
+ "phone",
239
+ "1234567890",
240
+ { shouldDirty: true, shouldValidate: true }
241
+ );
242
+ });
243
+
244
+ it("only validates the field when changed if validationMode=onChange and the field is touched", async () => {
245
+ render(
246
+ <EvervaultInputContext.Provider value={{ validationMode: "onChange" }}>
247
+ <EvervaultInput testID="phone" name="phone" />
248
+ </EvervaultInputContext.Provider>,
249
+ {
250
+ wrapper: Form,
251
+ }
252
+ );
253
+
254
+ const input = screen.getByTestId("phone");
255
+
256
+ const user = userEvent.setup();
257
+ await user.type(input, "1234567890", { skipBlur: true });
258
+ expect(input).toHaveProp("value", "1234567890");
259
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith(
260
+ "phone",
261
+ "1234567890",
262
+ { shouldDirty: true, shouldValidate: false }
263
+ );
264
+
265
+ fireEvent(input, "blur");
266
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith(
267
+ "phone",
268
+ "1234567890",
269
+ { shouldDirty: true, shouldTouch: true, shouldValidate: false }
270
+ );
271
+
272
+ await user.type(input, "1", { skipBlur: true });
273
+ expect(input).toHaveProp("value", "12345678901");
274
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith(
275
+ "phone",
276
+ "12345678901",
277
+ { shouldDirty: true, shouldValidate: true }
278
+ );
279
+ });
280
+
281
+ it("only validates the field when changed if validationMode=onChange and the field has errors", async () => {
282
+ render(
283
+ <EvervaultInputContext.Provider value={{ validationMode: "onChange" }}>
284
+ <EvervaultInput testID="phone" name="phone" />
285
+ </EvervaultInputContext.Provider>,
286
+ {
287
+ wrapper: createForm({
288
+ resolver: zodResolver(z.object({ phone: z.string().min(10) })),
289
+ }),
290
+ }
291
+ );
292
+
293
+ const input = screen.getByTestId("phone");
294
+
295
+ const user = userEvent.setup();
296
+ await user.type(input, "1234", { skipBlur: true });
297
+ expect(input).toHaveProp("value", "1234");
298
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith("phone", "1234", {
299
+ shouldDirty: true,
300
+ shouldValidate: false,
301
+ });
302
+
303
+ fireEvent(input, "blur");
304
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith("phone", "1234", {
305
+ shouldDirty: true,
306
+ shouldTouch: true,
307
+ shouldValidate: false,
308
+ });
309
+
310
+ await user.type(input, "5", { skipBlur: true });
311
+ expect(input).toHaveProp("value", "12345");
312
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith("phone", "12345", {
313
+ shouldDirty: true,
314
+ shouldValidate: true,
315
+ });
316
+ });
317
+
318
+ it("validates on blur, touch, and change if validationMode=all", async () => {
319
+ render(
320
+ <EvervaultInputContext.Provider value={{ validationMode: "all" }}>
321
+ <EvervaultInput testID="phone" name="phone" />
322
+ </EvervaultInputContext.Provider>,
323
+ {
324
+ wrapper: createForm({
325
+ resolver: zodResolver(z.object({ phone: z.string().min(10) })),
326
+ }),
327
+ }
328
+ );
329
+
330
+ const input = screen.getByTestId("phone");
331
+
332
+ const user = userEvent.setup();
333
+ await user.type(input, "1234", { skipBlur: true });
334
+ expect(input).toHaveProp("value", "1234");
335
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith("phone", "1234", {
336
+ shouldDirty: true,
337
+ shouldValidate: false,
338
+ });
339
+
340
+ fireEvent(input, "blur");
341
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith("phone", "1234", {
342
+ shouldDirty: true,
343
+ shouldTouch: true,
344
+ shouldValidate: true,
345
+ });
346
+
347
+ await user.type(input, "567890", { skipBlur: true });
348
+ expect(input).toHaveProp("value", "1234567890");
349
+ expect(methodMocks.setValue).toHaveBeenLastCalledWith(
350
+ "phone",
351
+ "1234567890",
352
+ { shouldDirty: true, shouldValidate: true }
353
+ );
354
+ });
355
+
356
+ it("should obfuscate the value when obfuscateValue=true", async () => {
357
+ const phoneMask = mask("[(999) 999]-9999");
358
+ const { rerender } = render(
359
+ <EvervaultInput testID="phone" mask={phoneMask} name="phone" />,
360
+ {
361
+ wrapper: Form,
362
+ }
363
+ );
364
+
365
+ const input = screen.getByTestId("phone");
366
+ const user = userEvent.setup();
367
+
368
+ await user.type(input, "1234567890");
369
+ expect(input).toHaveProp("value", "(123) 456-7890");
370
+
371
+ rerender(
372
+ <EvervaultInput
373
+ testID="phone"
374
+ mask={phoneMask}
375
+ name="phone"
376
+ obfuscateValue
377
+ />
378
+ );
379
+
380
+ await user.type(input, "1234567890");
381
+ expect(input).toHaveProp("value", "(•••) •••-7890");
382
+
383
+ rerender(
384
+ <EvervaultInput
385
+ testID="phone"
386
+ mask={phoneMask}
387
+ name="phone"
388
+ obfuscateValue="#"
389
+ />
390
+ );
391
+
392
+ await user.type(input, "1234567890");
393
+ expect(input).toHaveProp("value", "(###) ###-7890");
394
+
395
+ rerender(
396
+ <EvervaultInput
397
+ testID="phone"
398
+ mask={phoneMask}
399
+ name="phone"
400
+ obfuscateValue="🤔"
401
+ />
402
+ );
403
+
404
+ await user.type(input, "1234567890");
405
+ expect(input).toHaveProp("value", "(🤔🤔🤔) 🤔🤔🤔-7890");
406
+
407
+ const unobfuscatedMask = mask("(999) 999-9999");
408
+ rerender(
409
+ <EvervaultInput
410
+ testID="phone"
411
+ mask={unobfuscatedMask}
412
+ name="phone"
413
+ obfuscateValue
414
+ />
415
+ );
416
+
417
+ await user.type(input, "1234567890");
418
+ expect(input).toHaveProp("value", "(123) 456-7890");
419
+ });
420
+ });
package/src/Input.tsx ADDED
@@ -0,0 +1,182 @@
1
+ import {
2
+ createContext,
3
+ ForwardedRef,
4
+ forwardRef,
5
+ ReactNode,
6
+ Ref,
7
+ RefObject,
8
+ useCallback,
9
+ useContext,
10
+ useImperativeHandle,
11
+ useMemo,
12
+ useRef,
13
+ useState,
14
+ } from "react";
15
+ import { TextInput, TextInputProps } from "react-native";
16
+ import { mergeRefs } from "./utils";
17
+ import { Controller, useController, useFormContext } from "react-hook-form";
18
+ import MaskInput, { Mask, MaskArray } from "react-native-mask-input";
19
+
20
+ export interface EvervaultInputContextValue {
21
+ validationMode: "onChange" | "onBlur" | "onTouched" | "all";
22
+ }
23
+
24
+ export const EvervaultInputContext = createContext<EvervaultInputContextValue>({
25
+ validationMode: "all",
26
+ });
27
+
28
+ export type EvervaultInput = Pick<
29
+ TextInput,
30
+ | "isFocused"
31
+ | "focus"
32
+ | "blur"
33
+ | "clear"
34
+ | "measure"
35
+ | "measureInWindow"
36
+ | "measureLayout"
37
+ >;
38
+
39
+ function useForwardedInputRef(
40
+ ref: ForwardedRef<EvervaultInput>
41
+ ): RefObject<TextInput> {
42
+ const inputRef = useRef<TextInput>(null);
43
+
44
+ useImperativeHandle<EvervaultInput, EvervaultInput>(
45
+ ref,
46
+ useCallback(
47
+ () => ({
48
+ isFocused() {
49
+ return inputRef.current?.isFocused() ?? false;
50
+ },
51
+ focus() {
52
+ inputRef.current?.focus();
53
+ },
54
+ blur() {
55
+ inputRef.current?.blur();
56
+ },
57
+ clear() {
58
+ inputRef.current?.clear();
59
+ },
60
+ measure(callback) {
61
+ inputRef.current?.measure(callback);
62
+ },
63
+ measureInWindow(callback) {
64
+ inputRef.current?.measureInWindow(callback);
65
+ },
66
+ measureLayout(relativeToNativeComponentRef, onSuccess, onFail) {
67
+ inputRef.current?.measureLayout(
68
+ relativeToNativeComponentRef,
69
+ onSuccess,
70
+ onFail
71
+ );
72
+ },
73
+ }),
74
+ [inputRef]
75
+ )
76
+ );
77
+
78
+ return inputRef;
79
+ }
80
+
81
+ export type BaseEvervaultInputProps = Omit<
82
+ TextInputProps,
83
+ "onChange" | "onChangeText" | "value" | "defaultValue"
84
+ >;
85
+
86
+ export function mask(format: string): MaskArray {
87
+ const maskArray: MaskArray = [];
88
+
89
+ let isObfuscated = false;
90
+ format.split("").forEach((char) => {
91
+ if (char === "[") {
92
+ isObfuscated = true;
93
+ return;
94
+ } else if (char === "]") {
95
+ isObfuscated = false;
96
+ return;
97
+ }
98
+
99
+ let value: string | RegExp | [RegExp] = char;
100
+ if (char === "9") {
101
+ value = isObfuscated ? [/\d/] : /\d/;
102
+ }
103
+ maskArray.push(value);
104
+ });
105
+
106
+ return maskArray;
107
+ }
108
+
109
+ export interface EvervaultInputProps<Values extends Record<string, unknown>>
110
+ extends BaseEvervaultInputProps {
111
+ name: keyof Values;
112
+ mask?: Mask;
113
+ obfuscateValue?: boolean | string;
114
+ }
115
+
116
+ export const EvervaultInput = forwardRef<
117
+ EvervaultInput,
118
+ EvervaultInputProps<Record<string, unknown>>
119
+ >(function EvervaultInput({ name, mask, obfuscateValue, ...props }, ref) {
120
+ const { validationMode } = useContext(EvervaultInputContext);
121
+
122
+ const inputRef = useForwardedInputRef(ref);
123
+
124
+ const methods = useFormContext();
125
+
126
+ const { field, fieldState } = useController({
127
+ control: methods.control,
128
+ name,
129
+ shouldUnregister: true,
130
+ });
131
+
132
+ const obfuscationCharacter = useMemo(() => {
133
+ if (typeof obfuscateValue === "string") {
134
+ return obfuscateValue;
135
+ } else {
136
+ return "•";
137
+ }
138
+ }, [obfuscateValue]);
139
+
140
+ return (
141
+ <MaskInput
142
+ // Overridable props
143
+ id={field.name}
144
+ {...props}
145
+ // Strict props
146
+ ref={mergeRefs(inputRef, field.ref)}
147
+ editable={!field.disabled && (props.editable ?? true)}
148
+ onBlur={(evt) => {
149
+ const shouldValidate =
150
+ validationMode === "onBlur" ||
151
+ validationMode === "onTouched" ||
152
+ validationMode === "all";
153
+ methods.setValue(field.name, field.value, {
154
+ shouldDirty: true,
155
+ shouldTouch: true,
156
+ shouldValidate,
157
+ });
158
+ props.onBlur?.(evt);
159
+ }}
160
+ mask={mask}
161
+ maskAutoComplete={!!mask}
162
+ obfuscationCharacter={obfuscationCharacter}
163
+ showObfuscatedValue={!!obfuscateValue}
164
+ value={field.value}
165
+ onChangeText={(masked, unmasked) => {
166
+ const shouldValidate =
167
+ (validationMode === "onTouched" && fieldState.isTouched) ||
168
+ ((validationMode === "onChange" || validationMode === "all") &&
169
+ (!!fieldState.error || fieldState.isTouched));
170
+ methods.setValue(field.name, unmasked, {
171
+ shouldDirty: true,
172
+ shouldValidate,
173
+ });
174
+ }}
175
+ // Remove unwanted props
176
+ defaultValue={undefined}
177
+ onChange={undefined}
178
+ />
179
+ );
180
+ }) as <Values extends Record<string, unknown>>(
181
+ props: EvervaultInputProps<Values> & { ref?: Ref<EvervaultInput> }
182
+ ) => ReactNode;