npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@justkd/rn-navigator

v0.2.8

Published

React Native navigation with stateful transition animations leveraging predefined backgrounds or background colors.

Downloads

6

Readme

@justkd/rn-navigator

CircleCI

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)

image

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
}