Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions e2e/react-router/view-transitions/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules
.DS_Store
dist
dist-ssr
*.local

/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
11 changes: 11 additions & 0 deletions e2e/react-router/view-transitions/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"files.watcherExclude": {
"**/routeTree.gen.ts": true
},
"search.exclude": {
"**/routeTree.gen.ts": true
},
"files.readonlyInclude": {
"**/routeTree.gen.ts": true
}
}
6 changes: 6 additions & 0 deletions e2e/react-router/view-transitions/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`
12 changes: 12 additions & 0 deletions e2e/react-router/view-transitions/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!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>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
32 changes: 32 additions & 0 deletions e2e/react-router/view-transitions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "tanstack-e2e-router-react-example-view-transitions",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --port 3000",
"build": "vite build && tsc --noEmit",
"serve": "vite preview",
"start": "vite"
},
"dependencies": {
"@tailwindcss/postcss": "^4.1.15",
"@tanstack/react-router": "workspace:^",
"@tanstack/react-router-devtools": "workspace:^",
"@tanstack/router-plugin": "workspace:^",
"postcss": "^8.5.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
"tailwindcss": "^4.1.15",
"zod": "^3.24.2"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tanstack/router-e2e-utils": "workspace:^",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.7.2",
"vite": "^7.1.7"
}
}
41 changes: 41 additions & 0 deletions e2e/react-router/view-transitions/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineConfig, devices } from '@playwright/test'
import {
getDummyServerPort,
getTestServerPort,
} from '@tanstack/router-e2e-utils'
import packageJson from './package.json' with { type: 'json' }

const PORT = await getTestServerPort(packageJson.name)
const EXTERNAL_PORT = await getDummyServerPort(packageJson.name)
const baseURL = `http://localhost:${PORT}`
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
workers: 1,

reporter: [['line']],

use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL,
},

globalSetup: './tests/setup/global.setup.ts',
globalTeardown: './tests/setup/global.teardown.ts',

webServer: {
command: `VITE_NODE_ENV="test" VITE_SERVER_PORT=${PORT} VITE_EXTERNAL_PORT=${EXTERNAL_PORT} pnpm build && pnpm serve --port ${PORT}`,
url: baseURL,
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
},

projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
})
5 changes: 5 additions & 0 deletions e2e/react-router/view-transitions/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
55 changes: 55 additions & 0 deletions e2e/react-router/view-transitions/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
import './styles.css'

// Set up a Router instance
const router = createRouter({
routeTree,
defaultPreload: 'intent',
defaultStaleTime: 5000,
scrollRestoration: true,
/*
Using defaultViewTransition would prevent the need to
manually add `viewTransition: true` to every navigation.

If defaultViewTransition.types is a function, it will be called with the
location change info and should return an array of view transition types.
This is useful if you want to have different view transitions depending on
the navigation's specifics.

An example use case is sliding in a direction based on the index of the
previous and next routes when navigating via browser history back and forth.
*/
// defaultViewTransition: true
// OR
// defaultViewTransition: {
// types: ({ fromLocation, toLocation }) => {
// let direction = 'none'

// if (fromLocation) {
// const fromIndex = fromLocation.state.__TSR_index
// const toIndex = toLocation.state.__TSR_index

// direction = fromIndex > toIndex ? 'right' : 'left'
// }

// return [`slide-${direction}`]
// },
// },
})

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

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

if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(<RouterProvider router={router} />)
}
32 changes: 32 additions & 0 deletions e2e/react-router/view-transitions/src/posts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { notFound } from '@tanstack/react-router'
import axios from 'redaxios'

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

export const fetchPost = async (postId: string) => {
console.info(`Fetching post with id ${postId}...`)
await new Promise((r) => setTimeout(r, 0))
const post = await axios
.get<PostType>(`https://jsonplaceholder.typicode.com/posts/${postId}`)
.then((r) => r.data)
.catch((err) => {
if (err.status === 404) {
throw notFound()
}
throw err
})

return post
}

export const fetchPosts = async () => {
console.info('Fetching posts...')
await new Promise((r) => setTimeout(r, 0))
return axios
.get<Array<PostType>>('https://jsonplaceholder.typicode.com/posts')
.then((r) => r.data.slice(0, 10))
}
171 changes: 171 additions & 0 deletions e2e/react-router/view-transitions/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as HowItWorksRouteImport } from './routes/how-it-works'
import { Route as ExploreRouteImport } from './routes/explore'
import { Route as PostsRouteRouteImport } from './routes/posts.route'
import { Route as IndexRouteImport } from './routes/index'
import { Route as PostsIndexRouteImport } from './routes/posts.index'
import { Route as PostsPostIdRouteImport } from './routes/posts.$postId'

const HowItWorksRoute = HowItWorksRouteImport.update({
id: '/how-it-works',
path: '/how-it-works',
getParentRoute: () => rootRouteImport,
} as any)
const ExploreRoute = ExploreRouteImport.update({
id: '/explore',
path: '/explore',
getParentRoute: () => rootRouteImport,
} as any)
const PostsRouteRoute = PostsRouteRouteImport.update({
id: '/posts',
path: '/posts',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const PostsIndexRoute = PostsIndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => PostsRouteRoute,
} as any)
const PostsPostIdRoute = PostsPostIdRouteImport.update({
id: '/$postId',
path: '/$postId',
getParentRoute: () => PostsRouteRoute,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/posts': typeof PostsRouteRouteWithChildren
'/explore': typeof ExploreRoute
'/how-it-works': typeof HowItWorksRoute
'/posts/$postId': typeof PostsPostIdRoute
'/posts/': typeof PostsIndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/explore': typeof ExploreRoute
'/how-it-works': typeof HowItWorksRoute
'/posts/$postId': typeof PostsPostIdRoute
'/posts': typeof PostsIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/posts': typeof PostsRouteRouteWithChildren
'/explore': typeof ExploreRoute
'/how-it-works': typeof HowItWorksRoute
'/posts/$postId': typeof PostsPostIdRoute
'/posts/': typeof PostsIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/posts'
| '/explore'
| '/how-it-works'
| '/posts/$postId'
| '/posts/'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/explore' | '/how-it-works' | '/posts/$postId' | '/posts'
id:
| '__root__'
| '/'
| '/posts'
| '/explore'
| '/how-it-works'
| '/posts/$postId'
| '/posts/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
PostsRouteRoute: typeof PostsRouteRouteWithChildren
ExploreRoute: typeof ExploreRoute
HowItWorksRoute: typeof HowItWorksRoute
}

declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/how-it-works': {
id: '/how-it-works'
path: '/how-it-works'
fullPath: '/how-it-works'
preLoaderRoute: typeof HowItWorksRouteImport
parentRoute: typeof rootRouteImport
}
'/explore': {
id: '/explore'
path: '/explore'
fullPath: '/explore'
preLoaderRoute: typeof ExploreRouteImport
parentRoute: typeof rootRouteImport
}
'/posts': {
id: '/posts'
path: '/posts'
fullPath: '/posts'
preLoaderRoute: typeof PostsRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/posts/': {
id: '/posts/'
path: '/'
fullPath: '/posts/'
preLoaderRoute: typeof PostsIndexRouteImport
parentRoute: typeof PostsRouteRoute
}
'/posts/$postId': {
id: '/posts/$postId'
path: '/$postId'
fullPath: '/posts/$postId'
preLoaderRoute: typeof PostsPostIdRouteImport
parentRoute: typeof PostsRouteRoute
}
}
}

interface PostsRouteRouteChildren {
PostsPostIdRoute: typeof PostsPostIdRoute
PostsIndexRoute: typeof PostsIndexRoute
}

const PostsRouteRouteChildren: PostsRouteRouteChildren = {
PostsPostIdRoute: PostsPostIdRoute,
PostsIndexRoute: PostsIndexRoute,
}

const PostsRouteRouteWithChildren = PostsRouteRoute._addFileChildren(
PostsRouteRouteChildren,
)

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
PostsRouteRoute: PostsRouteRouteWithChildren,
ExploreRoute: ExploreRoute,
HowItWorksRoute: HowItWorksRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
Loading
Loading