From 27989f3bb207b27fcc49788ef2756012a5a5117c Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sun, 12 Apr 2026 21:05:42 +0900 Subject: [PATCH] #95 --- frontend/src/components/TopNav.tsx | 111 ++++++++++++++--------------- frontend/src/pages/MorePage.tsx | 3 +- 2 files changed, 56 insertions(+), 58 deletions(-) diff --git a/frontend/src/components/TopNav.tsx b/frontend/src/components/TopNav.tsx index 936b96b..aedbf82 100644 --- a/frontend/src/components/TopNav.tsx +++ b/frontend/src/components/TopNav.tsx @@ -19,13 +19,15 @@ import type { Menu, MenuVisibleItem, Tag, User } from '@/types' type Props = { user: User | null } -export const menuOutline = ({ tag, wikiId, user }: { tag?: Tag | null - wikiId: number | null - user: User | null }): Menu => { +export const menuOutline = ({ tag, wikiId, user, pathName }: { + tag?: Tag | null + wikiId: number | null + user: User | null, + pathName: string }): Menu => { const postCount = tag?.postCount ?? 0 - const wikiPageFlg = Boolean (/^\/wiki\/(?!new|changes)[^\/]+/.test (location.pathname) && wikiId) - const wikiTitle = location.pathname.split ('/')[2] ?? '' + const wikiPageFlg = Boolean (/^\/wiki\/(?!new|changes)[^\/]+/.test (pathName) && wikiId) + const wikiTitle = pathName.split ('/')[2] ?? '' return [ { name: '広場', to: '/posts', subMenu: [ @@ -80,7 +82,7 @@ export default (({ user }: Props) => { const measure = (idx: number) => { const nav = navRef.current - const el = itemsRef.current[idx < 0 ? menu.length : idx] + const el = itemsRef.current[idx < 0 ? visibleMenu.length : idx] if (!(nav) || !(el)) { @@ -119,10 +121,10 @@ export default (({ user }: Props) => { queryKey: tagsKeys.show (effectiveTitle), queryFn: () => fetchTagByName (effectiveTitle) }) - const menu = menuOutline ({ tag, wikiId, user }) - const activeIdx = (menu - .filter ((item): item is MenuVisibleItem => item.visible ?? true) - .findIndex (item => location.pathname.startsWith (item.base || item.to))) + const menu = menuOutline ({ tag, wikiId, user, pathName: location.pathname }) + const visibleMenu = menu.filter ((item): item is MenuVisibleItem => item.visible ?? true) + const activeIdx = + visibleMenu.findIndex (item => location.pathname.startsWith (item.base || item.to)) const prevActiveIdxRef = useRef (activeIdx) @@ -135,26 +137,24 @@ export default (({ user }: Props) => { const dir = dirRef.current useLayoutEffect (() => { - const raf = requestAnimationFrame (() => measure (activeIdx)) - const onResize = () => requestAnimationFrame (() => measure (activeIdx)) + const raf = requestAnimationFrame (() => measure (moreVsbl ? -1 : activeIdx)) + const onResize = () => requestAnimationFrame (() => measure (moreVsbl ? -1 : activeIdx)) addEventListener ('resize', onResize) return () => { cancelAnimationFrame (raf) removeEventListener ('resize', onResize) } - }, [activeIdx]) + }) useEffect (() => { const unsubscribe = WikiIdBus.subscribe (setWikiId) return () => unsubscribe () - }, []) + }, [activeIdx]) useEffect (() => { setMenuOpen (false) - setOpenItemIdx (menu - .filter ((item): item is MenuVisibleItem => item.visible ?? true) - .findIndex (item => location.pathname.startsWith (item.base || item.to))) + setOpenItemIdx (activeIdx) }, [location]) return ( @@ -181,30 +181,29 @@ export default (({ user }: Props) => { transform: `translateX(${ hl.left }px)`, opacity: hl.visible ? 1 : 0 }}/> - {menu.filter ((item): item is MenuVisibleItem => item.visible ?? true) - .map ((item, i) => ( + {visibleMenu.map ((item, i) => ( { - setMoreVsbl (false) - setTimeout (() => measure (activeIdx), 300) - }}> + animate={{ opacity: moreVsbl ? 0 : 1 }} + transition={{ opacity: { duration: .12 }, + layout: { duration: .2, ease: 'easeOut' } }} + style={{ pointerEvents: moreVsbl ? 'none' : 'auto' }} + onMouseEnter={() => setMoreVsbl (false)}> { itemsRef.current[i] = el }} className={cn ('relative z-10 flex h-full items-center px-5', - (i === openItemIdx) && 'font-bold', - moreVsbl && 'pointer-events-none')}> + (i === openItemIdx) && 'font-bold')}> {item.name} ))} { - itemsRef.current[menu.length] = el + itemsRef.current[visibleMenu.length] = el }} onClick={e => e.preventDefault ()} onMouseEnter={() => { @@ -235,33 +234,33 @@ export default (({ user }: Props) => { { - if (!(moreVsbl)) - return - setMoreVsbl (false) - setTimeout (() => measure (activeIdx), 300) + if (moreVsbl) + setMoreVsbl (false) }} - initial={{ height: 0 }} - animate={{ height: (moreVsbl ? 40 * menu.length : (activeIdx < 0 ? 0 : 40)) }} - exit={{ height: 0 }} - transition={{ duration: .2, ease: 'easeOut' }}> + transition={{ layout: { duration: .2, ease: 'easeOut' } }} + onAnimationComplete={() => { + measure (moreVsbl ? -1 : activeIdx) + }}> {moreVsbl ? ( menu.map ((item, i) => (
- - {item.name} - +

{item.name}

+ {item.subMenu .filter (subItem => subItem.visible ?? true) .map ((subItem, j) => ( @@ -269,21 +268,24 @@ export default (({ user }: Props) => { ? ( x.visible ?? true)[activeIdx]?.name + transition={{ duration: .2, ease: 'easeOut' }} + {...((visibleMenu[activeIdx]?.name === item.name) ? { layoutId: `submenu-${ item.name }-${ j }` } : { initial: { y: -40, opacity: 0 }, animate: { y: 0, opacity: 1 }, - exit: { y: -40, opacity: 0 }, - transition: { duration: .2, ease: 'easeOut' } })}> + exit: { y: -40, opacity: 0 } })}> {subItem.component} ) : ( x.visible ?? true)[activeIdx]?.name + {...((visibleMenu[activeIdx]?.name === item.name) - && { layoutId: `submenu-${ item.name }-${ j }` })}> + ? { layoutId: `submenu-${ item.name }-${ j }` } + : { initial: { y: -40, opacity: 0 }, + animate: { y: 0, opacity: 1 }, + exit: { y: -40, opacity: 0 } })}> { )))}
))) - : ((menu.filter (item => item.visible ?? true)[activeIdx]?.subMenu ?? []).length > 0 + : ((visibleMenu[activeIdx]?.subMenu ?? []).length > 0 && (
@@ -308,24 +310,20 @@ export default (({ user }: Props) => { animate="centre" exit="exit" transition={{ duration: .2, ease: 'easeOut' }}> - {(menu.filter (item => item.visible ?? true)[activeIdx]?.subMenu ?? []) + {(visibleMenu[activeIdx]?.subMenu ?? []) .filter (item => item.visible ?? true) .map ((item, i) => ( 'component' in item ? ( x.visible ?? true)[activeIdx].name }-${ - i }`}> + layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}> {item.component} ) : ( x.visible ?? true)[activeIdx].name }-${ - i }`}> + layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}> { exit="closed" transition={{ duration: .2, ease: 'easeOut' }}> - {menu.filter ((item): item is MenuVisibleItem => item.visible ?? true) - .map ((item, i) => ( + {visibleMenu.map ((item, i) => ( { { - itemsRef.current[menu.length] = el + itemsRef.current[visibleMenu.length] = el }} className={cn ('w-full min-h-[40px] flex items-center pl-8', ((openItemIdx < 0) diff --git a/frontend/src/pages/MorePage.tsx b/frontend/src/pages/MorePage.tsx index dd57ec1..98c3efa 100644 --- a/frontend/src/pages/MorePage.tsx +++ b/frontend/src/pages/MorePage.tsx @@ -12,7 +12,8 @@ import type { User } from '@/types' export default (() => { - const menu = menuOutline ({ tag: null, wikiId: null, user: { } as User }) + const menu = menuOutline ( + { tag: null, wikiId: null, user: { } as User, pathName: location.pathname }) return (