feat: create basic router

This commit is contained in:
Valerio Ageno
2024-05-11 15:08:49 +02:00
parent df80756c44
commit 73c94e5fbb
30 changed files with 686 additions and 105 deletions
@@ -0,0 +1,18 @@
import { useRouterStore } from '../hooks/useRouterStore'
import type { AnchorHTMLAttributes, MouseEvent } from 'react'
export default function Link(
props: AnchorHTMLAttributes<HTMLAnchorElement>,
): JSX.Element {
const handleTransition = (e: MouseEvent<HTMLAnchorElement>): void => {
e.preventDefault()
props.onClick?.(e)
useRouterStore.setState({ location: { pathname: props.href || '' } })
window.history.pushState('', '', props.href)
}
return (
<a {...props} onClick={handleTransition}>
{props.children}
</a>
)
}
@@ -0,0 +1,30 @@
import * as React from 'react'
import { useRouter } from '../hooks/useRouter'
import { useRouterStore } from '../hooks/useRouterStore'
export function Matches(): JSX.Element | undefined {
const matchId = useRouterStore.getState().matches[0]?.id
//if (!matchId) return <></>
return (
<React.Suspense>
<Match id={matchId} />
</React.Suspense>
)
}
interface MatchProps {
id: string
}
const Match = React.memo(function ({ id }: MatchProps) {
const location = useRouterStore((st) => st.location)
const router = useRouter()
console.log(router, location)
const route = router.routesById[location?.pathname]
return route.component()
})
@@ -0,0 +1,7 @@
import * as React from 'react'
const Outlet = React.memo(function Outlet() {
return <h1>Outlet</h1>
})
export default Outlet
@@ -0,0 +1,20 @@
import * as React from 'react'
import type { Router } from '../router'
const routerContext = React.createContext<Router>(null!)
const ROUTER_DOM_CONTEXT = '__TUONO_ROUTER__CONTEXT__'
export function getRouterContext(): any {
if (typeof document === 'undefined') {
return routerContext
}
if (window[ROUTER_DOM_CONTEXT as any]) {
return window[ROUTER_DOM_CONTEXT as any]
}
window[ROUTER_DOM_CONTEXT as any] = routerContext as any
return routerContext
}
@@ -0,0 +1,60 @@
import * as React from 'react'
import { getRouterContext } from './RouterContext'
import { Matches } from './Matches'
type Router = any
interface RouterContextProviderProps {
router: Router
children: ReactNode
}
function RouterContextProvider({
router,
children,
...rest
}: RouterContextProviderProps): JSX.Element {
// Allow the router to update options on the router instance
router.update({
...router.options,
...rest,
context: {
...router.options.context,
...rest.context,
},
})
const routerContext = getRouterContext()
const pendingElement = router.options.defaultPendingComponent ? (
<router.options.defaultPendingComponent />
) : null
const provider = (
<React.Suspense fallback={pendingElement}>
<routerContext.Provider value={router}>{children}</routerContext.Provider>
</React.Suspense>
)
// NOTE: verify usefulness
if (router.options.Wrap) {
return <router.options.Wrap>{provider}</router.options.Wrap>
}
return provider
}
interface RouterProviderProps {
router: Router
}
export function RouterProvider({
router,
...rest
}: RouterProviderProps): JSX.Element {
return (
<RouterContextProvider router={router} {...rest}>
<Matches />
</RouterContextProvider>
)
}