Skip to content

Commit

Permalink
examples: wip framer motion
Browse files Browse the repository at this point in the history
  • Loading branch information
tannerlinsley committed Jul 24, 2023
1 parent 5d49120 commit 14160f5
Show file tree
Hide file tree
Showing 8 changed files with 356 additions and 0 deletions.
5 changes: 5 additions & 0 deletions examples/react/wip-with-framer-motion/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
6 changes: 6 additions & 0 deletions examples/react/wip-with-framer-motion/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Example

To run this example:

- `npm install` or `yarn`
- `npm start` or `yarn start`
13 changes: 13 additions & 0 deletions examples/react/wip-with-framer-motion/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
37 changes: 37 additions & 0 deletions examples/react/wip-with-framer-motion/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "tanstack-router-react-example-basic",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite --port=3000",
"build": "vite build",
"serve": "vite preview",
"start": "vite"
},
"dependencies": {
"@tanstack/react-actions": "0.0.1-beta.113",
"@tanstack/react-loaders": "0.0.1-beta.113",
"@tanstack/router": "0.0.1-beta.117",
"@tanstack/router-devtools": "0.0.1-beta.117",
"@vitejs/plugin-react": "^1.1.3",
"axios": "^1.1.3",
"framer-motion": "^10.13.0",
"immer": "^9.0.15",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"vite": "^2.8.6",
"zod": "^3.19.1"
},
"devDependencies": {
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.8"
},
"pnpm": {
"overrides": {
"@tanstack/router": "workspace:*",
"@tanstack/react-actions": "workspace:*",
"@tanstack/react-loaders": "workspace:*",
"@tanstack/router-devtools": "workspace:*"
}
}
}
269 changes: 269 additions & 0 deletions examples/react/wip-with-framer-motion/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { AnimatePresence, motion } from 'framer-motion'
import {
Outlet,
RouterProvider,
Router,
Link,
RootRoute,
Route,
ErrorComponent,
useRouter,
useMatches,
RouterContext,
} from '@tanstack/router'
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
import axios from 'axios'
import {
LoaderClient,
Loader,
LoaderClientProvider,
useLoader,
} from '@tanstack/react-loaders'

type PostType = {
id: string
title: string
body: string
}

const fetchPosts = async () => {
console.log('Fetching posts...')
await new Promise((r) => setTimeout(r, 500))
return axios
.get<PostType[]>('https://jsonplaceholder.typicode.com/posts')
.then((r) => r.data.slice(0, 10))
}

const fetchPost = async (postId: string) => {
console.log(`Fetching post with id ${postId}...`)
await new Promise((r) => setTimeout(r, 500))
const post = await axios
.get<PostType>(`https://jsonplaceholder.typicode.com/posts/${postId}`)
.then((r) => r.data)

if (!post) {
throw new NotFoundError(`Post with id "${postId}" not found!`)
}

return post
}

const postsLoader = new Loader({
fn: fetchPosts,
})

const postLoader = new Loader({
fn: fetchPost,
onInvalidate: () => {
postsLoader.invalidate()
},
})

const loaderClient = new LoaderClient({
getLoaders: () => ({
posts: postsLoader,
post: postLoader,
}),
})

declare module '@tanstack/react-loaders' {
interface Register {
loaderClient: typeof loaderClient
}
}

type RouterContext = {
loaderClient: typeof loaderClient
}

export const transitionProps = {
initial: { y: -20, opacity: 0, position: 'absolute' },
animate: { y: 0, opacity: 1, damping: 5 },
exit: { y: 60, opacity: 0 },
transition: {
type: 'spring',
stiffness: 150,
damping: 10,
},
} as const

const rootRoute = RootRoute.withRouterContext<RouterContext>()({
component: () => {
const router = useRouter()
const matches = useMatches()
return (
<>
<div className="p-2 flex gap-2 text-lg">
<Link
to="/"
activeProps={{
className: 'font-bold',
}}
activeOptions={{ exact: true }}
>
Home
</Link>{' '}
<Link
to={'/posts'}
activeProps={{
className: 'font-bold',
}}
>
Posts
</Link>
</div>
<hr />
<AnimatePresence mode="wait">
<RouterContext router={router} key={router.state.location.key}>
<Outlet />
</RouterContext>
</AnimatePresence>
{/* Start rendering router matches */}
<TanStackRouterDevtools position="bottom-right" />
</>
)
},
})

const indexRoute = new Route({
getParentRoute: () => rootRoute,
path: '/',
component: () => {
return (
<motion.div className="p-2" {...transitionProps}>
<h3>Welcome Home!</h3>
</motion.div>
)
},
})

const postsRoute = new Route({
getParentRoute: () => rootRoute,
path: 'posts',
loader: async ({ context }) => {
const postsLoader = context.loaderClient.loaders.posts

await postsLoader.load()

return {
promise: new Promise((r) => setTimeout(r, 500)),
useLoader: () =>
useLoader({
loader: postsLoader,
}),
}
},
component: ({ useLoader }) => {
const postsLoader = useLoader().useLoader()

return (
<motion.div className="p-2 flex gap-2" {...transitionProps}>
<ul className="list-disc pl-4">
{[
...postsLoader.state.data,
{ id: 'i-do-not-exist', title: 'Non-existent Post' },
]?.map((post) => {
return (
<li key={post.id} className="whitespace-nowrap">
<Link
to={postRoute.to}
params={{
postId: post.id,
}}
className="block py-1 text-blue-800 hover:text-blue-600"
activeProps={{ className: 'text-black font-bold' }}
>
<div>{post.title.substring(0, 20)}</div>
</Link>
</li>
)
})}
</ul>
<hr />
<Outlet />
</motion.div>
)
},
})

const postsIndexRoute = new Route({
getParentRoute: () => postsRoute,
path: '/',
component: () => <div>Select a post.</div>,
})

class NotFoundError extends Error {}

const postRoute = new Route({
getParentRoute: () => postsRoute,
path: '$postId',
loader: async ({ context: { loaderClient }, params: { postId } }) => {
const postLoader = loaderClient.loaders.post
await postLoader.load({
variables: postId,
})

// Return a curried hook!
return () =>
useLoader({
loader: postLoader,
variables: postId,
})
},
errorComponent: ({ error }) => {
if (error instanceof NotFoundError) {
return <div>{error.message}</div>
}

return <ErrorComponent error={error} />
},
component: () => {
const {
state: { data: post },
} = postRoute.useLoader()()

return (
<div className="space-y-2">
<h4 className="text-xl font-bold underline">{post.title}</h4>
<div className="text-sm">{post.body}</div>
</div>
)
},
})

const routeTree = rootRoute.addChildren([
postsRoute.addChildren([postRoute, postsIndexRoute]),
indexRoute,
])

// Set up a Router instance
const router = new Router({
routeTree,
defaultPreload: 'intent',
context: {
loaderClient,
},
})

// Register things for typesafety
declare module '@tanstack/router' {
interface Register {
router: typeof router
}
}

const rootElement = document.getElementById('app')!

if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)

root.render(
// <React.StrictMode>
<LoaderClientProvider loaderClient={loaderClient}>
<RouterProvider router={router} />
</LoaderClientProvider>,
// </React.StrictMode>,
)
}
12 changes: 12 additions & 0 deletions examples/react/wip-with-framer-motion/tsconfig.dev.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"composite": true,
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./build/types"
},
"files": ["src/main.tsx"],
"include": [
"src"
// "__tests__/**/*.test.*"
]
}
7 changes: 7 additions & 0 deletions examples/react/wip-with-framer-motion/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"jsx": "react"
}
}
7 changes: 7 additions & 0 deletions examples/react/wip-with-framer-motion/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})

0 comments on commit 14160f5

Please sign in to comment.