@jobber/components-native 0.99.0 → 0.100.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 (37) hide show
  1. package/dist/package.json +3 -6
  2. package/dist/src/ContentOverlay/BottomSheetKeyboardAwareScrollView.js +19 -0
  3. package/dist/src/ContentOverlay/ContentOverlay.js +143 -107
  4. package/dist/src/ContentOverlay/ContentOverlay.style.js +8 -12
  5. package/dist/src/ContentOverlay/computeContentOverlayBehavior.js +76 -0
  6. package/dist/src/ContentOverlay/constants.js +1 -0
  7. package/dist/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.js +25 -0
  8. package/dist/src/ContentOverlay/index.js +1 -1
  9. package/dist/src/InputText/InputText.js +44 -1
  10. package/dist/tsconfig.build.tsbuildinfo +1 -1
  11. package/dist/types/src/ContentOverlay/BottomSheetKeyboardAwareScrollView.d.ts +11 -0
  12. package/dist/types/src/ContentOverlay/ContentOverlay.d.ts +2 -5
  13. package/dist/types/src/ContentOverlay/ContentOverlay.style.d.ts +11 -10
  14. package/dist/types/src/ContentOverlay/computeContentOverlayBehavior.d.ts +32 -0
  15. package/dist/types/src/ContentOverlay/constants.d.ts +1 -0
  16. package/dist/types/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.d.ts +7 -0
  17. package/dist/types/src/ContentOverlay/index.d.ts +1 -1
  18. package/dist/types/src/ContentOverlay/types.d.ts +5 -12
  19. package/jestSetup.js +2 -0
  20. package/package.json +3 -6
  21. package/src/ContentOverlay/BottomSheetKeyboardAwareScrollView.tsx +36 -0
  22. package/src/ContentOverlay/ContentOverlay.stories.tsx +32 -36
  23. package/src/ContentOverlay/ContentOverlay.style.ts +12 -12
  24. package/src/ContentOverlay/ContentOverlay.test.tsx +157 -79
  25. package/src/ContentOverlay/ContentOverlay.tsx +247 -205
  26. package/src/ContentOverlay/computeContentOverlayBehavior.test.ts +276 -0
  27. package/src/ContentOverlay/computeContentOverlayBehavior.ts +119 -0
  28. package/src/ContentOverlay/constants.ts +1 -0
  29. package/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.test.ts +81 -0
  30. package/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.ts +36 -0
  31. package/src/ContentOverlay/index.ts +4 -1
  32. package/src/ContentOverlay/types.ts +5 -13
  33. package/src/InputText/InputText.test.tsx +122 -0
  34. package/src/InputText/InputText.tsx +62 -2
  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
@@ -0,0 +1,11 @@
1
+ import type { KeyboardAwareScrollViewProps } from "react-native-keyboard-controller";
2
+ import { type BottomSheetScrollViewMethods } from "@gorhom/bottom-sheet";
3
+ declare const BottomSheetKeyboardAwareScrollView: import("react").NamedExoticComponent<{
4
+ bottomOffset?: number;
5
+ disableScrollOnKeyboardHide?: boolean;
6
+ enabled?: boolean;
7
+ extraKeyboardSpace?: number;
8
+ ScrollViewComponent?: React.ComponentType<import("react-native").ScrollViewProps>;
9
+ } & import("react-native").ScrollViewProps & import("react").RefAttributes<BottomSheetScrollViewMethods>>;
10
+ export { BottomSheetKeyboardAwareScrollView };
11
+ export type { KeyboardAwareScrollViewProps };
@@ -1,7 +1,4 @@
1
1
  import React from "react";
2
- import type { Modalize } from "react-native-modalize";
3
2
  import type { ContentOverlayProps } from "./types";
4
- export declare const ContentOverlay: React.ForwardRefExoticComponent<ContentOverlayProps & React.RefAttributes<{
5
- open?: Modalize["open"];
6
- close?: Modalize["close"];
7
- } | undefined>>;
3
+ export declare function useIsKeyboardHandledByScrollView(): boolean;
4
+ export declare function ContentOverlay({ children, title, accessibilityLabel, fullScreen, showDismiss, isDraggable, adjustToContentHeight, keyboardShouldPersistTaps, scrollEnabled, modalBackgroundColor, onClose, onOpen, onBeforeExit, loading, ref, }: ContentOverlayProps): React.JSX.Element;
@@ -1,32 +1,33 @@
1
1
  export declare const useStyles: () => {
2
+ handleWrapper: {
3
+ paddingBottom: number;
4
+ paddingTop: number;
5
+ };
2
6
  handle: {
3
7
  width: number;
4
8
  height: number;
5
9
  backgroundColor: string;
6
- top: number;
7
10
  borderRadius: number;
8
11
  };
9
- overlay: {
12
+ backdrop: {
10
13
  backgroundColor: string;
11
14
  };
12
- modal: {
15
+ background: {
13
16
  borderTopLeftRadius: number;
14
17
  borderTopRightRadius: number;
15
18
  };
16
- modalForLargeScreens: {
17
- width: number;
18
- alignSelf: "center";
19
- };
20
19
  header: {
21
20
  flexDirection: "row";
22
- backgroundColor: string;
23
- paddingTop: number;
24
21
  zIndex: number;
22
+ minHeight: number;
25
23
  borderTopLeftRadius: number;
26
24
  borderTopRightRadius: number;
27
- minHeight: number;
28
25
  };
29
26
  headerShadow: {
27
+ position: "absolute";
28
+ top: number;
29
+ height: number;
30
+ width: "100%";
30
31
  shadowColor: string;
31
32
  shadowOffset: {
32
33
  width: number;
@@ -0,0 +1,32 @@
1
+ export interface ContentOverlayConfig {
2
+ fullScreen: boolean;
3
+ adjustToContentHeight: boolean;
4
+ isDraggable: boolean;
5
+ hasOnBeforeExit: boolean;
6
+ showDismiss: boolean;
7
+ }
8
+ export interface ContentOverlayState {
9
+ isScreenReaderEnabled: boolean;
10
+ position: number;
11
+ }
12
+ export type InitialHeight = "fullScreen" | "contentHeight";
13
+ export interface ContentOverlayBehavior {
14
+ initialHeight: InitialHeight;
15
+ isDraggable: boolean;
16
+ showDismiss: boolean;
17
+ }
18
+ /**
19
+ * Computes the abstract behavior of ContentOverlay from its props and state.
20
+ *
21
+ * This pure function documents and centralizes the complex logic that determines:
22
+ * - Initial height mode (fullScreen vs contentHeight)
23
+ * - Whether the overlay is draggable
24
+ * - Whether the dismiss button should be shown
25
+ *
26
+ * The logic accounts for legacy behavior where:
27
+ * - `onBeforeExit` silently overrides `isDraggable` to false
28
+ * - Default props (neither fullScreen nor adjustToContentHeight) are treated
29
+ * as contentHeight for the new implementation
30
+ * - Dismiss button visibility depends on multiple factors including position state
31
+ */
32
+ export declare function computeContentOverlayBehavior(config: ContentOverlayConfig, state: ContentOverlayState): ContentOverlayBehavior;
@@ -0,0 +1 @@
1
+ export declare const KEYBOARD_TOP_PADDING_AUTO_SCROLL = 20;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Hook that dismisses the bottom sheet on the hardware back button press if it is visible
3
+ * @param bottomSheetModalRef ref to the bottom sheet modal component
4
+ */
5
+ export declare function useBottomSheetModalBackHandler(onCloseController: () => void): {
6
+ handleSheetPositionChange: (index: number) => void;
7
+ };
@@ -1,2 +1,2 @@
1
- export { ContentOverlay } from "./ContentOverlay";
1
+ export { ContentOverlay, useIsKeyboardHandledByScrollView, } from "./ContentOverlay";
2
2
  export type { ContentOverlayRef, ModalBackgroundColor } from "./types";
@@ -1,5 +1,4 @@
1
- import type { ReactNode } from "react";
2
- import type { Modalize } from "react-native-modalize";
1
+ import type { ReactNode, Ref } from "react";
3
2
  export interface ContentOverlayProps {
4
3
  /**
5
4
  * Content to be passed into the overlay
@@ -64,24 +63,18 @@ export interface ContentOverlayProps {
64
63
  * Callback that is called between overlay is closed and when the "x" button is pressed
65
64
  */
66
65
  readonly onBeforeExit?: () => void;
67
- /**
68
- * Define the behavior of the keyboard when having inputs inside the modal.
69
- * @default padding
70
- */
71
- readonly keyboardAvoidingBehavior?: "height" | "padding" | "position";
72
66
  /**
73
67
  * Boolean to show a disabled state
74
68
  * @default false
75
69
  */
76
70
  readonly loading?: boolean;
77
71
  /**
78
- * Define keyboard's Android behavior like iOS's one.
79
- * @default Platform.select({ ios: true, android: false })
72
+ * Ref to the content overlay component.
80
73
  */
81
- readonly avoidKeyboardLikeIOS?: boolean;
74
+ readonly ref?: Ref<ContentOverlayRef>;
82
75
  }
83
76
  export type ModalBackgroundColor = "surface" | "background";
84
77
  export type ContentOverlayRef = {
85
- open?: Modalize["open"];
86
- close?: Modalize["close"];
78
+ open?: () => void;
79
+ close?: () => void;
87
80
  } | undefined;
package/jestSetup.js CHANGED
@@ -16,6 +16,8 @@ jest.mock("./dist/src/Button/components/InternalButtonLoading", () => {
16
16
  };
17
17
  });
18
18
 
19
+ // NOTE: this is the old way we used to mock reanimated. We actually do not need to mock it anymore.
20
+ // To ensure correct test behaviour, please add `jest.unmock("react-native-reanimated")` to your test suite.
19
21
  jest.mock("react-native-reanimated", () => {
20
22
  const reanimated = require("react-native-reanimated/mock");
21
23
  const timing = () => ({ start: () => undefined });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.99.0",
3
+ "version": "0.100.0",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -46,8 +46,6 @@
46
46
  "react-hook-form": "^7.52.0",
47
47
  "react-intl": "^7.1.11",
48
48
  "react-native-keyboard-controller": "^1.20.7",
49
- "react-native-modalize": "^2.0.13",
50
- "react-native-portalize": "^1.0.7",
51
49
  "react-native-toast-message": "^2.1.6",
52
50
  "react-native-uuid": "^1.4.9",
53
51
  "ts-xor": "^1.1.0"
@@ -69,6 +67,7 @@
69
67
  "date-fns-tz": "^2.0.0",
70
68
  "react-native": "^0.82.1",
71
69
  "react-native-gesture-handler": "^2.29.1",
70
+ "react-native-keyboard-controller": "^1.12.0",
72
71
  "react-native-modal-datetime-picker": "^18.0.0",
73
72
  "react-native-reanimated": "^3.7.1",
74
73
  "react-native-safe-area-context": "^5.4.0",
@@ -90,11 +89,9 @@
90
89
  "react-native-gesture-handler": ">=2.22.0",
91
90
  "react-native-keyboard-controller": "^1.20.7",
92
91
  "react-native-modal-datetime-picker": " >=13.0.0",
93
- "react-native-modalize": "^2.0.13",
94
- "react-native-portalize": "^1.0.7",
95
92
  "react-native-reanimated": "^3.0.0",
96
93
  "react-native-safe-area-context": "^5.4.0",
97
94
  "react-native-svg": ">=12.0.0"
98
95
  },
99
- "gitHead": "c92c33f8e0a06cbc2addce9eca3005c1231a9202"
96
+ "gitHead": "2ef76c22620e1627eca8cf576b35e78228262574"
100
97
  }
@@ -0,0 +1,36 @@
1
+ import { memo } from "react";
2
+ import type { KeyboardAwareScrollViewProps } from "react-native-keyboard-controller";
3
+ import { KeyboardAwareScrollView } from "react-native-keyboard-controller";
4
+ import {
5
+ type BottomSheetScrollViewMethods,
6
+ SCROLLABLE_TYPE,
7
+ createBottomSheetScrollableComponent,
8
+ } from "@gorhom/bottom-sheet";
9
+ import Reanimated from "react-native-reanimated";
10
+
11
+ /**
12
+ * A keyboard-aware scroll view component that integrates with @gorhom/bottom-sheet.
13
+ *
14
+ * This component wraps `KeyboardAwareScrollView` from `react-native-keyboard-controller`
15
+ * with the bottom sheet HOCs to ensure proper keyboard handling on Android when using
16
+ * TextInputs inside a BottomSheet.
17
+ *
18
+ * @see https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/components/keyboard-aware-scroll-view#gorhombottom-sheet
19
+ */
20
+ const AnimatedScrollView =
21
+ Reanimated.createAnimatedComponent<KeyboardAwareScrollViewProps>(
22
+ KeyboardAwareScrollView,
23
+ );
24
+
25
+ const BottomSheetScrollViewComponent = createBottomSheetScrollableComponent<
26
+ BottomSheetScrollViewMethods,
27
+ KeyboardAwareScrollViewProps
28
+ >(SCROLLABLE_TYPE.SCROLLVIEW, AnimatedScrollView);
29
+
30
+ const BottomSheetKeyboardAwareScrollView = memo(BottomSheetScrollViewComponent);
31
+
32
+ BottomSheetKeyboardAwareScrollView.displayName =
33
+ "BottomSheetKeyboardAwareScrollView";
34
+
35
+ export { BottomSheetKeyboardAwareScrollView };
36
+ export type { KeyboardAwareScrollViewProps };
@@ -1,23 +1,15 @@
1
1
  import React, { useRef } from "react";
2
2
  import { View } from "react-native";
3
3
  import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
4
- import { Host } from "react-native-portalize";
5
4
  import { SafeAreaProvider } from "react-native-safe-area-context";
6
- import type { ContentOverlayRef } from "@jobber/components-native";
7
- import {
8
- Button,
9
- Content,
10
- ContentOverlay,
11
- Text,
12
- } from "@jobber/components-native";
5
+ import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
6
+ import { Button, Heading, Text } from "@jobber/components-native";
7
+ import { ContentOverlay } from "./ContentOverlay";
8
+ import type { ContentOverlayRef } from "./types";
13
9
 
14
10
  const meta = {
15
11
  title: "Components/Overlays/ContentOverlay",
16
12
  component: ContentOverlay,
17
- parameters: {
18
- viewport: { defaultViewport: "mobile1" },
19
- showNativeOnWebDisclaimer: true,
20
- },
21
13
  } satisfies Meta<typeof ContentOverlay>;
22
14
  export default meta;
23
15
 
@@ -30,36 +22,40 @@ interface ContentOverlayStoryArgs {
30
22
 
31
23
  type Story = StoryObj<ContentOverlayStoryArgs>;
32
24
 
33
- const BasicTemplate = (args: ContentOverlayStoryArgs) => {
25
+ const BasicTemplate = () => {
34
26
  const contentOverlayRef = useRef<ContentOverlayRef>(null);
35
27
 
28
+ const openContentOverlay = () => {
29
+ contentOverlayRef.current?.open?.();
30
+ };
31
+
32
+ const closeContentOverlay = () => {
33
+ contentOverlayRef.current?.close?.();
34
+ };
35
+
36
36
  return (
37
37
  <SafeAreaProvider>
38
- <Host>
39
- <View
40
- style={{
41
- width: 300,
42
- }}
38
+ <BottomSheetModalProvider>
39
+ <View style={{ display: "flex", flexDirection: "column", gap: 16 }}>
40
+ <Heading>Basic ContentOverlay</Heading>
41
+ <Text>
42
+ Note that due to the differences between React Native Web and React
43
+ Native, this does not render 100% properly
44
+ </Text>
45
+ <Button label="Open Content Overlay" onPress={openContentOverlay} />
46
+ <Button label="Close Content Overlay" onPress={closeContentOverlay} />
47
+ </View>
48
+ <ContentOverlay
49
+ ref={contentOverlayRef}
50
+ title="Content Overlay Title"
51
+ onClose={() => console.log("closed content overlay")}
52
+ onOpen={() => console.log("opened content overlay")}
43
53
  >
44
- <ContentOverlay
45
- title={args.title}
46
- onClose={args.onClose}
47
- onOpen={args.onOpen}
48
- fullScreen={args.fullScreen}
49
- ref={contentOverlayRef}
50
- >
51
- <Content>
52
- <Text>I am some text inside the ContentOverlay.</Text>
53
- </Content>
54
- </ContentOverlay>
55
- <View>
56
- <Button
57
- label="Open Overlay"
58
- onPress={() => contentOverlayRef.current?.open?.()}
59
- />
54
+ <View style={{ padding: 16 }}>
55
+ <Text>This is the content inside the overlay.</Text>
60
56
  </View>
61
- </View>
62
- </Host>
57
+ </ContentOverlay>
58
+ </BottomSheetModalProvider>
63
59
  </SafeAreaProvider>
64
60
  );
65
61
  };
@@ -2,43 +2,43 @@ import { buildThemedStyles } from "../AtlantisThemeContext";
2
2
 
3
3
  export const useStyles = buildThemedStyles(tokens => {
4
4
  const modalBorderRadius = tokens["radius-larger"];
5
- const titleOffsetFromHandle = tokens["space-base"];
6
5
 
7
6
  return {
7
+ handleWrapper: {
8
+ paddingBottom: tokens["space-smallest"],
9
+ paddingTop: tokens["space-small"],
10
+ },
11
+
8
12
  handle: {
9
13
  width: tokens["space-largest"],
10
14
  height: tokens["space-smaller"] + tokens["space-smallest"],
11
15
  backgroundColor: tokens["color-border"],
12
- top: tokens["space-small"],
13
16
  borderRadius: tokens["radius-circle"],
14
17
  },
15
18
 
16
- overlay: {
19
+ backdrop: {
17
20
  backgroundColor: tokens["color-overlay"],
18
21
  },
19
22
 
20
- modal: {
23
+ background: {
21
24
  borderTopLeftRadius: modalBorderRadius,
22
25
  borderTopRightRadius: modalBorderRadius,
23
26
  },
24
27
 
25
- modalForLargeScreens: {
26
- width: 640,
27
- alignSelf: "center",
28
- },
29
-
30
28
  header: {
31
29
  flexDirection: "row",
32
- backgroundColor: tokens["color-surface"],
33
- paddingTop: titleOffsetFromHandle,
34
30
  zIndex: tokens["elevation-base"],
31
+ minHeight: tokens["space-extravagant"] - tokens["space-base"],
35
32
  borderTopLeftRadius: modalBorderRadius,
36
33
  borderTopRightRadius: modalBorderRadius,
37
- minHeight: tokens["space-extravagant"],
38
34
  },
39
35
 
40
36
  headerShadow: {
41
37
  ...tokens["shadow-base"],
38
+ position: "absolute",
39
+ top: -20,
40
+ height: 20,
41
+ width: "100%",
42
42
  },
43
43
 
44
44
  childrenStyle: {