react-native-modalfy
Version:
Modal citizen of React Native
492 lines (456 loc) • 18.6 kB
text/typescript
import type { ComponentType } from 'react'
import type { Animated, ViewStyle } from 'react-native'
/* ======================== ========================
*
* ======================== INTERNAL TYPES ========================
*
* ======================== ========================
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ModalfyCustomParams {}
type ModalfyExtendedParams = ModalfyCustomParams[keyof ModalfyCustomParams] extends never
? { [key: string]: any }
: ModalfyCustomParams
// It should be declared as interface to prevent typescript type replacement
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ModalfyParams extends ModalfyExtendedParams {}
export type ModalTransitionValue = Animated.AnimatedInterpolation | string | number | undefined | null
export type ModalTransitionOptions = (animatedValue: Animated.Value) => {
[key: string]:
| {
[key: string]: ModalTransitionValue
}[]
| ModalTransitionValue
}
export type ModalListener = (eventName: ModalEventName, callback: ModalEventCallback) => ModalEventListener
export type ModalEventListeners = Set<{
event: string
handler: ModalEventCallback
}>
export type ModalEventName = 'onAnimate' | 'onClose'
export type ModalOnAnimateEventCallback = (value?: number) => void
export type ModalClosingAction = {
type: ModalClosingActionName
origin: ModalClosingActionOrigin
}
export type ModalClosingActionOrigin = 'default' | 'fling' | 'backdrop'
export type ModalClosingActionName = 'closeModal' | 'closeModals' | 'closeAllModals'
export type ModalOnCloseEventCallback = (closingAction: ModalClosingAction) => void
export type ModalEventCallback = ModalOnAnimateEventCallback | ModalOnCloseEventCallback
export type ModalEventListener = { remove: () => boolean }
export type ModalEventAction = 'add'
export type ModalEventPayload = {
eventName: ModalEventName
handler: ModalEventCallback
}
export type ModalStatePendingClosingAction =
| {
modalName?: string
action: 'closeModal'
callback?: () => void
}
| {
modalName: string
action: 'closeModals'
callback?: () => void
}
| {
modalName?: never
action: 'closeAllModals'
callback?: () => void
}
export type ModalPendingClosingAction =
| {
hash: string
currentModalHash?: string
modalName?: string
action: 'closeModal'
callback?: () => void
}
| {
hash: string
currentModalHash?: string
modalName: string
action: 'closeModals'
callback?: () => void
}
| {
hash: string
modalName: never
currentModalHash?: string
action: 'closeAllModals'
callback?: () => void
}
export interface ModalStackItem<P extends ModalfyParams> {
name: Exclude<keyof P, symbol | number>
component: ComponentType<any> & { modalOptions?: ModalOptions }
hash: string
index: number
options?: ModalOptions
params?: any
callback?: () => void
}
export interface ModalStack<P extends ModalfyParams> {
names: Array<Exclude<keyof P, symbol | number>>
content: ModalStackItem<P>[]
defaultOptions: ModalOptions
openedItems: Set<ModalStackItem<P>>
pendingClosingActions: Set<ModalPendingClosingAction>
}
export interface ModalContextProvider<
P extends ModalfyParams,
M extends Exclude<keyof P, symbol | number> = Exclude<keyof P, symbol | number>,
> {
currentModal: M | null
closeAllModals: (callback?: () => void) => void
closeModal: (stackItem?: M | ModalStackItem<P>, callback?: () => void) => void
closeModals: (modalName: M, callback?: () => void) => boolean
getParam: <N extends keyof P[M], D extends P[M][N]>(
hash: ModalStackItem<P>['hash'],
paramName: N,
defaultValue?: D,
) => D extends P[M][N] ? P[M][N] : undefined
openModal: <N extends M>(modalName: N, params?: P[N], callback?: () => void) => void
stack: ModalStack<P>
}
export type ModalInternalState<P extends ModalfyParams> = {
currentModal: ModalContextProvider<P>['currentModal'] | string | null
stack: ModalContextProvider<P>['stack']
}
export interface ModalStateSubscriber<P extends ModalfyParams> {
state: ModalInternalState<P>
equalityFn: ModalStateEqualityChecker<P>
error: boolean
stateListener: ModalStateListener<P>
unsubscribe: () => boolean
}
export interface ModalStateSubscription<P extends ModalfyParams> {
unsubscribe: ModalStateSubscriber<P>['unsubscribe']
}
export type ModalStateListener<P extends ModalfyParams> = (state: ModalInternalState<P> | null, error?: Error) => void
export type ModalStateEqualityChecker<P extends ModalfyParams> = (
currentState: ModalInternalState<P>,
newState: ModalInternalState<P>,
) => boolean
export type ModalState<P extends ModalfyParams> = Omit<
ModalContextProvider<P>,
'currentModal' | 'stack' | 'openModal'
> & {
openModal: <M extends Exclude<keyof P, symbol | number>, N extends M>(args: {
modalName: N
params?: P[N]
isCalledOutsideOfContext?: boolean
callback?: () => void
}) => void
handleBackPress: () => boolean
init: <T extends ModalfyParams>(
updater: (currentState: ModalInternalState<T>) => ModalInternalState<T>,
) => ModalInternalState<T>
getState: <T extends ModalfyParams>() => ModalInternalState<T>
setState: <T extends ModalfyParams>(
updater: (currentState: ModalInternalState<T>) => ModalInternalState<T>,
) => ModalInternalState<T>
subscribe: <T extends ModalfyParams>(
listener: ModalStateListener<T>,
equalityFn?: ModalStateEqualityChecker<T>,
) => ModalStateSubscription<T>
queueClosingAction: (action: ModalStatePendingClosingAction) => ModalPendingClosingAction | null
removeClosingAction: (action: ModalPendingClosingAction) => boolean
}
export interface SharedProps<P extends ModalfyParams> extends ModalContextProvider<P> {
clearListeners: (hash: string) => void
eventListeners: ModalEventListeners
registerListener: (
hash: ModalStackItem<P>['hash'],
eventName: ModalEventName,
handler: ModalEventCallback,
) => ModalEventListener
removeClosingAction: ModalState<P>['removeClosingAction']
}
export type UsableModalProp<P extends ModalfyParams> = Pick<
ModalContextProvider<P>,
'closeAllModals' | 'closeModals' | 'currentModal' | 'openModal'
> & {
closeModal: (modalName?: Exclude<keyof P, symbol | number>, callback?: () => void) => void
}
export interface UsableModalComponentProp<P extends ModalfyParams, M extends keyof P>
extends Omit<ModalContextProvider<P>, 'closeModal' | 'stack' | 'getParam'> {
addListener: ModalListener
closeModal: (modalName?: M, callback?: () => void) => void
getParam: <N extends keyof P[M], D extends P[M][N]>(
paramName: N,
defaultValue?: D,
) => D extends P[M][N] ? P[M][N] : undefined
removeAllListeners: () => void
params?: P[M]
}
/* ======================== ========================
*
* ======================== CONSUMER TYPES ========================
*
* ======================== ========================
*/
/**
* Interface of the modal stack configuration.
* These settings will let Modalfy know what modals you will be rendering and how.
*
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/guides/typing#config-and-options).
*/
export interface ModalStackConfig {
[key: string]: ComponentType<any> | ModalOptions
}
/**
* Interface of the modal configuration options.
* These settings will let Modalfy how to render and animate a modal.
*
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/guides/typing#config-and-options).
*/
export interface ModalOptions {
/**
* Animation configuration used to animate a modal in, at the top of the stack.
* It uses Animated.timing() by default, if you want to use another animation type, see `animationIn`.
*
* Note: only `easing` and `duration` are needed.
*
* @default { easing: Easing.inOut(Easing.exp), duration: 450 }
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#animateinconfig).
*/
animateInConfig?: Pick<Animated.TimingAnimationConfig, 'duration' | 'easing'>
/**
* Animation function that receives the `animatedValue` used by the library to animate the modal opening,
* and a `toValue` argument representing the modal position in the stack.
*
* Since Modalfy v3, the function receives a `callback` argument, which _can_ be called when the animation is finished,
* depending on whether or not you'll have a callback in a `openModal()`.
*
* Note: If you just want to use Animated.timing(), check `animateInConfig`.
*
* @default -
* @example
* animationIn: (modalAnimatedValue, modalToValue, callback) => {
* Animated.parallel([
* Animated.timing(modalAnimatedValue, {
* toValue: modalToValue,
* duration: 300,
* easing: Easing.inOut(Easing.exp),
* useNativeDriver: true,
* }),
* Animated.timing(myOtherAnimatedValue, {
* toValue: 1,
* duration: 300,
* easing: Easing.inOut(Easing.exp),
* useNativeDriver: true,
* }),
* ]).start() => callback?.())
* }
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#animationin).
*/
animationIn?: (
animatedValue: Animated.Value,
toValue: number,
callback?: () => void,
) => Animated.CompositeAnimation | void
/**
* Animation configuration used to animate a modal out (underneath other modals or when closing the last one).
* Uses Animated.timing(), if you want to use another animation type, use `animationOut`.
*
* Note: only `easing` and `duration` are needed.
*
* @default { easing: Easing.inOut(Easing.exp), duration: 450 }
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#animationout).
*/
animateOutConfig?: Pick<Animated.TimingAnimationConfig, 'duration' | 'easing'>
/**
* Animation function that receives the `animatedValue` used by the library to animate the modal closing,
* and a `toValue` argument representing the modal position in the stack.
*
* Since Modalfy v3, the function receives a `callback` argument, which **_must_** be called when the animation is finished.
*
* Note: If you just want to use Animated.timing(), check `animateOutConfig`.
*
* @default -
* @example
* animationOut: (modalAnimatedValue, modalToValue, callback) => {
* Animated.parallel([
* Animated.timing(modalAnimatedValue, {
* toValue: modalToValue,
* duration: 300,
* easing: Easing.inOut(Easing.exp),
* useNativeDriver: true,
* }),
* Animated.timing(myOtherAnimatedValue, {
* toValue: 1,
* duration: 300,
* easing: Easing.inOut(Easing.exp),
* useNativeDriver: true,
* }),
* ]).start(() => callback())
* }
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#animationout).
*/
animationOut?: (
animatedValue: Animated.Value,
toValue: number,
callback?: () => void,
) => Animated.CompositeAnimation | void
/**
* How you want the modal stack to behave when users press the backdrop, but also when the physical back button is pressed on Android.
*
* @default 'pop'
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#backbehavior).
*/
backBehavior?: 'clear' | 'pop' | 'none'
/**
* Color of the modal stack backdrop.
*
* @default 'black'
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#backdropcolor).
*/
backdropColor?: ViewStyle['backgroundColor']
/**
* Number between `0` and `1` that defines the backdrop opacity.
*
* @default 0.6
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#backdropopacity).
*/
backdropOpacity?: number
/**
* Number that defines how long the backdrop should take to animate in and out.
*
* @default 300
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#backdropanimationduration).
*/
backdropAnimationDuration?: number
/**
* Styles applied to the `<View>` directly wrapping your modal component.
*
* @default '{}'
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#containerstyle).
*/
containerStyle?: ViewStyle
/**
* Disable fling gesture detection to close the modal.
*
* Note: the fling gesture handler is not enabled when `position` is `center`.
*
* @default false
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#disableflinggesture).
*/
disableFlingGesture?: boolean
/**
* React component that will be rendered when you'll open the modal.
*
* Note: only needed when you're using this inside createModalStack() 1st argument.
*
* @default -
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#modal).
*/
modal?: ComponentType<any>
/**
* Vertical positioning of the modal.
*
* @default 'center'
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#position).
*/
position?: 'center' | 'top' | 'bottom'
/**
* Styles applied to the `<Animated.View>` directly wrapping the entire modal stack & backdrop.
*
* The styles can be provided as a regular object or as a function (that will receive an `Animated.Value` representing the opacity of the modal stack as sole argument).
*
* Note: the object returned by `stackContainerStyle()` must contain keys that work with `useNativeDriver: true`.
*
* @default '{}'
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#stackcontainerstyle).
*/
stackContainerStyle?: ViewStyle | ((opacity: Animated.Value) => ViewStyle)
/**
* `transitionOptions(animatedValue)` returns a React Native style object containing values that can use the provided `animatedValue` to run animation interpolations on a modal.
*
* Note: the object returned by `transitionOptions()` must contain keys that work with `useNativeDriver: true`.
*
* @default -
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#transitionoptions).
*/
transitionOptions?: ModalTransitionOptions
/**
* How you want any modal to respond to a touch/click.
*
* @default 'auto'
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/api/types/modaloptions#pointereventsbehavior).
*/
pointerEventsBehavior?: 'auto' | 'none' | 'current-modal-only' | 'current-modal-none'
}
/**
* Interface of the `modal` prop exposed by the library to regular components.
*
* @argument { unknown } ModalStackParamsList? - Interface of the whole modal stack params.
* @argument { unknown } Props? - Component's props interface.
*
* Note: Modal components used in `createModalStack()`'s config should employ `ModalComponentProp` instead.
*
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/guides/typing#modalprop).
*/
export type ModalProp<P extends ModalfyParams, Props = unknown> = Props & {
/**
* Interface of the `modal` prop exposed by the library to regular components.
*
* Note: Modal components used in `createModalStack()`'s config should employ `ModalComponentProp` instead.
*
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/guides/typing#modalprop).
*/
modal: UsableModalProp<P>
}
/**
* Interface of the `modal` prop exposed by the library specifically to modal components.
*
* @argument { unknown } ModalStackParamsList? - Interface of the whole modal stack params.
* @argument { unknown } Props? - Component's props interface.
* @argument { string } ModalName? - Name of the current modal
*
* Note: Components that are not used from `createModalStack()`'s config should employ `ModalProp` instead.
*
* @related [`ModalProps`](https://colorfy-software.gitbook.io/react-native-modalfy/guides/typing#modalprops).
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/guides/typing#modalcomponentprop).
*/
export type ModalComponentProp<P extends ModalfyParams, Props = unknown, M extends keyof P = keyof P> = Props & {
/**
* Interface of the `modal` prop exposed by the library specifically to modal components.
*
* Note:
* * A simplified version of this interface is ModalProps.
* * Components that are not used from `createModalStack()`'s config should employ `ModalProp` instead.
*
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/guides/typing#modalcomponentprop).
*/
modal: UsableModalComponentProp<P, M>
}
/**
* Interface of the `modal` prop exposed by the library specifically to modal components.
*
* @argument { string } ModalName? - Name of the current modal
* @argument { unknown } Props? - Component's props interface.
*
* Note: Components that are not used from `createModalStack()`'s config should employ `ModalProp` instead.
*
* @related [`ModalComponentProp`](https://colorfy-software.gitbook.io/react-native-modalfy/guides/typing#modalcomponentprop).
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/guides/typing#modalprops).
*/
export type ModalProps<N extends keyof ModalfyParams = keyof ModalfyParams, P = void> = ModalComponentProp<
ModalfyParams,
P,
N
>
/**
* Interface for a React component containing its props and the `modalOptions` static property.
*
* Note: Only use with Hooks modal components (those present in your `createModalStack()`'s config).
* If you're working with a Class modal component, you can directly use `static modalOptions: ModalOptions`.
*
* @argument { unknown } Props? - Component's props interface.
*
* @see [API reference](https://colorfy-software.gitbook.io/react-native-modalfy/guides/typing#modalcomponentwithoptions).
*/
export type ModalComponentWithOptions<P = unknown> = ComponentType<P> & {
modalOptions?: ModalOptions
}