@justkd/rn-navigator
v0.2.8
Published
React Native navigation with stateful transition animations leveraging predefined backgrounds or background colors.
Downloads
6
Maintainers
Readme
@justkd/rn-navigator
React Native navigation with stateful transition animations leveraging predefined backgrounds or background colors.
Each route is just a route key you assign to a given component. These should each be top level components for the intended route.
Example
const {
navigate,
to,
back
} = useNavigation()
navigate(to['/Home])
navigate(back)
const {
navigate,
navigator,
to,
bg
} = useNavigation()
navigate(to.Last, {
background: bg.one,
payload: { data: [] }
})
console.log( navigator.payload() )
TODO
- tests 😒
- other animation options
- skip animation
- refactor for less setup
- add docs for middleware
- add docs for navigator children
Install
npm i @justkd/rn-navigator
yarn add @justkd/rn-navigator
Setup
There are a few steps to set up @justkd/rn-navigator
in your project. It doesn't matter what you name the following files or how you organize them.
(required) Navigation.keys.ts
import {
getTypedRouteKeys,
type GetTypedRouteKeys
} from '@justkd/rn-navigator'
const routeKeys = [
'/Home',
'/Last'
] as const
export const { navigationRouteKeys } =
getTypedRouteKeys<typeof routeKeys>(routeKeys)
export type NavigationRouteKey = keyof GetTypedRouteKeys<
typeof navigationRouteKeys
>
// Import the relevant getters and types from the package.
import {
getTypedRouteKeys,
type GetTypedRouteKeys
} from '@justkd/rn-navigator'
// All route keys will need to be declared here first.
// Also, it's important to cast the object type `as const`.
const routeKeys = [
'/Home',
'/Last'
] as const
// Ensure the navigator knows the typed keys and export
// the returned, deconstructed value `navigationRouteKeys`.
export const { navigationRouteKeys } =
getTypedRouteKeys<typeof routeKeys>(routeKeys)
// Export the route key type. The variable name doesn't matter.
export type NavigationRouteKey = keyof GetTypedRouteKeys<
typeof navigationRouteKeys
>
(required) Navigation.routes.ts
import { type ComponentType } from 'react'
import { type NavigationRouteKey } from './Navigation.keys'
import { getTypedRoutes } from '@justkd/rn-navigator'
import { Home } from './routes/Home'
import { Last } from './routes/Last'
const routes: Record<NavigationRouteKey, ComponentType> = {
'/Home': Home,
'/Last': Last,
}
export const { navigationRoutes } =
getTypedRoutes<typeof routes>(routes)
// Import the relevant getters and types.
import { type ComponentType } from 'react'
import { type NavigationRouteKey } from './Navigation.keys'
import { getTypedRoutes } from '@justkd/rn-navigator'
// Import the relevant top level components to represent each route.
import { Home } from './routes/Home'
import { Last } from './routes/Last'
// Enforce record type and map route keys to route components.
const routes: Record<NavigationRouteKey, ComponentType> = {
'/Home': Home,
'/Last': Last,
}
// Export the returned, deconstructed value `navigationRoutes`.
export const { navigationRoutes } =
getTypedRoutes<typeof routes>(routes)
(optional) Navigation.backgrounds.ts
Backgrounds are optional. A default color and/or image can also be declared as a controller prop.
import {
getTypedBackgrounds,
type NavigationBackground
} from '@justkd/rn-navigator'
import ImageOne from './assets/1.jpg'
import ImageTwo from './assets/2.jpg'
const backgrounds: Record<string, NavigationBackground> = {
one: { image: { source: ImageOne } },
two: { image: { source: ImageTwo } },
black: { color: 'black' },
white: { color: 'white' },
combined: {
color: 'purple',
image: {
resizeMode: 'contain',
source: ImageSix,
style: { marginLeft: '5%' },
},
},
} as const
export const {
navigationBackgrounds,
navigationBackgroundKeys,
} = getTypedBackgrounds(backgrounds)
// Import the relevant getters and types.
import { getTypedBackgrounds, type NavigationBackground } from '@justkd/rn-navigator'
// Import image assets that you want to use as backgrounds during animated transitions.
import ImageOne from './assets/1.jpg'
import ImageTwo from './assets/2.jpg'
// Enforce record type and map transition backgrounds to background keys.
// The keys are arbitrary. This should be cast to `as const` as well.
const backgrounds: Record<string, NavigationBackground> = {
one: { image: { source: ImageOne } },
two: { image: { source: ImageTwo } },
black: { color: 'black' },
white: { color: 'white' },
combined: {
color: 'purple',
image: {
resizeMode: 'contain',
source: ImageSix,
style: { marginLeft: '5%' },
},
},
} as const
// Export the returned, deconstructed values `navigationBackgrounds` and `navigationBackgroundKeys`.
export const {
navigationBackgrounds,
navigationBackgroundKeys,
} = getTypedBackgrounds(backgrounds)
(required) Navigation.ts
import { getNavigationController } from '@justkd/rn-navigator'
import { navigationBackgrounds } from './Navigation.backgrounds'
import { navigationRouteKeys } from './Navigation.keys'
export const { NavigationController, useNavigation } =
getNavigationController<
typeof navigationRouteKeys,
typeof navigationBackgrounds
>({
onNavigation: (state) => {
console.log('navigate', state)
},
})
// Import the relevant getters and types.
import { getNavigationController } from '@justkd/rn-navigator'
import { navigationBackgrounds } from './Navigation.backgrounds'
import { navigationRouteKeys } from './Navigation.keys'
// Export the returned, deconstructed values `NavigationController` and `useNavigation`.
export const { NavigationController, useNavigation } =
getNavigationController<
typeof navigationRouteKeys,
typeof navigationBackgrounds
>({
onNavigation: (state) => {
console.log('navigate', state)
},
})
Use
// Import the values you generated in the other files.
import {
NavigationController,
navigationRoutes,
navigationBackgrounds,
} from './Navigation'
// Default background image and color are optional
// import DefaultBackgroundImage from './assets/DefaultBackgroundImage.jpg'
/* =^..^= ✿ =^..^= */
export function App() {
return (
<NavigationController
initialRoute="/Home"
routes={navigationRoutes}
backgrounds={navigationBackgrounds}
// backgroundColor="black"
// backgroundImage={DefaultBackgroundImage}
/>
)
}
// Import the `useNavigation` hook from wherever you generated it earlier.
import { useNavigation } from 'your_path_to/Your_Navigation_File'
export function Home() {
type PayloadType = { data: [] }
const { navigate, to, bg, back, navigator } = useNavigation()
const onPress = () => {
navigate<PayloadType>(
to['/A'],
{
background: bg['one'],
payload: { data: []
}
})
// navigate( back )
// console.log( navigator.peek() )
}
return (
<View style={{ flex: 1, justifyContent: 'center' }}>
<Pressable onPress={onPress}>
<Text>{'Navigate'}</Text>
</Pressable>
</View>
)
}
API
/**
* Type representing the props for the `NavigationController` component.
*/
export interface NavigationControllerProps<
R extends string | number | symbol,
B,
> {
/**
* Provide the `navigationRoutes` object generated by `getTypedRoutes`.
*/
routes: Record<R, ComponentType>
/**
* The initial route to show. This will also be the
* target route when using the `reset` function.
*/
initialRoute: R
/**
* Default background color.
*/
backgroundColor?: ViewStyle['backgroundColor']
/**
* Default background image.
*/
backgroundImage?: ImageSourcePropType
/**
* Provide the `navigationBackgrounds` object generated by `getTypedBackgrounds`.
*/
backgrounds?: B
/**
* If `true`, will error if the controller component ever unmounts.
* It probably shouldn't ever unmount if it's meant to be at the top level.
* But you can also use the controlelr to encapsulate sub navigators and routes.
*/
topLevelController?: boolean
}
/**
* Type representing the values returned by the `useNavigation` hook.
*/
export type UseNavigationReturnType<
R extends string | number | symbol,
B extends string | number | symbol,
> = {
/**
* Navigates to the given route with event options and animated transitions.
*/
navigate: NavigateFn<R>
/**
* Object exposing typed route keys for navigation.
*/
to: Record<R, R>
/**
* Object exposing typed background keys for navigation.
*/
bg: Record<B, B>
/**
* Use as the navigation route target when navigating back in the history stack.
*/
back: BackToken
/**
* Object holding utility methods for the navigator.
*/
navigator: {
/**
* Return a frozen copy of the current state.
* This is just for looking.
* @example
* import { useNavigation } from './Navigation'
* const { navigator } = useNavigation()
* console.log( navigator.peek() )
*/
peek: () => NavigationState
/**
* Reset the navigator. Animates navigation back to the
* `initialRoute`. A background can be provided as a param.
* @example
* import { useNavigation } from './Navigation'
* const { navigator, bg } = useNavigation()
* navigator.reset(bg.reset)
*/
reset: (background?: string) => void
/**
* If no param is provided, retrieve the payload for the current route.
* If an index is provided, retrieve the relevant payload from the
* navigation history stack.
* @example
* import { useNavigation } from './Navigation'
* const { navigator } = useNavigation()
* console.log( navigator.payload() )
* console.log( navigator.payload(1) ) // only if there is history to check
*/
payload: <T extends GenericObj>(n?: number) => T | null
/**
* If no param is provided, retrieve the name for the current route.
* If an index is provided, retrieve the relevant route name from the
* navigation history stack.
* @example
* import { useNavigation } from 'your_path_to/Your_Navigation_File'
* const { navigator } = useNavigation()
* console.log( navigator.route() )
* console.log( navigator.route(1) ) // only if there is history to check
*/
route: (index?: number) => string | null
/**
* !!! You probably don't want to use this. But it's here just in case. !!!
* Set the navigation state directly.
* @example
* import { useNavigation, type NavigationState } from '@justkd/rn-navigator'
* const { navigator } = useNavigation()
* const nextState: NavigationState = {
* queue: [{ to: '/Home' }],
* history: []
* }
* console.log( navigator.set( nextState ) )
*/
set: (next: Partial<NavigationState>) => void
}
}
/**
* Used to ensure the user generated route keys type does not include
* the internal back token. The back token is still accepted by
* the navigate fn, and the route keys will be strongly typed.
*/
export type GetTypedRouteKeys<T> = Omit<T, InternalBackTokenType>
/**
* Represents a background object when defining navigation backgrounds.
*/
export type NavigationBackground = {
color?: ViewStyle['backgroundColor']
image?: {
source: ImageSourcePropType
resizeMode?: ImageBackgroundProps['resizeMode']
style?: ImageBackgroundProps['style']
}
}
/**
* Type representing the values stored in the navigation queue and history.
*/
export type NavigationEvent = {
to: string
payload?: Record<string, any>
background?: string
}
/**
* Type representing the internal navigation state.
*/
export type NavigationState = {
queue: NavigationEvent[]
history: NavigationEvent[]
isNavigating:
| null
| 'fwd'
| 'back'
| 'error'
background?: string
}
/**
* Uses the values returned from the other getters and returns
* a strongly typed object holding all navigation routes.
*/
export const getTypedRoutes = <T extends Record<any, any>>(
routes: T,
) => { navigationRoutes: T }
/**
* Ensures the background key helper is strongly typed.
*/
export const getTypedBackgrounds = <T extends Record<any, any>>(
backgrounds: T,
) => {
navigationBackgroundKeys: Record<keyof T, keyof T>,
navigationBackgrounds: T,
}
/**
* Adds the internal back token to the user route keys.
* The back token won't be included in the exposed type,
* but the navigate fn will still accept it from the `back`
* helper returned by `useNavigation`. The route keys will
* also be strongly typed.
*/
export const getTypedRouteKeys = <T extends readonly string[]>(
arr: T,
) => {
navigationRouteKeys: Record<
T[number],
T[number]
> & InternalBackTokenType
}
/**
* Returns a function component representing the `NavigationController`
* and a `useNavigation` hook specific to that controller.
*/
export function getNavigationController<
R,
B = any,
>(): {
useNavigation: UseNavigationReturnType<
keyof R,
keyof B
>,
NavigationController: (
props: NavigationControllerProps<
Exclude<keyof R, InternalBackTokenType>,
BackgroundGeneric
>
): JSX.Element
}