diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4ae1579..4ad6239 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -31,7 +31,7 @@ "react-helmet-async": "^2.0.5", "react-markdown": "^10.1.0", "react-markdown-editor-lite": "^1.3.4", - "react-router-dom": "^6.30.0", + "react-router-dom": "^6.30.1", "react-youtube": "^10.1.0", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.3.0", @@ -6030,9 +6030,9 @@ } }, "node_modules/react-router": { - "version": "6.30.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", - "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==", + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", + "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", "license": "MIT", "dependencies": { "@remix-run/router": "1.23.0" @@ -6045,13 +6045,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.30.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz", - "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==", + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", + "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", "license": "MIT", "dependencies": { "@remix-run/router": "1.23.0", - "react-router": "6.30.0" + "react-router": "6.30.1" }, "engines": { "node": ">=14.0.0" diff --git a/frontend/package.json b/frontend/package.json index e112578..df73a58 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,7 +33,7 @@ "react-helmet-async": "^2.0.5", "react-markdown": "^10.1.0", "react-markdown-editor-lite": "^1.3.4", - "react-router-dom": "^6.30.0", + "react-router-dom": "^6.30.1", "react-youtube": "^10.1.0", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.3.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d844463..98fa8ce 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,10 @@ +import { AnimatePresence, LayoutGroup } from 'framer-motion' import { useEffect, useState } from 'react' -import { BrowserRouter, Navigate, Route, Routes, useLocation } from 'react-router-dom' +import { BrowserRouter, + Navigate, + Route, + Routes, + useLocation } from 'react-router-dom' import RouteBlockerOverlay from '@/components/RouteBlockerOverlay' import TopNav from '@/components/TopNav' @@ -20,11 +25,41 @@ import WikiHistoryPage from '@/pages/wiki/WikiHistoryPage' import WikiNewPage from '@/pages/wiki/WikiNewPage' import WikiSearchPage from '@/pages/wiki/WikiSearchPage' -import type { FC } from 'react' +import type { Dispatch, FC, SetStateAction } from 'react' import type { User } from '@/types' +const RouteTransitionWrapper = ({ user, setUser }: { + user: User | null + setUser: Dispatch> }) => { + const location = useLocation () + + return ( + + + + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + + + ) +} + + const PostDetailRoute = ({ user }: { user: User | null }) => { const location = useLocation () const key = location.pathname @@ -81,23 +116,7 @@ export default (() => {
- - }/> - }/> - }/> - }/> - }/> - }/> - }/> - }/> - }/> - }/> - }/> - }/> - }/> - }/> - }/> - +
diff --git a/frontend/src/components/PostList.tsx b/frontend/src/components/PostList.tsx index 1856bc3..39adbb3 100644 --- a/frontend/src/components/PostList.tsx +++ b/frontend/src/components/PostList.tsx @@ -1,4 +1,9 @@ +import { motion } from 'framer-motion' +import { useRef } from 'react' +import { useLocation } from 'react-router-dom' + import PrefetchLink from '@/components/PrefetchLink' +import { useSharedTransitionStore } from '@/stores/sharedTransitionStore' import type { FC, MouseEvent } from 'react' @@ -8,19 +13,58 @@ type Props = { posts: Post[] onClick?: (event: MouseEvent) => void } -export default (({ posts, onClick }: Props) => ( -
- {posts.map ((post, i) => ( - - {post.title - ))} -
)) satisfies FC +export default (({ posts, onClick }: Props) => { + const location = useLocation () + + const setForLocationKey = useSharedTransitionStore (s => s.setForLocationKey) + + const cardRef = useRef (null) + + return ( +
+ {posts.map ((post, i) => { + const sharedId = `page-${ post.id }` + const layoutId = sharedId + + return ( + { + setForLocationKey (location.key, sharedId) + onClick?.(e) + }}> + { + if (!(cardRef.current)) + return + + cardRef.current.style.position = 'relative' + cardRef.current.style.zIndex = '9999' + }} + onLayoutAnimationComplete={() => { + if (!(cardRef.current)) + return + + cardRef.current.style.zIndex = '' + cardRef.current.style.position = '' + }} + transition={{ type: 'spring', stiffness: 500, damping: 40, mass: .5 }}> + {post.title + + ) + })} +
) +}) satisfies FC diff --git a/frontend/src/components/PrefetchLink.tsx b/frontend/src/components/PrefetchLink.tsx index f983b0d..0aebe18 100644 --- a/frontend/src/components/PrefetchLink.tsx +++ b/frontend/src/components/PrefetchLink.tsx @@ -1,5 +1,6 @@ import { useQueryClient } from '@tanstack/react-query' import { forwardRef, useMemo } from 'react' +import { flushSync } from 'react-dom' import { createPath, useNavigate } from 'react-router-dom' import { useOverlayStore } from '@/components/RouteBlockerOverlay' @@ -11,6 +12,7 @@ import type { To } from 'react-router-dom' type Props = AnchorHTMLAttributes & { to: To + state?: Record replace?: boolean className?: string cancelOnError?: boolean } @@ -20,11 +22,15 @@ export default forwardRef (({ to, replace, className, + state, onMouseEnter, onTouchStart, onClick, cancelOnError = false, ...rest }, ref) => { + if ('onClick' in rest) + delete rest['onClick'] + const navigate = useNavigate () const qc = useQueryClient () const url = useMemo (() => { @@ -57,25 +63,37 @@ export default forwardRef (({ } const handleClick = async (ev: MouseEvent) => { - onClick?.(ev) + try + { + onClick?.(ev) - if (ev.defaultPrevented - || ev.metaKey - || ev.ctrlKey - || ev.shiftKey - || ev.altKey) - return + if (ev.defaultPrevented + || ev.metaKey + || ev.ctrlKey + || ev.shiftKey + || ev.altKey) + return - ev.preventDefault () + ev.preventDefault () - setOverlay (true) - const ok = await doPrefetch () - setOverlay (false) + flushSync (() => { + setOverlay (true) + }) + const ok = await doPrefetch () + flushSync (() => { + setOverlay (false) + }) - if (!(ok) && cancelOnError) - return + if (!(ok) && cancelOnError) + return - navigate (to, { replace }) + navigate (to, { replace, ...(state && { state }) }) + } + catch (ex) + { + console.log (ex) + ev.preventDefault () + } } return ( diff --git a/frontend/src/components/TopNav.tsx b/frontend/src/components/TopNav.tsx index 2133395..6d77c95 100644 --- a/frontend/src/components/TopNav.tsx +++ b/frontend/src/components/TopNav.tsx @@ -138,9 +138,13 @@ export default (({ user }: Props) => {