Browse Source

#95

feature/095
みてるぞ 2 days ago
parent
commit
27989f3bb2
2 changed files with 56 additions and 58 deletions
  1. +54
    -57
      frontend/src/components/TopNav.tsx
  2. +2
    -1
      frontend/src/pages/MorePage.tsx

+ 54
- 57
frontend/src/components/TopNav.tsx View File

@@ -19,13 +19,15 @@ import type { Menu, MenuVisibleItem, Tag, User } from '@/types'
type Props = { user: User | null } 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 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 [ return [
{ name: '広場', to: '/posts', subMenu: [ { name: '広場', to: '/posts', subMenu: [
@@ -80,7 +82,7 @@ export default (({ user }: Props) => {


const measure = (idx: number) => { const measure = (idx: number) => {
const nav = navRef.current 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)) if (!(nav) || !(el))
{ {
@@ -119,10 +121,10 @@ export default (({ user }: Props) => {
queryKey: tagsKeys.show (effectiveTitle), queryKey: tagsKeys.show (effectiveTitle),
queryFn: () => fetchTagByName (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<number> (activeIdx) const prevActiveIdxRef = useRef<number> (activeIdx)


@@ -135,26 +137,24 @@ export default (({ user }: Props) => {
const dir = dirRef.current const dir = dirRef.current


useLayoutEffect (() => { 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) addEventListener ('resize', onResize)
return () => { return () => {
cancelAnimationFrame (raf) cancelAnimationFrame (raf)
removeEventListener ('resize', onResize) removeEventListener ('resize', onResize)
} }
}, [activeIdx])
})


useEffect (() => { useEffect (() => {
const unsubscribe = WikiIdBus.subscribe (setWikiId) const unsubscribe = WikiIdBus.subscribe (setWikiId)
return () => unsubscribe () return () => unsubscribe ()
}, [])
}, [activeIdx])


useEffect (() => { useEffect (() => {
setMenuOpen (false) setMenuOpen (false)
setOpenItemIdx (menu
.filter ((item): item is MenuVisibleItem => item.visible ?? true)
.findIndex (item => location.pathname.startsWith (item.base || item.to)))
setOpenItemIdx (activeIdx)
}, [location]) }, [location])


return ( return (
@@ -181,30 +181,29 @@ export default (({ user }: Props) => {
transform: `translateX(${ hl.left }px)`, transform: `translateX(${ hl.left }px)`,
opacity: hl.visible ? 1 : 0 }}/> opacity: hl.visible ? 1 : 0 }}/>


{menu.filter ((item): item is MenuVisibleItem => item.visible ?? true)
.map ((item, i) => (
{visibleMenu.map ((item, i) => (
<motion.div <motion.div
key={item.to} key={item.to}
layoutId={`menu-${ item.name }`} layoutId={`menu-${ item.name }`}
onMouseEnter={() => {
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)}>
<PrefetchLink <PrefetchLink
to={item.to} to={item.to}
ref={(el: (HTMLAnchorElement | null)) => { ref={(el: (HTMLAnchorElement | null)) => {
itemsRef.current[i] = el itemsRef.current[i] = el
}} }}
className={cn ('relative z-10 flex h-full items-center px-5', 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} {item.name}
</PrefetchLink> </PrefetchLink>
</motion.div>))} </motion.div>))}
<PrefetchLink <PrefetchLink
to="#" to="#"
ref={(el: (HTMLAnchorElement | null)) => { ref={(el: (HTMLAnchorElement | null)) => {
itemsRef.current[menu.length] = el
itemsRef.current[visibleMenu.length] = el
}} }}
onClick={e => e.preventDefault ()} onClick={e => e.preventDefault ()}
onMouseEnter={() => { onMouseEnter={() => {
@@ -235,33 +234,33 @@ export default (({ user }: Props) => {
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
<motion.div <motion.div
key="submenu-shell" key="submenu-shell"
layout
className="relative hidden md:block overflow-hidden className="relative hidden md:block overflow-hidden
bg-yellow-200 dark:bg-red-950" bg-yellow-200 dark:bg-red-950"
style={{ height: moreVsbl ? 40 * menu.length : (activeIdx < 0 ? 0 : 40) }}
onMouseLeave={() => { onMouseLeave={() => {
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 {moreVsbl
? ( ? (
menu.map ((item, i) => ( menu.map ((item, i) => (
<div key={i} className="relative h-[40px]"> <div key={i} className="relative h-[40px]">
<div className="absolute inset-0 flex items-center px-3"> <div className="absolute inset-0 flex items-center px-3">
<motion.h2
<motion.div
transition={{ duration: .2, ease: 'easeOut' }}
{...((item.visible ?? true) {...((item.visible ?? true)
? { layoutId: `menu-${ item.name }` } ? { layoutId: `menu-${ item.name }` }
: { initial: { y: -40, opacity: 0 },
animate: { y: 0, opacity: 1 },
exit: { y: -40, opacity: 0 },
transition: { duration: .2, ease: 'easeOut' } })}
: { initial: { x: 40, y: -40, opacity: 0 },
animate: { x: 0, y: 0, opacity: 1 },
exit: { x: 40, y: -40, opacity: 0 } })}
className="z-10 h-full flex items-center px-3 font-bold w-24"> className="z-10 h-full flex items-center px-3 font-bold w-24">
{item.name}
</motion.h2>
<h2>{item.name}</h2>
</motion.div>
{item.subMenu {item.subMenu
.filter (subItem => subItem.visible ?? true) .filter (subItem => subItem.visible ?? true)
.map ((subItem, j) => ( .map ((subItem, j) => (
@@ -269,21 +268,24 @@ export default (({ user }: Props) => {
? ( ? (
<motion.div <motion.div
key={`c-${ i }-${ j }`} key={`c-${ i }-${ j }`}
{...((menu.filter (x => x.visible ?? true)[activeIdx]?.name
transition={{ duration: .2, ease: 'easeOut' }}
{...((visibleMenu[activeIdx]?.name
=== item.name) === item.name)
? { layoutId: `submenu-${ item.name }-${ j }` } ? { layoutId: `submenu-${ item.name }-${ j }` }
: { initial: { y: -40, opacity: 0 }, : { initial: { y: -40, opacity: 0 },
animate: { y: 0, opacity: 1 }, animate: { y: 0, opacity: 1 },
exit: { y: -40, opacity: 0 },
transition: { duration: .2, ease: 'easeOut' } })}>
exit: { y: -40, opacity: 0 } })}>
{subItem.component} {subItem.component}
</motion.div>) </motion.div>)
: ( : (
<motion.div <motion.div
key={`l-${ i }-${ j }`} key={`l-${ i }-${ j }`}
{...((menu.filter (x => x.visible ?? true)[activeIdx]?.name
{...((visibleMenu[activeIdx]?.name
=== item.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 } })}>
<PrefetchLink <PrefetchLink
to={subItem.to} to={subItem.to}
target={subItem.to.slice (0, 2) === '//' ? '_blank' : undefined} target={subItem.to.slice (0, 2) === '//' ? '_blank' : undefined}
@@ -293,7 +295,7 @@ export default (({ user }: Props) => {
</motion.div>)))} </motion.div>)))}
</div> </div>
</div>))) </div>)))
: ((menu.filter (item => item.visible ?? true)[activeIdx]?.subMenu ?? []).length > 0
: ((visibleMenu[activeIdx]?.subMenu ?? []).length > 0
&& ( && (
<div className="relative h-[40px]"> <div className="relative h-[40px]">
<AnimatePresence initial={false} custom={dir}> <AnimatePresence initial={false} custom={dir}>
@@ -308,24 +310,20 @@ export default (({ user }: Props) => {
animate="centre" animate="centre"
exit="exit" exit="exit"
transition={{ duration: .2, ease: 'easeOut' }}> transition={{ duration: .2, ease: 'easeOut' }}>
{(menu.filter (item => item.visible ?? true)[activeIdx]?.subMenu ?? [])
{(visibleMenu[activeIdx]?.subMenu ?? [])
.filter (item => item.visible ?? true) .filter (item => item.visible ?? true)
.map ((item, i) => ( .map ((item, i) => (
'component' in item 'component' in item
? ( ? (
<motion.div <motion.div
key={`c-${ i }`} key={`c-${ i }`}
layoutId={`submenu-${
menu.filter (x => x.visible ?? true)[activeIdx].name }-${
i }`}>
layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}>
{item.component} {item.component}
</motion.div>) </motion.div>)
: ( : (
<motion.div <motion.div
key={`l-${ i }`} key={`l-${ i }`}
layoutId={`submenu-${
menu.filter (x => x.visible ?? true)[activeIdx].name }-${
i }`}>
layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}>
<PrefetchLink <PrefetchLink
to={item.to} to={item.to}
target={item.to.slice (0, 2) === '//' ? '_blank' : undefined} target={item.to.slice (0, 2) === '//' ? '_blank' : undefined}
@@ -354,8 +352,7 @@ export default (({ user }: Props) => {
exit="closed" exit="closed"
transition={{ duration: .2, ease: 'easeOut' }}> transition={{ duration: .2, ease: 'easeOut' }}>
<Separator/> <Separator/>
{menu.filter ((item): item is MenuVisibleItem => item.visible ?? true)
.map ((item, i) => (
{visibleMenu.map ((item, i) => (
<Fragment key={i}> <Fragment key={i}>
<PrefetchLink <PrefetchLink
to={i === openItemIdx ? item.to : '#'} to={i === openItemIdx ? item.to : '#'}
@@ -411,7 +408,7 @@ export default (({ user }: Props) => {
<PrefetchLink <PrefetchLink
to="/more" to="/more"
ref={(el: (HTMLAnchorElement | null)) => { ref={(el: (HTMLAnchorElement | null)) => {
itemsRef.current[menu.length] = el
itemsRef.current[visibleMenu.length] = el
}} }}
className={cn ('w-full min-h-[40px] flex items-center pl-8', className={cn ('w-full min-h-[40px] flex items-center pl-8',
((openItemIdx < 0) ((openItemIdx < 0)


+ 2
- 1
frontend/src/pages/MorePage.tsx View File

@@ -12,7 +12,8 @@ import type { User } from '@/types'




export default (() => { 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 ( return (
<MainArea> <MainArea>


Loading…
Cancel
Save