@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.
- package/dist/package.json +3 -6
- package/dist/src/ContentOverlay/BottomSheetKeyboardAwareScrollView.js +19 -0
- package/dist/src/ContentOverlay/ContentOverlay.js +143 -107
- package/dist/src/ContentOverlay/ContentOverlay.style.js +8 -12
- package/dist/src/ContentOverlay/computeContentOverlayBehavior.js +76 -0
- package/dist/src/ContentOverlay/constants.js +1 -0
- package/dist/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.js +25 -0
- package/dist/src/ContentOverlay/index.js +1 -1
- package/dist/src/InputText/InputText.js +44 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/ContentOverlay/BottomSheetKeyboardAwareScrollView.d.ts +11 -0
- package/dist/types/src/ContentOverlay/ContentOverlay.d.ts +2 -5
- package/dist/types/src/ContentOverlay/ContentOverlay.style.d.ts +11 -10
- package/dist/types/src/ContentOverlay/computeContentOverlayBehavior.d.ts +32 -0
- package/dist/types/src/ContentOverlay/constants.d.ts +1 -0
- package/dist/types/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.d.ts +7 -0
- package/dist/types/src/ContentOverlay/index.d.ts +1 -1
- package/dist/types/src/ContentOverlay/types.d.ts +5 -12
- package/jestSetup.js +2 -0
- package/package.json +3 -6
- package/src/ContentOverlay/BottomSheetKeyboardAwareScrollView.tsx +36 -0
- package/src/ContentOverlay/ContentOverlay.stories.tsx +32 -36
- package/src/ContentOverlay/ContentOverlay.style.ts +12 -12
- package/src/ContentOverlay/ContentOverlay.test.tsx +157 -79
- package/src/ContentOverlay/ContentOverlay.tsx +247 -205
- package/src/ContentOverlay/computeContentOverlayBehavior.test.ts +276 -0
- package/src/ContentOverlay/computeContentOverlayBehavior.ts +119 -0
- package/src/ContentOverlay/constants.ts +1 -0
- package/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.test.ts +81 -0
- package/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.ts +36 -0
- package/src/ContentOverlay/index.ts +4 -1
- package/src/ContentOverlay/types.ts +5 -13
- package/src/InputText/InputText.test.tsx +122 -0
- package/src/InputText/InputText.tsx +62 -2
- package/dist/src/ContentOverlay/UNSAFE_WrappedModalize.js +0 -23
- package/dist/types/src/ContentOverlay/UNSAFE_WrappedModalize.d.ts +0 -3
- 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
|
|
5
|
-
|
|
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
|
-
|
|
12
|
+
backdrop: {
|
|
10
13
|
backgroundColor: string;
|
|
11
14
|
};
|
|
12
|
-
|
|
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
|
-
*
|
|
79
|
-
* @default Platform.select({ ios: true, android: false })
|
|
72
|
+
* Ref to the content overlay component.
|
|
80
73
|
*/
|
|
81
|
-
readonly
|
|
74
|
+
readonly ref?: Ref<ContentOverlayRef>;
|
|
82
75
|
}
|
|
83
76
|
export type ModalBackgroundColor = "surface" | "background";
|
|
84
77
|
export type ContentOverlayRef = {
|
|
85
|
-
open?:
|
|
86
|
-
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.
|
|
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": "
|
|
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
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
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 = (
|
|
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
|
-
<
|
|
39
|
-
<View
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
<
|
|
45
|
-
|
|
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
|
-
</
|
|
62
|
-
</
|
|
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
|
-
|
|
19
|
+
backdrop: {
|
|
17
20
|
backgroundColor: tokens["color-overlay"],
|
|
18
21
|
},
|
|
19
22
|
|
|
20
|
-
|
|
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: {
|