[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:
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user