Skip to content

Commit

Permalink
feat: add compat mode for previously removed formSheet prop values (s…
Browse files Browse the repository at this point in the history
…oftware-mansion#2356)

## Description

This PR intends to add support for previously removed
`sheetAllowedDetents` and `sheetLargestUndimmedDetent` props values:
`medium`, `large` and `all`.

With addition of custom detents for iOS I've changed the API of
NativeScreen component to receive `number[]` as the detent list and
removed the old options. This removal was unnecessary. I've restored
these options on the `Screen` component abstraction layer, where old
options are translated to the new API w/o need for additional support in
downstream packages such as react-navigation.

Corresponding PR in `react-navigation`:

* react-navigation/react-navigation#12032

## Changes

* updated the types,
* added "translation layer" in InnerScreen.


## Test code and steps to reproduce

Test1649 is great for testing all sheet related props.

## Checklist

- [x] Included code example that can be used to test this change
- [x] Updated TS types
- [x] Updated documentation: <!-- For adding new props to native-stack
-->
- [x]
https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md
- [x]
https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md
- [x]
https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx
- [x]
https://github.com/software-mansion/react-native-screens/blob/main/src/native-stack/types.tsx
- [x] Ensured that CI passes
  • Loading branch information
kkafar authored and ja1ns committed Oct 9, 2024
1 parent 1a83f0c commit 1f98053
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 26 deletions.
30 changes: 19 additions & 11 deletions guides/GUIDE_FOR_LIBRARY_AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,17 +150,23 @@ Sets the current screen's available orientations and forces rotation if current

Defaults to `default` on iOS.

### `sheetAllowedDetents` (iOS only)
### `sheetAllowedDetents`

Describes heights where a sheet can rest.
Works only when `stackPresentation` is set to `formSheet`.
Works only when `presentation` is set to `formSheet`.

Heights should be described as fraction (a number from `[0, 1]` interval) of screen height / maximum detent height.
There is also possibility to specify `[-1]` literal array with single element, which intets to set the sheet height
There is also possibility to specify `fitToContents` literal, which intents to set the sheet height
to the height of its contents.

Please note that the array **must** be sorted in ascending order.

There are also legacy & **deprecated** options available:

* `medium` - corresponds to `[0.5]` detent value, around half of the screen height,
* `large` - corresponds to `[1.0]` detent value, maximum height,
* `all` - corresponds to `[0.5, 1.0]` value, the name is deceiving due to compatibility reasons.

Defaults to `[1.0]` literal.

### `sheetExpandsWhenScrolledToEdge` (iOS only)
Expand All @@ -170,7 +176,7 @@ Works only when `stackPresentation` is set to `formSheet`.

Defaults to `true`.

### `sheetCornerRadius (iOS only)
### `sheetCornerRadius`

The corner radius that the sheet will try to render with.
Works only when `stackPresentation` is set to `formSheet`.
Expand All @@ -188,15 +194,15 @@ Defaults to `false`.
### `sheetLargestUndimmedDetent` (iOS only)

The largest sheet detent for which a view underneath won't be dimmed.
Works only when `stackPresentation` is set to `formSheet`.
Works only when `presentation` is set to `formSheet`.

If this prop is set to:
This prop can be set to an number, which indicates index of detent in `sheetAllowedDetents` array for which
there won't be a dimming view beneath the sheet.

- `large` - the view underneath won't be dimmed at any detent level
- `medium` - the view underneath will be dimmed only when detent level is `large`
- `all` - the view underneath will be dimmed for any detent level
There also legacy & **deprecated** prop values available, which work in tandem with
corresponding legacy proop values for `sheetAllowedDetents` prop.

Defaults to `all`.
Defaults to `-1`, indicating that the dimming view should be always present.

### `stackAnimation`

Expand Down Expand Up @@ -365,7 +371,7 @@ function Home() {
}
```

### unstable_footerComponent
### unstable_sheetFooter (Android only)

Footer component that can be used alongside form sheet stack presentation style.

Expand All @@ -375,6 +381,8 @@ to implement such layout with JS-only code.
Please note that this prop is marked as unstable and might be subject of breaking changes,
even removal.

Currently supported on Android only.


## `<ScreenStackHeaderConfig>`

Expand Down
21 changes: 19 additions & 2 deletions native-stack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,14 +247,28 @@ Defaults to `pop`.
Describes heights where a sheet can rest.
Works only when `stackPresentation` is set to `formSheet`.

Heights should be described as fraction (a number from [0, 1] interval) of screen height / maximum detent height.
There is also possibility to specify `fitToContents` literal, which intents to set the sheet height
Heights should be described as fraction (a number from `[0, 1]` interval) of screen height / maximum detent height.
There is also possibility to specify `[-1]` literal array with single element, which intets to set the sheet height
to the height of its contents.

Please note that the array **must** be sorted in ascending order.

There are also legacy & **deprecated** options available:

* 'medium' - corresponds to `[0.5]` detent value, around half of the screen height,
* 'large' - corresponds to `[1.0]` detent value, maximum height,
* 'all' - corresponds to `[0.5, 1.0]` value, the name is deceiving due to compatibility reasons.

Defaults to `[1.0]` literal.

#### `sheetElevation` (Android only)

Integer value describing elevation of the sheet, impacting shadow on the top edge of the sheet.

Not dynamic - changing it after the component is rendered won't have an effect.

Defaults to `24`.

#### `sheetExpandsWhenScrolledToEdge` (iOS only)

Whether the sheet should expand to larger detent when scrolling.
Expand Down Expand Up @@ -290,6 +304,9 @@ Works only when `stackPresentation` is set to `formSheet`.
This prop can be set to an number, which indicates index of detent in `sheetAllowedDetents` array for which
there won't be a dimming view beneath the sheet.

There also legacy & **deprecated** prop values available, which work in tandem with
corresponding legacy prop values for `sheetAllowedDetents` prop.

Defaults to `-1`, indicating that the dimming view should be always present.

#### `stackAnimation`
Expand Down
2 changes: 1 addition & 1 deletion react-navigation
Submodule react-navigation updated 39 files
+105 −1 example/__typechecks__/static.check.tsx
+14 −0 packages/bottom-tabs/CHANGELOG.md
+2 −2 packages/bottom-tabs/package.json
+1 −1 packages/bottom-tabs/src/views/BottomTabView.tsx
+6 −0 packages/core/CHANGELOG.md
+1 −1 packages/core/package.json
+1 −1 packages/core/src/BaseNavigationContainer.tsx
+12 −3 packages/core/src/getPathFromState.tsx
+121 −63 packages/core/src/getStateFromPath.tsx
+4 −0 packages/devtools/CHANGELOG.md
+2 −3 packages/devtools/package.json
+1 −1 packages/devtools/src/useDevToolsBase.tsx
+8 −0 packages/drawer/CHANGELOG.md
+2 −2 packages/drawer/package.json
+10 −0 packages/elements/CHANGELOG.md
+1 −1 packages/elements/package.json
+7 −2 packages/elements/src/PlatformPressable.tsx
+1 −1 packages/elements/src/SafeAreaProviderCompat.tsx
+8 −0 packages/material-top-tabs/CHANGELOG.md
+3 −3 packages/material-top-tabs/package.json
+8 −0 packages/native-stack/CHANGELOG.md
+2 −2 packages/native-stack/package.json
+27 −13 packages/native-stack/src/types.tsx
+1 −1 packages/native-stack/src/utils/debounce.tsx
+2 −2 packages/native-stack/src/views/DebugContainer.native.tsx
+10 −0 packages/native-stack/src/views/FooterComponent.tsx
+89 −78 packages/native-stack/src/views/NativeStackView.native.tsx
+4 −0 packages/native/CHANGELOG.md
+1 −1 packages/native/package.json
+38 −26 packages/native/src/createStaticNavigation.tsx
+1 −1 packages/native/src/useScrollToTop.tsx
+4 −0 packages/react-native-tab-view/CHANGELOG.md
+1 −1 packages/react-native-tab-view/package.json
+1 −1 packages/react-native-tab-view/src/SceneView.tsx
+1 −1 packages/react-native-tab-view/src/TabBar.tsx
+8 −0 packages/stack/CHANGELOG.md
+2 −2 packages/stack/package.json
+1 −1 packages/stack/src/utils/throttle.tsx
+17 −140 yarn.lock
55 changes: 53 additions & 2 deletions src/components/Screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,50 @@ interface ViewConfig extends View {
};
}

// This value must be kept in sync with native side.
const SHEET_FIT_TO_CONTENTS = [-1];
const SHEET_COMPAT_LARGE = [1.0];
const SHEET_COMPAT_MEDIUM = [0.5];
const SHEET_COMPAT_ALL = [0.5, 1.0];

// These exist to transform old 'legacy' values used by the formsheet API to the new API shape.
// We can get rid of it, once we get rid of support for legacy values: 'large', 'medium', 'all'.
function resolveSheetAllowedDetents(
allowedDetentsCompat: ScreenProps['sheetAllowedDetents'],
): number[] {
if (Array.isArray(allowedDetentsCompat)) {
return allowedDetentsCompat;
} else if (allowedDetentsCompat === 'fitToContents') {
return SHEET_FIT_TO_CONTENTS;
} else if (allowedDetentsCompat === 'large') {
return SHEET_COMPAT_LARGE;
} else if (allowedDetentsCompat === 'medium') {
return SHEET_COMPAT_MEDIUM;
} else if (allowedDetentsCompat === 'all') {
return SHEET_COMPAT_ALL;
} else {
// Safe default, only large detent is allowed.
return [1.0];
}
}

function resolveSheetLargestUndimmedDetent(
lud: ScreenProps['sheetLargestUndimmedDetent'],
): number {
if (typeof lud === 'number') {
return lud;
} else if (lud === 'large') {
return 1;
} else if (lud === 'medium') {
return 0;
} else if (lud === 'all') {
return -1;
} else {
// Safe default, every detent is dimmed
return -1;
}
}

export const InnerScreen = React.forwardRef<View, ScreenProps>(
function InnerScreen(props, ref) {
const innerRef = React.useRef<ViewConfig | null>(null);
Expand All @@ -66,17 +110,24 @@ export const InnerScreen = React.forwardRef<View, ScreenProps>(
// To maintain default behavior of formSheet stack presentation style and to have reasonable
// defaults for new medium-detent iOS API we need to set defaults here
const {
// formSheet presentation related props
sheetAllowedDetents = [1.0],
sheetLargestUndimmedDetent = -1,
sheetGrabberVisible = false,
sheetCornerRadius = -1.0,
sheetExpandsWhenScrolledToEdge = true,
sheetElevation = 24,
sheetInitialDetent = 0,

// Other
stackPresentation,
} = rest;

if (enabled && isNativePlatformSupported) {
const resolvedSheetAllowedDetents =
resolveSheetAllowedDetents(sheetAllowedDetents);
const resolvedSheetLargestUndimmedDetent =
resolveSheetLargestUndimmedDetent(sheetLargestUndimmedDetent);
// Due to how Yoga resolves layout, we need to have different components for modal nad non-modal screens
const AnimatedScreen =
Platform.OS === 'android' ||
Expand Down Expand Up @@ -134,8 +185,8 @@ export const InnerScreen = React.forwardRef<View, ScreenProps>(
// Detailed information can be found here https://github.com/software-mansion/react-native-screens/pull/2351
style={[style, { zIndex: undefined }]}
activityState={activityState}
sheetAllowedDetents={sheetAllowedDetents}
sheetLargestUndimmedDetent={sheetLargestUndimmedDetent}
sheetAllowedDetents={resolvedSheetAllowedDetents}
sheetLargestUndimmedDetent={resolvedSheetLargestUndimmedDetent}
sheetElevation={sheetElevation}
sheetGrabberVisible={sheetGrabberVisible}
sheetCornerRadius={sheetCornerRadius}
Expand Down
2 changes: 1 addition & 1 deletion src/native-stack/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ export type NativeStackNavigationOptions = {
*
* @platform android
*/
unstable_footerComponent?: React.ReactNode;
unstable_sheetFooter?: () => React.ReactNode;
};

export type NativeStackNavigatorProps =
Expand Down
13 changes: 7 additions & 6 deletions src/native-stack/views/NativeStackView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ const RouteView = ({
swipeDirection = 'horizontal',
transitionDuration,
freezeOnBlur,
unstable_footerComponent = null,
unstable_sheetFooter = null,
} = options;

let {
Expand All @@ -229,9 +229,10 @@ const RouteView = ({
} = options;

// We only want to allow backgroundColor for now
unstable_screenStyle = unstable_screenStyle
? { backgroundColor: unstable_screenStyle.backgroundColor }
: null;
unstable_screenStyle =
stackPresentation === 'formSheet' && unstable_screenStyle
? { backgroundColor: unstable_screenStyle.backgroundColor }
: null;

if (sheetAllowedDetents === 'fitToContents') {
sheetAllowedDetents = [-1];
Expand Down Expand Up @@ -450,8 +451,8 @@ const RouteView = ({
route={route}
headerShown={isHeaderInPush}
/>
{unstable_footerComponent && (
<FooterComponent>{unstable_footerComponent}</FooterComponent>
{stackPresentation === 'formSheet' && unstable_sheetFooter && (
<FooterComponent>{unstable_sheetFooter()}</FooterComponent>
)}
</HeaderHeightContext.Provider>
</AnimatedHeaderHeightContext.Provider>
Expand Down
27 changes: 24 additions & 3 deletions src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ export interface ScreenProps extends ViewProps {
active?: 0 | 1 | Animated.AnimatedInterpolation<number>;
activityState?: 0 | 1 | 2 | Animated.AnimatedInterpolation<number>;
children?: React.ReactNode;
unstable_footer?: React.ReactNode;
/**
* Boolean indicating that swipe dismissal should trigger animation provided by `stackAnimation`. Defaults to `false`.
*
Expand Down Expand Up @@ -299,9 +298,15 @@ export interface ScreenProps extends ViewProps {
*
* Please note that the array **must** be sorted in ascending order.
*
* There are also legacy & **deprecated** options available:
*
* * 'medium' - corresponds to `[0.5]` detent value, around half of the screen height,
* * 'large' - corresponds to `[1.0]` detent value, maximum height,
* * 'all' - corresponds to `[0.5, 1.0]` value, the name is deceiving due to compatibility reasons.
*
* Defaults to `[1.0]` literal.
*/
sheetAllowedDetents?: number[];
sheetAllowedDetents?: number[] | 'fitToContents' | 'medium' | 'large' | 'all';
/**
* Integer value describing elevation of the sheet, impacting shadow on the top edge of the sheet.
*
Expand Down Expand Up @@ -346,9 +351,12 @@ export interface ScreenProps extends ViewProps {
* This prop can be set to an number, which indicates index of detent in `sheetAllowedDetents` array for which
* there won't be a dimming view beneath the sheet.
*
* There also legacy & **deprecated** prop values available, which work in tandem with
* corresponding legacy prop values for `sheetAllowedDetents` prop.
*
* Defaults to `-1`, indicating that the dimming view should be always present.
*/
sheetLargestUndimmedDetent?: number;
sheetLargestUndimmedDetent?: number | 'medium' | 'large' | 'all';
/**
* Index of the detent the sheet should expand to after being opened.
* Works only when `stackPresentation` is set to `formSheet`.
Expand Down Expand Up @@ -426,6 +434,19 @@ export interface ScreenProps extends ViewProps {
* @platform ios
*/
transitionDuration?: number;
/**
* Footer component that can be used alongside formSheet stack presentation style.
*
* This option is provided, because due to implementation details it might be problematic
* to implement such layout with JS-only code.
*
* Please note that this prop is marked as unstable and might be subject of breaking changes,
* including removal, in particular when we find solution that will make implementing it with JS
* straightforward.
*
* @platform android
*/
unstable_sheetFooter?: () => React.ReactNode;
}

export interface ScreenContainerProps extends ViewProps {
Expand Down

0 comments on commit 1f98053

Please sign in to comment.