From 14e3567663869edf1a14cd56f5935026c5319fb8 Mon Sep 17 00:00:00 2001 From: Hailey Date: Wed, 2 Oct 2024 21:49:49 -0700 Subject: [PATCH] [Sheets] [Pt. 12] Add event for selected snap point change (#5584) Co-authored-by: Eric Bailey Co-authored-by: Samuel Newman --- modules/bottom-sheet/index.ts | 13 ++++++++-- .../bottom-sheet/ios/BottomSheetModule.swift | 11 ++++---- modules/bottom-sheet/ios/SheetView.swift | 23 +++++++++++++++-- modules/bottom-sheet/src/BottomSheet.types.ts | 10 ++++++++ src/components/BottomSheetButton.tsx | 0 src/components/Button.tsx | 12 +++++---- src/components/Dialog/context.ts | 4 ++- src/components/Dialog/index.tsx | 25 ++++++++++++++++--- src/components/Dialog/index.web.tsx | 3 ++- src/components/Dialog/types.ts | 4 ++- src/components/NormalizedRNGHPressable.tsx | 2 ++ 11 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 src/components/BottomSheetButton.tsx diff --git a/modules/bottom-sheet/index.ts b/modules/bottom-sheet/index.ts index ea0c87b83e..2eae5bfc2f 100644 --- a/modules/bottom-sheet/index.ts +++ b/modules/bottom-sheet/index.ts @@ -1,4 +1,13 @@ -import {BottomSheetState, BottomSheetViewProps} from './src/BottomSheet.types' +import { + BottomSheetSnapPoint, + BottomSheetState, + BottomSheetViewProps, +} from './src/BottomSheet.types' import {BottomSheetView} from './src/BottomSheetView' -export {type BottomSheetState, BottomSheetView, type BottomSheetViewProps} +export { + BottomSheetSnapPoint, + type BottomSheetState, + BottomSheetView, + type BottomSheetViewProps, +} diff --git a/modules/bottom-sheet/ios/BottomSheetModule.swift b/modules/bottom-sheet/ios/BottomSheetModule.swift index 2b4498adf7..bd878c5abc 100644 --- a/modules/bottom-sheet/ios/BottomSheetModule.swift +++ b/modules/bottom-sheet/ios/BottomSheetModule.swift @@ -10,8 +10,9 @@ public class BottomSheetModule: Module { View(SheetView.self) { Events([ + "onAttemptDismiss", + "onSnapPointChange", "onStateChange", - "onAttemptDismiss" ]) AsyncFunction("dismiss") { (view: SheetView) in @@ -26,10 +27,6 @@ public class BottomSheetModule: Module { view.cornerRadius = CGFloat(prop) } - Prop("preventDismiss") { (view: SheetView, prop: Bool) in - view.preventDismiss = prop - } - Prop("minHeight") { (view: SheetView, prop: Double) in view.minHeight = prop } @@ -38,6 +35,10 @@ public class BottomSheetModule: Module { view.maxHeight = prop } + Prop("preventDismiss") { (view: SheetView, prop: Bool) in + view.preventDismiss = prop + } + Prop("preventExpansion") { (view: SheetView, prop: Bool) in view.preventExpansion = prop } diff --git a/modules/bottom-sheet/ios/SheetView.swift b/modules/bottom-sheet/ios/SheetView.swift index ccf7fcb23d..2bfaa0a815 100644 --- a/modules/bottom-sheet/ios/SheetView.swift +++ b/modules/bottom-sheet/ios/SheetView.swift @@ -7,8 +7,9 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate { private var innerView: UIView? // Events - private let onStateChange = EventDispatcher() private let onAttemptDismiss = EventDispatcher() + private let onSnapPointChange = EventDispatcher() + private let onStateChange = EventDispatcher() // Open event firing private var isOpen: Bool = false { @@ -51,6 +52,19 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate { } } } + private var selectedDetentIdentifier: UISheetPresentationController.Detent.Identifier? { + didSet { + if selectedDetentIdentifier == .large { + onSnapPointChange([ + "snapPoint": 2 + ]) + } else { + onSnapPointChange([ + "snapPoint": 1 + ]) + } + } + } // MARK: - Lifecycle @@ -103,11 +117,12 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate { } let sheetVc = SheetViewController() + sheetVc.setDetents(contentHeight: self.clampHeight(contentHeight), preventExpansion: self.preventExpansion) if let sheet = sheetVc.sheetPresentationController { sheet.delegate = self sheet.preferredCornerRadius = self.cornerRadius + self.selectedDetentIdentifier = sheet.selectedDetentIdentifier } - sheetVc.setDetents(contentHeight: self.clampHeight(contentHeight), preventExpansion: self.preventExpansion) sheetVc.view.addSubview(innerView) self.sheetVc = sheetVc @@ -158,4 +173,8 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate { func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { self.destroy() } + + func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheetPresentationController: UISheetPresentationController) { + self.selectedDetentIdentifier = sheetPresentationController.selectedDetentIdentifier + } } diff --git a/modules/bottom-sheet/src/BottomSheet.types.ts b/modules/bottom-sheet/src/BottomSheet.types.ts index 8177df5787..e3cf64b997 100644 --- a/modules/bottom-sheet/src/BottomSheet.types.ts +++ b/modules/bottom-sheet/src/BottomSheet.types.ts @@ -3,6 +3,12 @@ import {ColorValue, NativeSyntheticEvent} from 'react-native' export type BottomSheetState = 'closed' | 'closing' | 'open' | 'opening' +export enum BottomSheetSnapPoint { + Hidden, + Partial, + Full, +} + export interface BottomSheetViewProps { children: React.ReactNode cornerRadius?: number @@ -15,6 +21,10 @@ export interface BottomSheetViewProps { minHeight?: number maxHeight?: number + onAttemptDismiss?: (event: NativeSyntheticEvent) => void + onSnapPointChange?: ( + event: NativeSyntheticEvent<{snapPoint: BottomSheetSnapPoint}>, + ) => void onStateChange?: ( event: NativeSyntheticEvent<{state: BottomSheetState}>, ) => void diff --git a/src/components/BottomSheetButton.tsx b/src/components/BottomSheetButton.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 0c7c1ac46d..a43bc552e1 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -122,11 +122,13 @@ export const Button = React.forwardRef( }, ref, ) => { - // This will pick the correct default pressable to use. If we are inside a dialog, we need to use the RNGH - // pressable so that it is usable inside the dialog. - const {insideDialog} = useDialogContext() + /* + * This will pick the correct default pressable to use. If we are inside a + * native dialog, we need to use the RNGH pressable. + */ + const {isNativeDialog} = useDialogContext() if (!PressableComponent) { - if (insideDialog) { + if (isNativeDialog) { PressableComponent = NormalizedRNGHPressable } else { PressableComponent = Pressable @@ -468,7 +470,7 @@ export const Button = React.forwardRef( role="button" accessibilityHint={undefined} // optional {...rest} - // @ts-expect-error ref type + // @ts-ignore - this will always be a pressable ref={ref} aria-label={label} aria-pressed={state.pressed} diff --git a/src/components/Dialog/context.ts b/src/components/Dialog/context.ts index 27b8a56375..6247b6d3d1 100644 --- a/src/components/Dialog/context.ts +++ b/src/components/Dialog/context.ts @@ -6,10 +6,12 @@ import { DialogControlRefProps, DialogOuterProps, } from '#/components/Dialog/types' +import {BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomSheet.types' export const Context = React.createContext({ close: () => {}, - insideDialog: false, + isNativeDialog: false, + nativeSnapPoint: BottomSheetSnapPoint.Hidden, }) export function useDialogContext() { diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index 0695c11ebf..cb2e37ac7e 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -8,7 +8,7 @@ import {isIOS} from '#/platform/detection' import {useDialogStateControlContext} from '#/state/dialogs' import {List, ListMethods, ListProps} from '#/view/com/util/List' import {atoms as a, flatten, useTheme} from '#/alf' -import {Context} from '#/components/Dialog/context' +import {Context, useDialogContext} from '#/components/Dialog/context' import { DialogControlProps, DialogInnerProps, @@ -16,7 +16,10 @@ import { } from '#/components/Dialog/types' import {createInput} from '#/components/forms/TextField' import {Portal} from '#/components/Portal' -import {BottomSheetView} from '../../../modules/bottom-sheet' +import { + BottomSheetSnapPoint, + BottomSheetView, +} from '../../../modules/bottom-sheet' export {useDialogContext, useDialogControl} from '#/components/Dialog/context' export * from '#/components/Dialog/types' @@ -57,6 +60,10 @@ export function OuterWithoutPortal({ const closeCallbacks = React.useRef<(() => void)[]>([]) const {setDialogIsOpen} = useDialogStateControlContext() + const [snapPoint, setSnapPoint] = React.useState( + BottomSheetSnapPoint.Partial, + ) + const callQueuedCallbacks = React.useCallback(() => { for (const cb of closeCallbacks.current) { try { @@ -103,7 +110,10 @@ export function OuterWithoutPortal({ [open, close], ) - const context = React.useMemo(() => ({close, insideDialog: true}), [close]) + const context = React.useMemo( + () => ({close, isNativeDialog: true, nativeSnapPoint: snapPoint}), + [close, snapPoint], + ) const Wrapper = isIOS ? View : GestureHandlerRootView @@ -113,6 +123,9 @@ export function OuterWithoutPortal({ ref={ref} topInset={30} bottomInset={insets.bottom} + onSnapPointChange={e => { + setSnapPoint(e.nativeEvent.snapPoint) + }} onStateChange={e => { if (e.nativeEvent.state === 'closed') { onCloseAnimationComplete() @@ -147,8 +160,12 @@ export function Inner({children, style}: DialogInnerProps) { export const ScrollableInner = React.forwardRef( function ScrollableInner({children, style}, ref) { const insets = useSafeAreaInsets() + const {nativeSnapPoint} = useDialogContext() return ( - + {children} diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx index a50e960a8a..3d860f112b 100644 --- a/src/components/Dialog/index.web.tsx +++ b/src/components/Dialog/index.web.tsx @@ -103,7 +103,8 @@ export function Outer({ const context = React.useMemo( () => ({ close, - insideDialog: true, + isNativeDialog: false, + nativeSnapPoint: 0, }), [close], ) diff --git a/src/components/Dialog/types.ts b/src/components/Dialog/types.ts index c5219e5eab..2fd4d7c05a 100644 --- a/src/components/Dialog/types.ts +++ b/src/components/Dialog/types.ts @@ -7,6 +7,7 @@ import type { import {ViewStyleProp} from '#/alf' import {BottomSheetViewProps} from '../../../modules/bottom-sheet' +import {BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomSheet.types' type A11yProps = Required @@ -37,7 +38,8 @@ export type DialogControlProps = DialogControlRefProps & { export type DialogContextProps = { close: DialogControlProps['close'] - insideDialog: boolean + isNativeDialog: boolean + nativeSnapPoint: BottomSheetSnapPoint } export type DialogControlOpenOptions = { diff --git a/src/components/NormalizedRNGHPressable.tsx b/src/components/NormalizedRNGHPressable.tsx index 351005619d..2cf2249fc6 100644 --- a/src/components/NormalizedRNGHPressable.tsx +++ b/src/components/NormalizedRNGHPressable.tsx @@ -115,6 +115,8 @@ export class NormalizedRNGHPressable extends React.Component { render() { return (