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