UNPKG

@crossed/sheet

Version:

A Cross Platform(Android & iOS) ActionSheet with a robust and flexible api, native performance and zero dependency code for react native. Create anything you want inside ActionSheet.

257 lines (238 loc) 6.92 kB
/** * Copyright (c) Paymium. * * This source code is licensed under the MIT license found in the * LICENSE file in the root of this projects source tree. */ import { createContext, useCallback, useContext, useState } from 'react'; import { Animated } from 'react-native'; import { Sheets, ActionSheetRef } from '../types'; export type RouteDefinition<T extends {} = {}> = T; export type Route< Key extends keyof Sheets = never, K extends keyof Sheets[Key]['routes'] = never, > = { /** * Name of the route. */ name: K | (string & {}); /** * A react component that will render when this route is navigated to. */ component: any; /** * Initial params for the route. */ params?: Sheets[Key]['routes'][K]; }; export type Router<Key extends keyof Sheets = never> = { currentRoute: Route<Key>; /** * Navigate to a route * * @param name Name of the route to navigate to * @param params Params to pass to the route upon navigation. These can be accessed in the route using `useSheetRouteParams` hook. * @param snap Snap value for navigation animation. Between -100 to 100. A positive value snaps inwards, while a negative value snaps outwards. */ navigate: <RouteKey extends keyof Sheets[Key]['routes']>( _name: RouteKey | (string & {}), _params?: Sheets[Key]['routes'][RouteKey] | any, _snap?: number ) => void; /** * Navigate back from a route. * * @param name Name of the route to navigate back to. * @param snap Snap value for navigation animation. Between -100 to 100. A positive value snaps inwards, while a negative value snaps outwards. */ goBack: <RouteKey extends keyof Sheets[Key]['routes']>( _name?: RouteKey | (string & {}), _snap?: number ) => void; /** * Close the action sheet. */ close: () => void; /** * Pop to top of the stack. */ popToTop: () => void; /** * Whether this router has any routes registered. */ hasRoutes: () => boolean | undefined; /** * Get the currently rendered stack. */ stack: Route<Key>[]; /** * An internal function called by sheet to navigate to initial route. */ initialNavigation: () => void; canGoBack: () => boolean; }; export const useRouter = ({ onNavigate, onNavigateBack, initialRoute, routes, getRef, routeOpacity, }: { initialRoute?: string; routes?: Route[]; getRef?: () => ActionSheetRef; onNavigate?: (_route: string) => void; onNavigateBack?: (_route: string) => void; routeOpacity: Animated.Value; }): Router => { const [stack, setStack] = useState<Route[]>([]); const currentRoute: Route | undefined = stack?.[stack.length - 1]; const animate = useCallback( (snap = 0, opacity = 0, delay = 0) => { getRef?.().snapToRelativeOffset(snap); Animated.timing(routeOpacity, { toValue: opacity, duration: 150, useNativeDriver: true, delay: delay, }).start(); }, [getRef, routeOpacity] ); const navigate = useCallback( (name: string, params?: any, snap?: number) => { animate(snap || 20, 0); setTimeout(() => { setStack((state: any) => { const next = routes?.find((route) => route.name === name); if (!next) { animate(0, 1); return state; } const currentIndex = state.findIndex( (route) => route.name === next.name ); if (currentIndex > -1) { const nextStack = [...state]; nextStack.splice(currentIndex, 1); return [...nextStack, { ...next, params: params || next.params }]; } onNavigate?.(next.name); animate(0, 1, 150); return [...state, { ...next, params: params || next.params }]; }); }, 100); }, [animate, routes, onNavigate] ); const initialNavigation = () => { if (!routes) return; if (initialRoute) { const route = routes?.find((rt) => rt.name === initialRoute); if (route) { setStack([route]); } } else { setStack([routes[0]]); } Animated.timing(routeOpacity, { toValue: 1, duration: 150, useNativeDriver: true, }).start(); }; const goBack = (name?: string, snap?: number) => { getRef?.().snapToRelativeOffset(snap || -10); animate(snap || -10, 0); setTimeout(() => { setStack((state) => { const next = routes?.find((route) => route.name === name); if (state.length === 1) { close(); animate(0, 1); return state; } if (!next) { const nextStack = [...state]; nextStack.pop(); if (currentRoute) { onNavigateBack?.(nextStack[nextStack.length - 1]?.name); animate(0, 1, 150); } return nextStack; } const currentIndex = stack.findIndex( (route) => route.name === next.name ); if (currentIndex > -1) { const nextStack = [...state]; nextStack.splice(currentIndex); onNavigateBack?.(nextStack[nextStack.length - 1]?.name); animate(0, 1, 150); return [...nextStack, next]; } animate(0, 1, 150); onNavigateBack?.(next.name); return [...stack, next]; }); }, 100); }; const close = () => { getRef?.()?.hide(); }; const popToTop = () => { if (!stack[0]) { return; } goBack(stack[0].name); }; const canGoBack = () => { return stack && stack.length > 1; }; return { currentRoute: currentRoute as unknown as Route, navigate: navigate as any, goBack: goBack as any, close, popToTop, hasRoutes: () => routes && routes.length > 0, stack, initialNavigation, canGoBack, }; }; export const RouterContext = createContext<Router | undefined>(undefined); /** * A hook that you can use to control the router. */ export function useSheetRouter<SheetId extends keyof Sheets>( _id?: SheetId | (string & {}) ): Router<SheetId> | undefined { return useContext(RouterContext); } export const RouterParamsContext = createContext<any>(undefined); /** * A hook that returns the params for current navigation route. */ export function useSheetRouteParams< SheetId extends keyof Sheets = never, RouteKey extends keyof Sheets[SheetId]['routes'] = never, >( _id?: SheetId | (string & {}), _routeKey?: RouteKey | (string & {}) ): Sheets[SheetId]['routes'][RouteKey] { const context = useContext(RouterParamsContext); return context; } export type RouteScreenProps< SheetId extends keyof Sheets = never, RouteKey extends keyof Sheets[SheetId]['routes'] = never, > = { router: Router<SheetId>; params: Sheets[SheetId]['routes'][RouteKey]; /** * @deprecated use `useSheetPayload` hook. */ payload: Sheets[SheetId]['beforeShowPayload']; };