Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ export const useStyles = buildThemedStyles(tokens => {
borderTopRightRadius: modalBorderRadius,
},

modalForLargeScreens: {
width: 640,
alignSelf: "center",
},

header: {
flexDirection: "row",
zIndex: tokens["elevation-base"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import {
waitFor,
} from "@testing-library/react-native";
import { AccessibilityInfo, View } from "react-native";
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
import type { ReactTestInstance } from "react-test-renderer";
import type { ContentOverlayRef, ModalBackgroundColor } from "./types";
import { ContentOverlay } from "./ContentOverlay";
import { ContentOverlayProvider } from "./ContentOverlayProvider";
import { tokens } from "../utils/design";
import { Button } from "../Button";
import { Content } from "../Content";
Expand Down Expand Up @@ -78,7 +78,7 @@ function renderContentOverlay(
const contentOverlayRef = createRef<ContentOverlayRef>();

render(
<BottomSheetModalProvider>
<ContentOverlayProvider>
<View>
<Text>I am a bunch of text</Text>
<Button
Expand All @@ -103,7 +103,7 @@ function renderContentOverlay(
</Content>
</ContentOverlay>
</View>
</BottomSheetModalProvider>,
</ContentOverlayProvider>,
);
}

Expand Down Expand Up @@ -358,3 +358,88 @@ describe("modalBackgroundColor prop", () => {
});
});
});

describe("scrollEnabled prop", () => {
describe("when scrollEnabled is false (default)", () => {
it("should render content in BottomSheetView", async () => {
const options: testRendererOptions = {
...getDefaultOptions(),
};
await renderAndOpenContentOverlay(options);

expect(screen.getByText(options.text)).toBeDefined();
expect(screen.getByTestId("ATL-Overlay-Children")).toBeDefined();
});
});
});

describe("loading prop", () => {
describe("when loading is true", () => {
it("should show subdued heading text", async () => {
const overlayRef = createRef<ContentOverlayRef>();

render(
<ContentOverlayProvider>
<View>
<ContentOverlay
ref={overlayRef}
title="Loading Overlay"
loading={true}
showDismiss={true}
>
<Text>Loading content</Text>
</ContentOverlay>
</View>
</ContentOverlayProvider>,
);

await act(async () => {
overlayRef.current?.open?.();
});

await waitFor(() => {
expect(screen.getByText("Loading Overlay")).toBeDefined();
});
});
});
});

describe("onBeforeExit callback", () => {
describe("when close button is pressed with onBeforeExit defined", () => {
it("should call onBeforeExit instead of immediately closing", async () => {
const overlayRef = createRef<ContentOverlayRef>();
const onBeforeExitCallback = jest.fn();

render(
<ContentOverlayProvider>
<View>
<ContentOverlay
ref={overlayRef}
title="Confirmation Required"
onBeforeExit={onBeforeExitCallback}
showDismiss={true}
>
<Text>Must confirm to close</Text>
</ContentOverlay>
</View>
</ContentOverlayProvider>,
);

await act(async () => {
overlayRef.current?.open?.();
});

await waitFor(() => {
expect(screen.getByText("Must confirm to close")).toBeDefined();
});

const closeButton = screen.getByTestId("ATL-Overlay-CloseButton");
await user.press(closeButton);

await waitFor(() => {
expect(onBeforeExitCallback).toHaveBeenCalled();
expect(screen.getByText("Must confirm to close")).toBeDefined();
});
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍

});
63 changes: 45 additions & 18 deletions packages/components-native/src/ContentOverlay/ContentOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import React, { useImperativeHandle, useMemo, useRef, useState } from "react";
import { AccessibilityInfo, View, findNodeHandle } from "react-native";
import {
AccessibilityInfo,
View,
findNodeHandle,
useWindowDimensions,
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import {
BottomSheetBackdrop,
Expand All @@ -21,6 +26,8 @@ import { Heading } from "../Heading";
import { useAtlantisI18n } from "../hooks/useAtlantisI18n";
import { useAtlantisTheme } from "../AtlantisThemeContext";

const LARGE_SCREEN_BREAKPOINT = 640;

function getModalBackgroundColor(
variation: ModalBackgroundColor,
tokens: ReturnType<typeof useAtlantisTheme>["tokens"],
Expand Down Expand Up @@ -52,6 +59,7 @@ export function ContentOverlay({
ref,
}: ContentOverlayProps) {
const insets = useSafeAreaInsets();
const { width: windowWidth } = useWindowDimensions();
const bottomSheetModalRef = useRef<BottomSheetModalType>(null);
const previousIndexRef = useRef(-1);
const [currentPosition, setCurrentPosition] = useState<number>(-1);
Expand All @@ -66,7 +74,7 @@ export function ContentOverlay({
const shouldShowDismiss =
showDismiss || isScreenReaderEnabled || isFullScreenOrTopPosition;

const draggable = onBeforeExit ? false : isDraggable;
const draggable = determineDraggable(isDraggable, onBeforeExit);
// Prevent the Overlay from being flush with the top of the screen, even if we are "100%" or "fullscreen"
const topInset = insets.top || tokens["space-larger"];

Expand All @@ -76,18 +84,14 @@ export function ContentOverlay({
BottomSheetScrollViewMethods & { scrollTop?: number }
>(null);

// If isDraggable is true, we always want to have a snap point at 100%
// enableDynamicSizing will add another snap point of the content height
const snapPoints = useMemo(() => {
// When this is enabled, it prevents the modal from being dragged to fullscreen. This is solely necessary
// to match the behaviour of the previous library. Ideally we should consider ripping this out, but there's
// a lot of surface area to test and verify.
if (adjustToContentHeight) {
return [];
}

// There is a bug with "restore" behavior after keyboard is dismissed.
// https://github.com/gorhom/react-native-bottom-sheet/issues/2465
// providing a 100% snap point "fixes" it for now, but there is an approved PR to fix it
// that just needs to be merged and released: https://github.com/gorhom/react-native-bottom-sheet/pull/2511
return ["100%"];
}, [adjustToContentHeight]);
}, []);

const onCloseController = () => {
if (!onBeforeExit) {
Expand Down Expand Up @@ -127,9 +131,6 @@ export function ContentOverlay({
AccessibilityInfo.setAccessibilityFocus(reactTag);
}
}
} else if (previousIndex >= 0 && index === -1) {
// Transitioned from open to closed
onClose?.();
}

previousIndexRef.current = index;
Expand All @@ -140,10 +141,19 @@ export function ContentOverlay({
setShowHeaderShadow(scrollTop > 0);
};

const modalStyle = [
const sheetStyle = useMemo(
() =>
windowWidth > LARGE_SCREEN_BREAKPOINT
? {
width: LARGE_SCREEN_BREAKPOINT,
marginLeft: (windowWidth - LARGE_SCREEN_BREAKPOINT) / 2,
}
: undefined,
[windowWidth],
);

const backgroundStyle = [
styles.background,
// TODO: Add large screen styles?
// windowWidth > 640 ? styles.modalForLargeScreens : undefined,
{ backgroundColor: getModalBackgroundColor(modalBackgroundColor, tokens) },
];

Expand Down Expand Up @@ -224,7 +234,8 @@ export function ContentOverlay({
<BottomSheetModal
ref={bottomSheetModalRef}
onChange={handleChange}
backgroundStyle={modalStyle}
style={sheetStyle}
backgroundStyle={backgroundStyle}
handleStyle={styles.handleWrapper}
handleIndicatorStyle={handleIndicatorStyles}
backdropComponent={props => (
Expand All @@ -233,8 +244,12 @@ export function ContentOverlay({
snapPoints={snapPoints}
enablePanDownToClose={draggable}
enableContentPanningGesture={draggable}
enableHandlePanningGesture={draggable}
enableDynamicSizing={!fullScreen || adjustToContentHeight}
keyboardBehavior="interactive"
keyboardBlurBehavior="restore"
topInset={topInset}
onDismiss={() => onClose?.()}
>
{scrollEnabled ? (
<BottomSheetScrollView
Expand Down Expand Up @@ -284,3 +299,15 @@ function Backdrop(
/>
);
}

function determineDraggable(isDraggable: boolean, onBeforeExit?: () => void) {
// If onBeforeExit is provided, we don't want to allow the modal to be dragged to fullscreen.
// This appears to be because previously we could only reliably get a callback when the overlay was closed
// via the dismiss button. Furthermore, the dismiss button would only be present if it was not draggable.
// While we no longer need to adhere to this somewhat awkward behavior, we will leave it as is until a larger refactor.
if (onBeforeExit) {
return false;
}

return isDraggable;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";

interface ContentOverlayProviderProps {
readonly children: React.ReactNode;
}

export function ContentOverlayProvider({
children,
}: ContentOverlayProviderProps) {
return <BottomSheetModalProvider>{children}</BottomSheetModalProvider>;
}
1 change: 1 addition & 0 deletions packages/components-native/src/ContentOverlay/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { ContentOverlay } from "./ContentOverlay";
export { ContentOverlayProvider } from "./ContentOverlayProvider";
export type { ContentOverlayRef, ModalBackgroundColor } from "./types";
Loading