@jobber/components-native 0.95.3 → 0.95.4-improve-co-ca924fd.14

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 (37) hide show
  1. package/dist/package.json +3 -5
  2. package/dist/src/ContentOverlay/ContentOverlay.js +128 -107
  3. package/dist/src/ContentOverlay/ContentOverlay.style.js +8 -12
  4. package/dist/src/ContentOverlay/ContentOverlayProvider.js +5 -0
  5. package/dist/src/ContentOverlay/computeContentOverlayBehavior.js +76 -0
  6. package/dist/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.js +25 -0
  7. package/dist/src/ContentOverlay/index.js +1 -0
  8. package/dist/src/InputText/InputText.js +35 -1
  9. package/dist/src/utils/meta/meta.json +1 -0
  10. package/dist/tsconfig.build.tsbuildinfo +1 -1
  11. package/dist/types/src/ContentOverlay/ContentOverlay.d.ts +1 -5
  12. package/dist/types/src/ContentOverlay/ContentOverlay.style.d.ts +11 -10
  13. package/dist/types/src/ContentOverlay/ContentOverlayProvider.d.ts +6 -0
  14. package/dist/types/src/ContentOverlay/computeContentOverlayBehavior.d.ts +32 -0
  15. package/dist/types/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.d.ts +7 -0
  16. package/dist/types/src/ContentOverlay/index.d.ts +1 -0
  17. package/dist/types/src/ContentOverlay/types.d.ts +5 -12
  18. package/jestSetup.js +2 -0
  19. package/package.json +3 -5
  20. package/src/ContentOverlay/ContentOverlay.stories.tsx +59 -0
  21. package/src/ContentOverlay/ContentOverlay.style.ts +12 -12
  22. package/src/ContentOverlay/ContentOverlay.test.tsx +157 -79
  23. package/src/ContentOverlay/ContentOverlay.tsx +223 -210
  24. package/src/ContentOverlay/ContentOverlayProvider.tsx +12 -0
  25. package/src/ContentOverlay/computeContentOverlayBehavior.test.ts +276 -0
  26. package/src/ContentOverlay/computeContentOverlayBehavior.ts +119 -0
  27. package/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.test.ts +81 -0
  28. package/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.ts +36 -0
  29. package/src/ContentOverlay/index.ts +1 -0
  30. package/src/ContentOverlay/types.ts +5 -13
  31. package/src/InputText/InputText.test.tsx +122 -0
  32. package/src/InputText/InputText.tsx +52 -2
  33. package/src/ThumbnailList/__snapshots__/ThumbnailList.test.tsx.snap +0 -20
  34. package/src/utils/meta/meta.json +1 -0
  35. package/dist/src/ContentOverlay/UNSAFE_WrappedModalize.js +0 -23
  36. package/dist/types/src/ContentOverlay/UNSAFE_WrappedModalize.d.ts +0 -3
  37. package/src/ContentOverlay/UNSAFE_WrappedModalize.tsx +0 -41
@@ -14,7 +14,8 @@ import type {
14
14
  TextInputProps,
15
15
  TextStyle,
16
16
  } from "react-native";
17
- import { Platform, TextInput } from "react-native";
17
+ import { Platform, TextInput, findNodeHandle } from "react-native";
18
+ import { useBottomSheetInternal } from "@gorhom/bottom-sheet";
18
19
  import type { RegisterOptions } from "react-hook-form";
19
20
  import type { IconNames } from "@jobber/design";
20
21
  import identity from "lodash/identity";
@@ -315,6 +316,11 @@ function InputTextInternal(
315
316
  disabled,
316
317
  });
317
318
 
319
+ // Bottom sheet keyboard handling - detect if we're inside a ContentOverlay
320
+ const bottomSheetContext = useBottomSheetInternal(true);
321
+ const animatedKeyboardState = bottomSheetContext?.animatedKeyboardState;
322
+ const textInputNodesRef = bottomSheetContext?.textInputNodesRef;
323
+
318
324
  // Android doesn't have an accessibility label like iOS does. By adding
319
325
  // it as a placeholder it readds it like a label. However we don't want to
320
326
  // add a placeholder on iOS.
@@ -438,11 +444,13 @@ function InputTextInternal(
438
444
  secureTextEntry={secureTextEntry}
439
445
  {...androidA11yProps}
440
446
  onFocus={event => {
447
+ handleBottomSheetFocus(event);
441
448
  _name && setFocusedInput(_name);
442
449
  setFocused(true);
443
450
  onFocus?.(event);
444
451
  }}
445
452
  onBlur={event => {
453
+ handleBottomSheetBlur(event);
446
454
  _name && setFocusedInput("");
447
455
  setFocused(false);
448
456
  onBlur?.(event);
@@ -469,6 +477,48 @@ function InputTextInternal(
469
477
  updateFormAndState(removedIOSCharValue);
470
478
  }
471
479
 
480
+ function handleBottomSheetFocus(event?: FocusEvent) {
481
+ if (!animatedKeyboardState || !textInputNodesRef || !event?.nativeEvent) {
482
+ return;
483
+ }
484
+
485
+ animatedKeyboardState.set(state => ({
486
+ ...state,
487
+ target: event.nativeEvent.target,
488
+ }));
489
+ }
490
+
491
+ function handleBottomSheetBlur(event?: FocusEvent) {
492
+ if (!animatedKeyboardState || !textInputNodesRef || !event?.nativeEvent) {
493
+ return;
494
+ }
495
+ const keyboardState = animatedKeyboardState.get();
496
+ const currentlyFocusedInput = TextInput.State.currentlyFocusedInput();
497
+ const currentFocusedInput =
498
+ currentlyFocusedInput !== null
499
+ ? findNodeHandle(
500
+ // @ts-expect-error - TextInput.State.currentlyFocusedInput() returns NativeMethods
501
+ // which is not directly assignable to findNodeHandle's expected type,
502
+ // but it works at runtime. This is a known type limitation in React Native.
503
+ currentlyFocusedInput,
504
+ )
505
+ : null;
506
+
507
+ // Only remove the target if it belongs to the current component
508
+ // and if the currently focused input is not in the targets set
509
+ const shouldRemoveCurrentTarget =
510
+ keyboardState.target === event.nativeEvent.target;
511
+ const shouldIgnoreBlurEvent =
512
+ currentFocusedInput && textInputNodesRef.current.has(currentFocusedInput);
513
+
514
+ if (shouldRemoveCurrentTarget && !shouldIgnoreBlurEvent) {
515
+ animatedKeyboardState.set(state => ({
516
+ ...state,
517
+ target: undefined,
518
+ }));
519
+ }
520
+ }
521
+
472
522
  function handleClear() {
473
523
  handleChangeText("");
474
524
  }
@@ -515,7 +565,7 @@ interface UseTextInputRefProps {
515
565
  }
516
566
 
517
567
  function useTextInputRef({ ref, onClear }: UseTextInputRefProps) {
518
- const textInputRef = useRef<InputTextRef | null>(null);
568
+ const textInputRef = useRef<TextInput | null>(null);
519
569
 
520
570
  useImperativeHandle(
521
571
  ref,
@@ -152,20 +152,6 @@ exports[`renders a thumbnail component with attachments 1`] = `
152
152
  }
153
153
  >
154
154
  <View
155
- backdropComponent={[Function]}
156
- backgroundStyle={
157
- {
158
- "borderTopLeftRadius": 24,
159
- "borderTopRightRadius": 24,
160
- }
161
- }
162
- enablePanDownToClose={true}
163
- handleStyle={
164
- {
165
- "display": "none",
166
- }
167
- }
168
- keyboardBlurBehavior="restore"
169
155
  style={
170
156
  {
171
157
  "display": "none",
@@ -174,12 +160,6 @@ exports[`renders a thumbnail component with attachments 1`] = `
174
160
  testID="bottom-sheet-mock"
175
161
  >
176
162
  <View
177
- style={
178
- {
179
- "paddingBottom": 8,
180
- "paddingTop": 8,
181
- }
182
- }
183
163
  testID="bottom-sheet-view"
184
164
  >
185
165
  <View
@@ -23,6 +23,7 @@
23
23
  "Chip",
24
24
  "Content",
25
25
  "ContentOverlay",
26
+ "ContentOverlayProvider",
26
27
  "Disclosure",
27
28
  "Divider",
28
29
  "EmptyState",
@@ -1,23 +0,0 @@
1
- import React, { forwardRef, useImperativeHandle, useRef, useState, } from "react";
2
- import { Modalize } from "react-native-modalize";
3
- export const UNSAFE_WrappedModalize = forwardRef((props, ref) => {
4
- const innerRef = useRef(null);
5
- const [openRenderId, setOpenRenderId] = useState(0);
6
- useImperativeHandle(ref, () => ({
7
- open(dest) {
8
- setOpenRenderId(id => id + 1);
9
- // Open on a fresh tick for additional safety
10
- requestAnimationFrame(() => {
11
- var _a;
12
- (_a = innerRef.current) === null || _a === void 0 ? void 0 : _a.open(dest);
13
- });
14
- },
15
- close(dest) {
16
- var _a;
17
- (_a = innerRef.current) === null || _a === void 0 ? void 0 : _a.close(dest);
18
- },
19
- }), []);
20
- // Use a unique key to force a remount, ensuring we get fresh gesture handler nodes within modalize
21
- return (React.createElement(Modalize, Object.assign({ key: `modalize-${openRenderId}`, ref: innerRef }, props)));
22
- });
23
- UNSAFE_WrappedModalize.displayName = "UNSAFE_WrappedModalize";
@@ -1,3 +0,0 @@
1
- import React from "react";
2
- import type { IHandles } from "react-native-modalize/lib/options";
3
- export declare const UNSAFE_WrappedModalize: React.ForwardRefExoticComponent<Omit<import("react-native-modalize/lib/options").IProps<any> & React.RefAttributes<any>, "ref"> & React.RefAttributes<IHandles | undefined>>;
@@ -1,41 +0,0 @@
1
- import React, {
2
- forwardRef,
3
- useImperativeHandle,
4
- useRef,
5
- useState,
6
- } from "react";
7
- import { Modalize } from "react-native-modalize";
8
- import type { IHandles } from "react-native-modalize/lib/options";
9
-
10
- type Props = React.ComponentProps<typeof Modalize>;
11
-
12
- export const UNSAFE_WrappedModalize = forwardRef<IHandles | undefined, Props>(
13
- (props, ref) => {
14
- const innerRef = useRef<IHandles | null>(null);
15
- const [openRenderId, setOpenRenderId] = useState(0);
16
-
17
- useImperativeHandle(
18
- ref,
19
- () => ({
20
- open(dest) {
21
- setOpenRenderId(id => id + 1);
22
- // Open on a fresh tick for additional safety
23
- requestAnimationFrame(() => {
24
- innerRef.current?.open(dest);
25
- });
26
- },
27
- close(dest) {
28
- innerRef.current?.close(dest);
29
- },
30
- }),
31
- [],
32
- );
33
-
34
- // Use a unique key to force a remount, ensuring we get fresh gesture handler nodes within modalize
35
- return (
36
- <Modalize key={`modalize-${openRenderId}`} ref={innerRef} {...props} />
37
- );
38
- },
39
- );
40
-
41
- UNSAFE_WrappedModalize.displayName = "UNSAFE_WrappedModalize";