[Sheets] [Pt. 12] Add event for selected snap point change (#5584)

Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
Hailey
2024-10-02 21:49:49 -07:00
committed by GitHub
parent 79996ac952
commit 14e3567663
11 changed files with 86 additions and 21 deletions
+11 -2
View File
@@ -1,4 +1,13 @@
import {BottomSheetState, BottomSheetViewProps} from './src/BottomSheet.types' import {
BottomSheetSnapPoint,
BottomSheetState,
BottomSheetViewProps,
} from './src/BottomSheet.types'
import {BottomSheetView} from './src/BottomSheetView' import {BottomSheetView} from './src/BottomSheetView'
export {type BottomSheetState, BottomSheetView, type BottomSheetViewProps} export {
BottomSheetSnapPoint,
type BottomSheetState,
BottomSheetView,
type BottomSheetViewProps,
}
@@ -10,8 +10,9 @@ public class BottomSheetModule: Module {
View(SheetView.self) { View(SheetView.self) {
Events([ Events([
"onAttemptDismiss",
"onSnapPointChange",
"onStateChange", "onStateChange",
"onAttemptDismiss"
]) ])
AsyncFunction("dismiss") { (view: SheetView) in AsyncFunction("dismiss") { (view: SheetView) in
@@ -26,10 +27,6 @@ public class BottomSheetModule: Module {
view.cornerRadius = CGFloat(prop) view.cornerRadius = CGFloat(prop)
} }
Prop("preventDismiss") { (view: SheetView, prop: Bool) in
view.preventDismiss = prop
}
Prop("minHeight") { (view: SheetView, prop: Double) in Prop("minHeight") { (view: SheetView, prop: Double) in
view.minHeight = prop view.minHeight = prop
} }
@@ -38,6 +35,10 @@ public class BottomSheetModule: Module {
view.maxHeight = prop view.maxHeight = prop
} }
Prop("preventDismiss") { (view: SheetView, prop: Bool) in
view.preventDismiss = prop
}
Prop("preventExpansion") { (view: SheetView, prop: Bool) in Prop("preventExpansion") { (view: SheetView, prop: Bool) in
view.preventExpansion = prop view.preventExpansion = prop
} }
+21 -2
View File
@@ -7,8 +7,9 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate {
private var innerView: UIView? private var innerView: UIView?
// Events // Events
private let onStateChange = EventDispatcher()
private let onAttemptDismiss = EventDispatcher() private let onAttemptDismiss = EventDispatcher()
private let onSnapPointChange = EventDispatcher()
private let onStateChange = EventDispatcher()
// Open event firing // Open event firing
private var isOpen: Bool = false { 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 // MARK: - Lifecycle
@@ -103,11 +117,12 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate {
} }
let sheetVc = SheetViewController() let sheetVc = SheetViewController()
sheetVc.setDetents(contentHeight: self.clampHeight(contentHeight), preventExpansion: self.preventExpansion)
if let sheet = sheetVc.sheetPresentationController { if let sheet = sheetVc.sheetPresentationController {
sheet.delegate = self sheet.delegate = self
sheet.preferredCornerRadius = self.cornerRadius sheet.preferredCornerRadius = self.cornerRadius
self.selectedDetentIdentifier = sheet.selectedDetentIdentifier
} }
sheetVc.setDetents(contentHeight: self.clampHeight(contentHeight), preventExpansion: self.preventExpansion)
sheetVc.view.addSubview(innerView) sheetVc.view.addSubview(innerView)
self.sheetVc = sheetVc self.sheetVc = sheetVc
@@ -158,4 +173,8 @@ class SheetView: ExpoView, UISheetPresentationControllerDelegate {
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
self.destroy() self.destroy()
} }
func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheetPresentationController: UISheetPresentationController) {
self.selectedDetentIdentifier = sheetPresentationController.selectedDetentIdentifier
}
} }
@@ -3,6 +3,12 @@ import {ColorValue, NativeSyntheticEvent} from 'react-native'
export type BottomSheetState = 'closed' | 'closing' | 'open' | 'opening' export type BottomSheetState = 'closed' | 'closing' | 'open' | 'opening'
export enum BottomSheetSnapPoint {
Hidden,
Partial,
Full,
}
export interface BottomSheetViewProps { export interface BottomSheetViewProps {
children: React.ReactNode children: React.ReactNode
cornerRadius?: number cornerRadius?: number
@@ -15,6 +21,10 @@ export interface BottomSheetViewProps {
minHeight?: number minHeight?: number
maxHeight?: number maxHeight?: number
onAttemptDismiss?: (event: NativeSyntheticEvent<object>) => void
onSnapPointChange?: (
event: NativeSyntheticEvent<{snapPoint: BottomSheetSnapPoint}>,
) => void
onStateChange?: ( onStateChange?: (
event: NativeSyntheticEvent<{state: BottomSheetState}>, event: NativeSyntheticEvent<{state: BottomSheetState}>,
) => void ) => void
+7 -5
View File
@@ -122,11 +122,13 @@ export const Button = React.forwardRef<View, ButtonProps>(
}, },
ref, 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. * This will pick the correct default pressable to use. If we are inside a
const {insideDialog} = useDialogContext() * native dialog, we need to use the RNGH pressable.
*/
const {isNativeDialog} = useDialogContext()
if (!PressableComponent) { if (!PressableComponent) {
if (insideDialog) { if (isNativeDialog) {
PressableComponent = NormalizedRNGHPressable PressableComponent = NormalizedRNGHPressable
} else { } else {
PressableComponent = Pressable PressableComponent = Pressable
@@ -468,7 +470,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
role="button" role="button"
accessibilityHint={undefined} // optional accessibilityHint={undefined} // optional
{...rest} {...rest}
// @ts-expect-error ref type // @ts-ignore - this will always be a pressable
ref={ref} ref={ref}
aria-label={label} aria-label={label}
aria-pressed={state.pressed} aria-pressed={state.pressed}
+3 -1
View File
@@ -6,10 +6,12 @@ import {
DialogControlRefProps, DialogControlRefProps,
DialogOuterProps, DialogOuterProps,
} from '#/components/Dialog/types' } from '#/components/Dialog/types'
import {BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomSheet.types'
export const Context = React.createContext<DialogContextProps>({ export const Context = React.createContext<DialogContextProps>({
close: () => {}, close: () => {},
insideDialog: false, isNativeDialog: false,
nativeSnapPoint: BottomSheetSnapPoint.Hidden,
}) })
export function useDialogContext() { export function useDialogContext() {
+21 -4
View File
@@ -8,7 +8,7 @@ import {isIOS} from '#/platform/detection'
import {useDialogStateControlContext} from '#/state/dialogs' import {useDialogStateControlContext} from '#/state/dialogs'
import {List, ListMethods, ListProps} from '#/view/com/util/List' import {List, ListMethods, ListProps} from '#/view/com/util/List'
import {atoms as a, flatten, useTheme} from '#/alf' import {atoms as a, flatten, useTheme} from '#/alf'
import {Context} from '#/components/Dialog/context' import {Context, useDialogContext} from '#/components/Dialog/context'
import { import {
DialogControlProps, DialogControlProps,
DialogInnerProps, DialogInnerProps,
@@ -16,7 +16,10 @@ import {
} from '#/components/Dialog/types' } from '#/components/Dialog/types'
import {createInput} from '#/components/forms/TextField' import {createInput} from '#/components/forms/TextField'
import {Portal} from '#/components/Portal' 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 {useDialogContext, useDialogControl} from '#/components/Dialog/context'
export * from '#/components/Dialog/types' export * from '#/components/Dialog/types'
@@ -57,6 +60,10 @@ export function OuterWithoutPortal({
const closeCallbacks = React.useRef<(() => void)[]>([]) const closeCallbacks = React.useRef<(() => void)[]>([])
const {setDialogIsOpen} = useDialogStateControlContext() const {setDialogIsOpen} = useDialogStateControlContext()
const [snapPoint, setSnapPoint] = React.useState<BottomSheetSnapPoint>(
BottomSheetSnapPoint.Partial,
)
const callQueuedCallbacks = React.useCallback(() => { const callQueuedCallbacks = React.useCallback(() => {
for (const cb of closeCallbacks.current) { for (const cb of closeCallbacks.current) {
try { try {
@@ -103,7 +110,10 @@ export function OuterWithoutPortal({
[open, close], [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 const Wrapper = isIOS ? View : GestureHandlerRootView
@@ -113,6 +123,9 @@ export function OuterWithoutPortal({
ref={ref} ref={ref}
topInset={30} topInset={30}
bottomInset={insets.bottom} bottomInset={insets.bottom}
onSnapPointChange={e => {
setSnapPoint(e.nativeEvent.snapPoint)
}}
onStateChange={e => { onStateChange={e => {
if (e.nativeEvent.state === 'closed') { if (e.nativeEvent.state === 'closed') {
onCloseAnimationComplete() onCloseAnimationComplete()
@@ -147,8 +160,12 @@ export function Inner({children, style}: DialogInnerProps) {
export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>( export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
function ScrollableInner({children, style}, ref) { function ScrollableInner({children, style}, ref) {
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const {nativeSnapPoint} = useDialogContext()
return ( return (
<ScrollView style={[a.px_xl, style]} ref={ref}> <ScrollView
style={[a.px_xl, style]}
ref={ref}
bounces={nativeSnapPoint === BottomSheetSnapPoint.Full}>
{children} {children}
<View style={{height: insets.bottom + a.pt_5xl.paddingTop}} /> <View style={{height: insets.bottom + a.pt_5xl.paddingTop}} />
</ScrollView> </ScrollView>
+2 -1
View File
@@ -103,7 +103,8 @@ export function Outer({
const context = React.useMemo( const context = React.useMemo(
() => ({ () => ({
close, close,
insideDialog: true, isNativeDialog: false,
nativeSnapPoint: 0,
}), }),
[close], [close],
) )
+3 -1
View File
@@ -7,6 +7,7 @@ import type {
import {ViewStyleProp} from '#/alf' import {ViewStyleProp} from '#/alf'
import {BottomSheetViewProps} from '../../../modules/bottom-sheet' import {BottomSheetViewProps} from '../../../modules/bottom-sheet'
import {BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomSheet.types'
type A11yProps = Required<AccessibilityProps> type A11yProps = Required<AccessibilityProps>
@@ -37,7 +38,8 @@ export type DialogControlProps = DialogControlRefProps & {
export type DialogContextProps = { export type DialogContextProps = {
close: DialogControlProps['close'] close: DialogControlProps['close']
insideDialog: boolean isNativeDialog: boolean
nativeSnapPoint: BottomSheetSnapPoint
} }
export type DialogControlOpenOptions = { export type DialogControlOpenOptions = {
@@ -115,6 +115,8 @@ export class NormalizedRNGHPressable extends React.Component<PressableProps> {
render() { render() {
return ( return (
<BSPressable <BSPressable
accessible={true}
accessibilityRole="button"
{...this.props} {...this.props}
onPress={this.onPress} onPress={this.onPress}
onLongPress={this.onLongPress} onLongPress={this.onLongPress}