From 943db65c0ff46cc37df2472c0aba4ccd52baf479 Mon Sep 17 00:00:00 2001 From: Alessandro Senese Date: Mon, 29 Jul 2024 17:53:08 +0100 Subject: [PATCH] Make ScrollView optional (#218) * Make ScrollView optional * Simplify scrollViewProps * Added BottomSheet docs * fix `topInset` code style * fix example with updated BottomSheet props * update 'children' to use code in docs * Add additional BottomSheet props to docs * fix doc render error * change prop name --------- Co-authored-by: JDMathew --- .../shared/src/screens/BottomSheet.screen.tsx | 4 +- packages/extras/docs/BottomSheet.md | 262 ++++++++++++++++++ .../extras/src/components/BottomSheet.tsx | 55 +++- .../version-0.7.x/components/BottomSheet.md | 2 +- 4 files changed, 309 insertions(+), 14 deletions(-) create mode 100644 packages/extras/docs/BottomSheet.md diff --git a/examples/shared/src/screens/BottomSheet.screen.tsx b/examples/shared/src/screens/BottomSheet.screen.tsx index a011cf09..6cefaa06 100644 --- a/examples/shared/src/screens/BottomSheet.screen.tsx +++ b/examples/shared/src/screens/BottomSheet.screen.tsx @@ -30,8 +30,8 @@ export const BottomSheetScreen: React.FC = () => { } scrollViewProps={{ style: styles.modalViewContent, - }} - scrollEnabled={true}> + scrollEnabled: true, + }}> ( + { + setModalVisible(!modalVisible); + }} + closeActionAccessibilityLabel="close bottomsheet" + bottomSheetStyle={styles.modalView} + headerComponent={ + +
+ + } + scrollViewStyle={styles.modalViewContent}> + setModalVisible(!modalVisible)} + accessibilityRole="button"> + Close bottom sheet + + +); +``` + +## Accessibility + +- Checks that the `closeActionAccessibilityLabel` is a valid [accessibilityLabel](./guidelines/accessibility-label) +- Provides a way [to close the bottom sheet](../guidelines/bottomsheet#2-can-be-dismissed) when the user taps on the overlay +- Makes sure that the overlay is announced as "button" +- Uses slide-in and slide-out animation **only** if the [Reduce Motion] (https://reactnative.dev/docs/accessibilityinfo) preference is turned off +- Prevents the focus from [escaping the bottom sheet](../guidelines/bottomsheet#3-the-focus-stays-inside-it) +- Provides a draggable area respecting the [minimum size](../guidelines/minimum-size) + +## Props + +### `animationDuration` + +The duration in milliseconds of the slide in/out animation. + +| Type | Default | +| ------ | ------- | +| number | 300 | + +### `autoCloseDelay` + +The duration in milliseconds before auto-closing the bottom sheet + +| Type | Default | +| ------ | --------- | +| number | undefined | + +:::tip + +The auto-close will respect the user [Timed action](./guidelines/timed-actions) preference. + +::: + +### `bottomSheetStyle` + +The style to use for the bottom sheet panel + +| Type | Default | +| --------- | -------------------------------------------- | +| ViewStyle | `{ width: '100%', backgroundColor: '#fff' }` | + +### `closeActionAccessibilityLabel` + +The accessibility label to use for the overlay button. + +| Type | +| ------ | +| string | + +### `closeDistance` + +A decimal fraction percentage of the content height which represents the minimum distance to swipe before the `onClose` function is run. For example, 0.3 represents 30% of the content height needing to be gesture swiped away before the `BottomSheet` will close and the `onClose` function will run. + +| Type | Default | +| ------ | ------- | +| number | 0.3 | + +### `footerComponent` + +The bottom sheet footer component. + +| Type | +| ----------- | +| JSX.Element | + +### `handleComponent` + +It can be used to either disable the default handle "line" or replace it with a custom component. + +| Type | +| ----------------------- | +| JSX.Element \| `'none'` | + +### `handleStyle` + +The style for the draggable line + +| Type | Default | +| --------- | -------------------------------------------------------------------------------------------------------------------------- | +| ViewStyle | `{ width: 48, height: 4, backgroundColor: 'grey', alignSelf: 'center', marginBottom: 24, borderRadius: 2, marginTop: 12 }` | + +### `headerComponent` + +The bottom sheet header component. + +| Type | +| ----------- | +| JSX.Element | + +### `maxHeight` + +The maximum height of the bottom sheet. + +| Type | Default | +| ------ | ------------------------ | +| number | 90% of the screen height | + +### `minVelocityToClose` + +The minimum velocity needed by quickly swiping down to close the bottom sheet. + +| Type | Default | +| ------ | ------------------------ | +| number | 90% of the screen height | + +### `onBottomSheetHidden` + +The callback to triggered after animations when the BottomSheet is hidden + +| Type | +| ---------- | +| () => void | + +### `onClose` + +The callback to trigger when the BottomSheet is dismissed + +| Type | +| ---------- | +| () => void | + +### `overlayOpacity` + +The opacity of the overlay. + +| Type | Default | +| ------ | ------- | +| number | 1 | + +### `overlayStyle` + +The style to use for the overlay + +| Type | Default | +| --------- | ---------------------------------------------------- | +| ViewStyle | `{ backgroundColor: 'rgba(0, 0, 0, 0.5)', flex: 1 }` | + +### `panGestureEnabled` + +Enable or disable the dragging gesture. + +| Type | Default | +| ------- | ------- | +| boolean | true | + +### `persistent` + +If true, the bottom sheet will not be closed when the user taps on the overlay; the dragging gesture is also disabled. + +| Type | Default | +| ------- | ------- | +| boolean | true | + +### `hasScrollableContent` + +If true, `children` of `BottomSheet` are wrapped in a [``](https://reactnative.dev/docs/scrollview) + +| Type | Default | +| ------- | ------- | +| boolean | true | + +### `scrollViewProps` + +The props to use for the [``](https://reactnative.dev/docs/scrollview) that wraps the BottomSheet content + +| Type | Default | +| --------------- | --------- | +| scrollViewProps | undefined | + +### `shouldHandleKeyboardEvents` + +If true, keyboard events are handled internally by the `BottomSheet` allowing for scrolling and animations to run smoothly + +| Type | Default | +| ------- | ------- | +| boolean | true | + +### `testID` + +A testID to be used by the BottomSheet. The `Modal` component within the `BottomSheet` will receive this base `testID` and other internal components will receive abstractions of the base `testID`. It is best to view the source code to understand the hierarchy of these components and what testID's they will receive. + +| Type | +| ------ | +| string | + +### `topInset` + +The value is used to calculate the correct max ScrollView height. + +| Type | Default | +| ------ | ------- | +| number | 0 | + +### `visible` + +The BottomSheet visibility + +| Type | +| ------- | +| boolean | + +### `ref` + +A `ref` object used to call `close` and `isVisible` functions on the bottomSheet + +| Type | +| ----------------------------------------------------------- | +| `{ close: () => Promise; isVisible: () => boolean; }` | + +## Known issues + +If the app crashes with the following error: + +> Unsupported top level event type "onGestureHandlerStateChange" dispatched + +Add the following import at the top of your `App.tsx|jsx` file: + +```js +// https://github.com/software-mansion/react-native-gesture-handler/issues/320 +import 'react-native-gesture-handler'; +``` + +## Related guidelines + +- [BottomSheet](../guidelines/bottomsheet) +- [Focus](../guidelines/focus) diff --git a/packages/extras/src/components/BottomSheet.tsx b/packages/extras/src/components/BottomSheet.tsx index 7dd30c4a..e55e749d 100644 --- a/packages/extras/src/components/BottomSheet.tsx +++ b/packages/extras/src/components/BottomSheet.tsx @@ -51,8 +51,11 @@ export type BottomSheetProps = { overlayStyle?: ViewStyle | ViewStyle[]; panGestureEnabled?: boolean; persistent?: boolean; - scrollEnabled?: boolean; - scrollViewProps?: Omit; + hasScrollableContent?: boolean; + scrollViewProps?: Omit< + ScrollViewWrapperProps, + 'testID' | 'maxScrollViewHeight' + >; testID?: string; topInset: number; visible: boolean; @@ -85,8 +88,8 @@ export const BottomSheet = React.forwardRef< animationDuration = 300, closeDistance = 0.3, handleComponent, - scrollEnabled = false, scrollViewProps, + hasScrollableContent = true, persistent = false, autoCloseDelay, panGestureEnabled = true, @@ -258,6 +261,10 @@ export const BottomSheet = React.forwardRef< ? 1 : 0; + const ContentWrapper = hasScrollableContent + ? ScrollViewWrapper + : FragmentWrapper; + return ( {headerComponent} - + testID={`${testID}-scrollview`} + maxScrollViewHeight={maxScrollViewHeight}> {children} - + { setFooterHeight(event.nativeEvent.layout.height); @@ -414,6 +417,36 @@ const GestureHandler = ({ ); }; +type ScrollViewWrapperProps = { + testID?: string; + maxScrollViewHeight: number; +} & ScrollViewProps; + +const ScrollViewWrapper: React.FC = ({ + scrollEnabled = false, + testID, + maxScrollViewHeight, + children, + style, + ...rest +}) => { + return ( + + {children} + + ); +}; + +const FragmentWrapper: React.FC> = ({ + children, +}) => { + return <>{children}; +}; + const styles = StyleSheet.create({ overlay: { backgroundColor: 'rgba(0, 0, 0, 0.5)', diff --git a/website/versioned_docs/version-0.7.x/components/BottomSheet.md b/website/versioned_docs/version-0.7.x/components/BottomSheet.md index 690577ba..be76c8d6 100644 --- a/website/versioned_docs/version-0.7.x/components/BottomSheet.md +++ b/website/versioned_docs/version-0.7.x/components/BottomSheet.md @@ -183,7 +183,7 @@ The props to use for the [``](https://reactnative.dev/docs/scrollv | --------------- | --------- | | scrollViewProps | undefined | -### ` `topInset` +### `topInset` The value is used to calculate the correct max ScrollView height.