Skip to content

Commit

Permalink
feat: export type helpers for components and hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
oedotme committed Mar 7, 2024
1 parent af2ae09 commit 255f4be
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 59 deletions.
23 changes: 6 additions & 17 deletions packages/react-router/src/client/components.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
import React from 'react'
import { generatePath, Link, LinkProps, Navigate } from 'react-router-dom'
import { generatePath, Link, Navigate } from 'react-router-dom'
import { LinkProps, To } from './types'

export const components = <Path extends string, Params extends Record<string, any>>() => {
type ParamPath = keyof Params
type To = { pathname: Path; search?: string; hash?: string }

type PropsWithParams<P extends Path | To> = LinkProps &
(P extends ParamPath
? { to: P; params: Params[P] }
: P extends To
? P['pathname'] extends ParamPath
? { to: P; params: Params[P['pathname']] }
: { to: P; params?: never }
: { to: P; params?: never })

return {
Link: <P extends Path | To>({ to, params, ...props }: PropsWithParams<P>) => {
const path = generatePath(typeof to === 'string' ? to : to.pathname, params || {})
Link: <P extends Path | To<Path>>({ to, params, ...props }: LinkProps<P, Params>) => {
const path = generatePath(typeof to === 'string' ? to : to.pathname, params || ({} as any))
return (
<Link {...props} to={typeof to === 'string' ? path : { pathname: path, search: to.search, hash: to.hash }} />
)
},
Navigate: <P extends Path | To>({ to, params, ...props }: PropsWithParams<P>) => {
const path = generatePath(typeof to === 'string' ? to : to.pathname, params || {})
Navigate: <P extends Path | To<Path>>({ to, params, ...props }: LinkProps<P, Params>) => {
const path = generatePath(typeof to === 'string' ? to : to.pathname, params || ({} as any))
return (
<Navigate
{...props}
Expand Down
38 changes: 15 additions & 23 deletions packages/react-router/src/client/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,28 @@
import { useCallback, useMemo } from 'react';
import { generatePath, NavigateOptions, useLocation, useNavigate, useParams } from 'react-router-dom'
import { useCallback, useMemo } from 'react'
import { generatePath, NavigateOptions as NavOptions, useLocation, useNavigate, useParams } from 'react-router-dom'
import { NavigateOptions, To } from './types'

export const hooks = <Path extends string, Params extends Record<string, any>, ModalPath extends string>() => {
type ParamPath = keyof Params
type To = { pathname: Path; search?: string; hash?: string }

type NavigateOptionsWithParams<P extends Path | To | number> = P extends number
? []
: P extends ParamPath
? [NavigateOptions & { params: Params[P] }]
: P extends To
? P['pathname'] extends ParamPath
? [NavigateOptions & { params: Params[P['pathname']] }]
: [NavigateOptions & { params?: never }]
: [NavigateOptions & { params?: never }] | []

return {
useParams: <P extends ParamPath>(path: P) => useParams<Params[typeof path]>() as Params[P],
useParams: <P extends keyof Params>(path: P) => useParams<Params[typeof path]>() as Params[P],
useNavigate: () => {
const navigate = useNavigate()
return useCallback(<P extends Path | To | number>(to: P, ...[options]: NavigateOptionsWithParams<P>) => {
if (typeof to === 'number') return navigate(to)
const path = generatePath(typeof to === 'string' ? to : to.pathname, options?.params || ({} as any))
navigate(typeof to === 'string' ? path : { pathname: path, search: to.search, hash: to.hash }, options)
}, [navigate])

return useCallback(
<P extends Path | To<Path> | number>(to: P, ...[options]: NavigateOptions<P, Params>) => {
if (typeof to === 'number') return navigate(to)
const path = generatePath(typeof to === 'string' ? to : to.pathname, options?.params || ({} as any))
return navigate(typeof to === 'string' ? path : { pathname: path, search: to.search, hash: to.hash }, options)
},
[navigate],
)
},
useModals: () => {
const location = useLocation()
const navigate = useNavigate()

type Options<P> = NavigateOptions &
(P extends ParamPath ? { at?: P; params: Params[P] } : { at?: P; params?: never })
type Options<P> = NavOptions &
(P extends keyof Params ? { at?: P; params: Params[P] } : { at?: P; params?: never })

return useMemo(() => {
return {
Expand Down
1 change: 1 addition & 0 deletions packages/react-router/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './components'
export * from './hooks'
export * from './types'
export * from './utils'
27 changes: 27 additions & 0 deletions packages/react-router/src/client/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { LinkProps as _LinkProps, NavigateOptions as NavOptions, NavigateProps as NavProps } from 'react-router-dom'

export type To<Pathname = string> = { pathname: Pathname; search?: string; hash?: string }

type ComponentProps<Path extends string | To, Params extends Record<string, any>> = Path extends keyof Params
? { to: Path; params: Params[Path] }
: Path extends { pathname: infer Pathname }
? Pathname extends keyof Params
? { to: To<Pathname>; params: Params[Pathname] }
: { to: To<Pathname>; params?: never }
: { to: Path; params?: never }

export type LinkProps<Path extends string | To, Params extends Record<string, any>> = Omit<_LinkProps, 'to'> &
ComponentProps<Path, Params>

export type NavigateProps<Path extends string | To, Params extends Record<string, any>> = Omit<NavProps, 'to'> &
ComponentProps<Path, Params>

export type NavigateOptions<Path extends string | To | number, Params extends Record<string, any>> = Path extends number
? []
: Path extends keyof Params
? [NavOptions & { params: Params[Path] }]
: Path extends { pathname: infer Pathname }
? Pathname extends keyof Params
? [NavOptions & { params: Params[Pathname] }]
: [NavOptions & { params?: never }] | []
: [NavOptions & { params?: never }] | []
4 changes: 1 addition & 3 deletions packages/react-router/src/client/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { generatePath, redirect } from 'react-router-dom'

export const utils = <Path extends string, Params extends Record<string, any>>() => {
type ParamPath = keyof Params

type Init = number | ResponseInit
type RedirectOptions<P> = P extends ParamPath ? [Init & { params: Params[P] }] : [Init & { params?: never }] | []
type RedirectOptions<P> = P extends keyof Params ? [Init & { params: Params[P] }] : [Init & { params?: never }] | []

return {
redirect: <P extends Path>(url: P, ...[options]: RedirectOptions<P>) => {
Expand Down
10 changes: 4 additions & 6 deletions packages/solid-router/src/client/components.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { A, AnchorProps, Navigate } from '@solidjs/router'
import { A, Navigate } from '@solidjs/router'

import { AnchorProps, NavigateProps } from './types'
import { generatePath } from './utils'

export const components = <Path extends string, Params extends Record<string, any>>() => {
type ParamPath = keyof Params
type Props<P> = AnchorProps & (P extends ParamPath ? { href: P; params: Params[P] } : { href: P; params?: never })

return {
A: <P extends Path>(props: Props<P>) => {
A: <P extends Path>(props: AnchorProps<P, Params>) => {
return <A {...props} href={props.params ? generatePath(props.href, props.params) : props.href} />
},
Navigate: <P extends Path>(props: Props<P>) => {
Navigate: <P extends Path>(props: NavigateProps<P, Params>) => {
return <Navigate {...props} href={props.params ? generatePath(props.href, props.params) : props.href} />
},
}
Expand Down
16 changes: 6 additions & 10 deletions packages/solid-router/src/client/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import { Accessor } from 'solid-js'
import { NavigateOptions, useLocation, useMatch, useNavigate, useParams } from '@solidjs/router'
import { NavigateOptions as NavOptions, useLocation, useMatch, useNavigate, useParams } from '@solidjs/router'
import { MatchFilters } from '@solidjs/router/dist/types'

import { generatePath } from './utils'
import { NavigateOptions } from './types'

export const hooks = <Path extends string, Params extends Record<string, any>, ModalPath extends string>() => {
type ParamPath = keyof Params
type Options<P> = P extends ParamPath
? [Partial<NavigateOptions> & { params: Params[P] }]
: [Partial<NavigateOptions> & { params?: never }] | []

return {
useParams: <P extends ParamPath>(path: P) => useParams<Params[typeof path]>() as Params[P],
useParams: <P extends keyof Params>(path: P) => useParams<Params[typeof path]>() as Params[P],
useNavigate: () => {
const navigate = useNavigate()
return <P extends Path>(href: P, ...[options]: Options<P>) => {
return <P extends Path>(href: P, ...[options]: NavigateOptions<P, Params>) => {
navigate(options?.params ? generatePath(href, options.params) : href, options)
}
},
Expand All @@ -25,8 +21,8 @@ export const hooks = <Path extends string, Params extends Record<string, any>, M
const location = useLocation<any>()
const navigate = useNavigate()

type Options<P> = Partial<NavigateOptions<any>> &
(P extends ParamPath ? { at?: P; params: Params[P] } : { at?: P; params?: never })
type Options<P> = Partial<NavOptions<any>> &
(P extends keyof Params ? { at?: P; params: Params[P] } : { at?: P; params?: never })

return {
current: location.state?.modal || '',
Expand Down
1 change: 1 addition & 0 deletions packages/solid-router/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './components'
export * from './hooks'
export * from './types'
export * from './utils'
15 changes: 15 additions & 0 deletions packages/solid-router/src/client/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AnchorProps as AProps, NavigateOptions as NavOptions, NavigateProps as NavProps } from '@solidjs/router'

type ComponentProps<Path extends string, Params extends Record<string, any>> = Path extends keyof Params
? { href: Path; params: Params[Path] }
: { href: Path; params?: never }

export type AnchorProps<Path extends string, Params extends Record<string, any>> = Omit<AProps, 'href'> &
ComponentProps<Path, Params>

export type NavigateProps<Path extends string, Params extends Record<string, any>> = Omit<NavProps, 'href'> &
ComponentProps<Path, Params>

export type NavigateOptions<Path extends string, Params extends Record<string, any>> = Path extends keyof Params
? [Partial<NavOptions> & { params: Params[Path] }]
: [Partial<NavOptions> & { params?: never }] | []

0 comments on commit 255f4be

Please sign in to comment.