@jobber/components-native 0.89.4 → 0.89.5-JOB-140604-4c8f8f2.41

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 (25) hide show
  1. package/dist/package.json +4 -2
  2. package/dist/src/BottomSheet/BottomSheet.js +55 -35
  3. package/dist/src/BottomSheet/BottomSheet.style.js +10 -8
  4. package/dist/src/BottomSheet/components/BottomSheetInputText/BottomSheetInputText.js +45 -0
  5. package/dist/src/BottomSheet/components/BottomSheetInputText/BottomSheetInputText.styles.js +8 -0
  6. package/dist/src/ButtonGroup/ButtonGroup.js +1 -1
  7. package/dist/src/InputText/InputText.js +2 -2
  8. package/dist/src/utils/meta/meta.json +1 -0
  9. package/dist/tsconfig.build.tsbuildinfo +1 -1
  10. package/dist/types/src/BottomSheet/BottomSheet.d.ts +13 -4
  11. package/dist/types/src/BottomSheet/BottomSheet.style.d.ts +9 -7
  12. package/dist/types/src/BottomSheet/components/BottomSheetInputText/BottomSheetInputText.d.ts +9 -0
  13. package/dist/types/src/BottomSheet/components/BottomSheetInputText/BottomSheetInputText.styles.d.ts +5 -0
  14. package/dist/types/src/InputText/InputText.d.ts +1 -1
  15. package/package.json +4 -2
  16. package/src/BottomSheet/BottomSheet.stories.tsx +129 -0
  17. package/src/BottomSheet/BottomSheet.style.ts +10 -11
  18. package/src/BottomSheet/BottomSheet.test.tsx +19 -24
  19. package/src/BottomSheet/BottomSheet.tsx +125 -103
  20. package/src/BottomSheet/components/BottomSheetInputText/BottomSheetInputText.styles.ts +9 -0
  21. package/src/BottomSheet/components/BottomSheetInputText/BottomSheetInputText.tsx +89 -0
  22. package/src/ButtonGroup/ButtonGroup.tsx +1 -1
  23. package/src/InputText/InputText.tsx +3 -3
  24. package/src/ThumbnailList/__snapshots__/ThumbnailList.test.tsx.snap +211 -1
  25. package/src/utils/meta/meta.json +1 -0
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.89.4",
3
+ "version": "0.89.5-JOB-140604-4c8f8f2.41+4c8f8f2c3",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -53,6 +53,7 @@
53
53
  "ts-xor": "^1.1.0"
54
54
  },
55
55
  "devDependencies": {
56
+ "@gorhom/bottom-sheet": "^5.2.6",
56
57
  "@react-native-community/datetimepicker": "^8.4.5",
57
58
  "@react-native/babel-preset": "^0.81.1",
58
59
  "@storybook/addon-a11y": "^9.1.2",
@@ -78,6 +79,7 @@
78
79
  },
79
80
  "peerDependencies": {
80
81
  "@babel/core": "^7.4.5",
82
+ "@gorhom/bottom-sheet": "^5.2.6",
81
83
  "@jobber/design": "*",
82
84
  "@react-native-community/datetimepicker": ">=6.7.0",
83
85
  "date-fns": "^2.30.0",
@@ -94,5 +96,5 @@
94
96
  "react-native-safe-area-context": "^5.4.0",
95
97
  "react-native-svg": ">=12.0.0"
96
98
  },
97
- "gitHead": "adc76ab79cbad1b62fa1d3ff35020f061b390292"
99
+ "gitHead": "4c8f8f2c37844355a5d654999ce62550aeeba2d8"
98
100
  }
@@ -1,52 +1,72 @@
1
- import React, { forwardRef, useState } from "react";
2
- import { useSafeAreaInsets } from "react-native-safe-area-context";
1
+ import React, { useCallback, useImperativeHandle, useRef } from "react";
3
2
  import { Keyboard, View } from "react-native";
4
- import { BottomSheetOption } from "./components/BottomSheetOption";
3
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
4
+ import RNBottomSheet, { BottomSheetBackdrop, BottomSheetFooter, BottomSheetView, } from "@gorhom/bottom-sheet";
5
5
  import { useStyles } from "./BottomSheet.style";
6
- import { UNSAFE_WrappedModalize } from "../ContentOverlay/UNSAFE_WrappedModalize";
7
- import { useIsScreenReaderEnabled } from "../hooks";
6
+ import { BottomSheetOption } from "./components/BottomSheetOption";
7
+ import { BottomSheetInputText } from "./components/BottomSheetInputText/BottomSheetInputText";
8
8
  import { Divider } from "../Divider";
9
9
  import { Heading } from "../Heading";
10
+ import { useIsScreenReaderEnabled } from "../hooks";
10
11
  import { useAtlantisI18n } from "../hooks/useAtlantisI18n";
11
- export const BottomSheet = forwardRef(BottomSheetInternal);
12
- function BottomSheetInternal({ children, showCancel, loading = false, heading, onOpen, onClose, }, ref) {
13
- const isScreenReaderEnabled = useIsScreenReaderEnabled();
14
- const [open, setOpen] = useState(false);
12
+ export function BottomSheet({ children, showCancel, loading = false, heading, onOpen, onClose, ref, }) {
15
13
  const styles = useStyles();
16
- return (React.createElement(React.Fragment, null,
17
- open && React.createElement(Overlay, { styles: styles }),
18
- React.createElement(UNSAFE_WrappedModalize, { ref: ref, adjustToContentHeight: true, modalStyle: styles.modal, overlayStyle: styles.overlayModalize, HeaderComponent: heading && React.createElement(Header, { heading: heading, styles: styles }), FooterComponent: React.createElement(Footer, { cancellable: (showCancel && !loading) || isScreenReaderEnabled, onCancel: () => {
19
- var _a;
20
- (_a = ref === null || ref === void 0 ? void 0 : ref.current) === null || _a === void 0 ? void 0 : _a.close();
21
- }, styles: styles }), withHandle: false, withReactModal: isScreenReaderEnabled, onOpen: openModal, onClose: closeModal },
22
- React.createElement(View, { style: !showCancel && !isScreenReaderEnabled ? styles.children : undefined }, children))));
23
- function openModal() {
24
- onOpen === null || onOpen === void 0 ? void 0 : onOpen();
25
- setOpen(true);
26
- dismissKeyboard();
27
- }
28
- function closeModal() {
29
- onClose === null || onClose === void 0 ? void 0 : onClose();
30
- setOpen(false);
31
- }
14
+ const isScreenReaderEnabled = useIsScreenReaderEnabled();
15
+ const cancellable = (showCancel && !loading) || isScreenReaderEnabled;
16
+ const { t } = useAtlantisI18n();
17
+ const insets = useSafeAreaInsets();
18
+ const previousIndexRef = useRef(-1);
19
+ const bottomSheetRef = useRef(null);
20
+ useImperativeHandle(ref, () => ({
21
+ open: () => {
22
+ var _a;
23
+ (_a = bottomSheetRef.current) === null || _a === void 0 ? void 0 : _a.expand();
24
+ },
25
+ close: () => {
26
+ var _a;
27
+ (_a = bottomSheetRef.current) === null || _a === void 0 ? void 0 : _a.close();
28
+ },
29
+ }));
30
+ const handleChange = (index) => {
31
+ const previousIndex = previousIndexRef.current;
32
+ if (previousIndex === -1 && index >= 0) {
33
+ // Transitioned from closed to open
34
+ dismissKeyboard();
35
+ onOpen === null || onOpen === void 0 ? void 0 : onOpen();
36
+ }
37
+ else if (previousIndex >= 0 && index === -1) {
38
+ // Transitioned from open to closed
39
+ dismissKeyboard();
40
+ onClose === null || onClose === void 0 ? void 0 : onClose();
41
+ }
42
+ previousIndexRef.current = index;
43
+ };
44
+ const renderFooter = useCallback((bottomSheetFooterProps) => {
45
+ return (React.createElement(BottomSheetFooter, Object.assign({}, bottomSheetFooterProps),
46
+ React.createElement(View, { style: [styles.footerContainer, { paddingBottom: insets.bottom }] }, cancellable && (React.createElement(View, { style: styles.footer },
47
+ React.createElement(View, { style: styles.footerDivider },
48
+ React.createElement(Divider, null)),
49
+ React.createElement(BottomSheetOption, { text: t("cancel"), icon: "remove", onPress: () => {
50
+ var _a;
51
+ (_a = bottomSheetRef.current) === null || _a === void 0 ? void 0 : _a.close();
52
+ } }))))));
53
+ }, [cancellable]);
54
+ return (React.createElement(RNBottomSheet, { ref: bottomSheetRef, index: -1, backdropComponent: Backdrop, backgroundStyle: styles.background, footerComponent: renderFooter, enablePanDownToClose: true, onChange: handleChange, keyboardBlurBehavior: "restore" },
55
+ React.createElement(BottomSheetView, { style: styles.content, enableFooterMarginAdjustment: true },
56
+ heading && React.createElement(Header, { heading: heading, styles: styles }),
57
+ children)));
32
58
  }
33
59
  function Header({ heading, styles, }) {
34
60
  return (React.createElement(View, { style: styles.header },
35
61
  React.createElement(Heading, { level: "subtitle" }, heading)));
36
62
  }
37
- function Footer({ cancellable, onCancel, styles, }) {
38
- const insets = useSafeAreaInsets();
39
- const { t } = useAtlantisI18n();
40
- return (React.createElement(View, { style: { marginBottom: insets.bottom } }, cancellable && (React.createElement(View, { style: styles.children },
41
- React.createElement(View, { style: styles.footerDivider },
42
- React.createElement(Divider, null)),
43
- React.createElement(BottomSheetOption, { text: t("cancel"), icon: "remove", onPress: onCancel })))));
44
- }
45
63
  function dismissKeyboard() {
46
64
  //Dismisses the keyboard before opening the bottom sheet.
47
65
  //In the case where an input text field is focused we don't want to show the bottom sheet behind or above keyboard
48
66
  Keyboard.dismiss();
49
67
  }
50
- function Overlay({ styles, }) {
51
- return React.createElement(View, { style: styles.overlay });
68
+ function Backdrop(bottomSheetBackdropProps) {
69
+ const styles = useStyles();
70
+ return (React.createElement(BottomSheetBackdrop, Object.assign({}, bottomSheetBackdropProps, { appearsOnIndex: 0, disappearsOnIndex: -1, style: styles.backdrop, opacity: 1 })));
52
71
  }
72
+ BottomSheet.InputText = BottomSheetInputText;
@@ -1,21 +1,23 @@
1
- import { Dimensions, StyleSheet } from "react-native";
1
+ import { StyleSheet } from "react-native";
2
2
  import { buildThemedStyles } from "../AtlantisThemeContext";
3
- const { height } = Dimensions.get("window");
4
3
  export const useStyles = buildThemedStyles(tokens => {
5
4
  const modalBorderRadius = tokens["radius-larger"];
6
5
  return {
7
- overlayModalize: {
8
- backgroundColor: "transparent",
9
- },
10
- overlay: Object.assign(Object.assign({}, StyleSheet.absoluteFillObject), { backgroundColor: tokens["color-overlay"], height }),
11
- modal: {
6
+ backdrop: Object.assign(Object.assign({}, StyleSheet.absoluteFillObject), { backgroundColor: tokens["color-overlay"] }),
7
+ background: {
12
8
  borderTopLeftRadius: modalBorderRadius,
13
9
  borderTopRightRadius: modalBorderRadius,
14
10
  paddingTop: tokens["space-small"],
15
11
  },
16
- children: {
12
+ content: {
13
+ paddingBottom: tokens["space-small"],
14
+ },
15
+ footer: {
17
16
  paddingBottom: tokens["space-small"],
18
17
  },
18
+ footerContainer: {
19
+ backgroundColor: tokens["color-surface"],
20
+ },
19
21
  header: {
20
22
  paddingHorizontal: tokens["space-base"],
21
23
  paddingTop: tokens["space-small"],
@@ -0,0 +1,45 @@
1
+ import React, { forwardRef, useCallback } from "react";
2
+ import { TextInput, View, findNodeHandle } from "react-native";
3
+ import { useBottomSheetInternal } from "@gorhom/bottom-sheet";
4
+ import { useStyles } from "./BottomSheetInputText.styles";
5
+ import { InputText } from "../../../InputText/InputText";
6
+ /**
7
+ * BottomSheetInputText is a wrapper around InputText that provides
8
+ * bottom sheet keyboard handling. It implements the handleOnFocus and
9
+ * handleOnBlur logic from BottomSheetTextInput to ensure proper keyboard
10
+ * positioning within bottom sheets.
11
+ */
12
+ export const BottomSheetInputText = forwardRef(function BottomSheetInputText(props, ref) {
13
+ const styles = useStyles();
14
+ const { onFocus, onBlur } = props;
15
+ const { animatedKeyboardState, textInputNodesRef } = useBottomSheetInternal();
16
+ const handleOnFocus = useCallback((event) => {
17
+ animatedKeyboardState.set((state) => (Object.assign(Object.assign({}, state), { target: event === null || event === void 0 ? void 0 : event.nativeEvent.target })));
18
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
19
+ }, [animatedKeyboardState, onFocus]);
20
+ const handleOnBlur = useCallback((event) => {
21
+ const keyboardState = animatedKeyboardState.get();
22
+ const currentlyFocusedInput = TextInput.State.currentlyFocusedInput();
23
+ const currentFocusedInput = currentlyFocusedInput !== null
24
+ ? findNodeHandle(
25
+ // @ts-expect-error - TextInput.State.currentlyFocusedInput() returns NativeMethods
26
+ // which is not directly assignable to findNodeHandle's expected type,
27
+ // but it works at runtime. This is a known type limitation in React Native.
28
+ currentlyFocusedInput)
29
+ : null;
30
+ /**
31
+ * we need to make sure that we only remove the target
32
+ * if the target belong to the current component and
33
+ * if the currently focused input is not in the targets set.
34
+ */
35
+ const shouldRemoveCurrentTarget = keyboardState.target === (event === null || event === void 0 ? void 0 : event.nativeEvent.target);
36
+ const shouldIgnoreBlurEvent = currentFocusedInput &&
37
+ textInputNodesRef.current.has(currentFocusedInput);
38
+ if (shouldRemoveCurrentTarget && !shouldIgnoreBlurEvent) {
39
+ animatedKeyboardState.set((state) => (Object.assign(Object.assign({}, state), { target: undefined })));
40
+ }
41
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
42
+ }, [animatedKeyboardState, textInputNodesRef, onBlur]);
43
+ return (React.createElement(View, { style: styles.inputText },
44
+ React.createElement(InputText, Object.assign({}, props, { ref: ref, onFocus: handleOnFocus, onBlur: handleOnBlur }))));
45
+ });
@@ -0,0 +1,8 @@
1
+ import { buildThemedStyles } from "../../../AtlantisThemeContext";
2
+ export const useStyles = buildThemedStyles(tokens => {
3
+ return {
4
+ inputText: {
5
+ padding: tokens["space-small"],
6
+ },
7
+ };
8
+ });
@@ -9,7 +9,7 @@ import { useAtlantisI18n } from "../hooks/useAtlantisI18n";
9
9
  export function ButtonGroup({ children, showCancelInBottomSheet, bottomSheetHeading, onOpenBottomSheet, onCloseBottomSheet, allowTapWhenOffline = false, }) {
10
10
  const { t } = useAtlantisI18n();
11
11
  const { handlePress } = usePreventTapWhenOffline();
12
- const secondaryActionsRef = useRef();
12
+ const secondaryActionsRef = useRef(null);
13
13
  const { primaryActions, secondaryActions } = getActions(children);
14
14
  const styles = useStyles();
15
15
  return (React.createElement(View, { style: styles.buttonGroup },
@@ -89,10 +89,10 @@ function InputTextInternal({ invalid, disabled, readonly = false, name, placehol
89
89
  _name && setFocusedInput(_name);
90
90
  setFocused(true);
91
91
  onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
92
- }, onBlur: () => {
92
+ }, onBlur: event => {
93
93
  _name && setFocusedInput("");
94
94
  setFocused(false);
95
- onBlur === null || onBlur === void 0 ? void 0 : onBlur();
95
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
96
96
  field.onBlur();
97
97
  trimWhitespace(inputTransform(field.value), updateFormAndState);
98
98
  }, ref: (instance) => {
@@ -12,6 +12,7 @@
12
12
  "AutoLink",
13
13
  "Banner",
14
14
  "BottomSheet",
15
+ "BottomSheet.InputText",
15
16
  "BottomSheetOption",
16
17
  "Button",
17
18
  "ButtonGroup",