This commit is contained in:
@@ -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
|
export const menuOutline = ({ tag, wikiId, user, pathName }: {
|
||||||
|
tag?: Tag | null
|
||||||
wikiId: number | null
|
wikiId: number | null
|
||||||
user: User | null }): Menu => {
|
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 wikiPageFlg = Boolean (/^\/wiki\/(?!new|changes)[^\/]+/.test (pathName) && wikiId)
|
||||||
const wikiTitle = location.pathname.split ('/')[2] ?? ''
|
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 menu = menuOutline ({ tag, wikiId, user, pathName: location.pathname })
|
||||||
const activeIdx = (menu
|
const visibleMenu = menu.filter ((item): item is MenuVisibleItem => item.visible ?? true)
|
||||||
.filter ((item): item is MenuVisibleItem => item.visible ?? true)
|
const activeIdx =
|
||||||
.findIndex (item => location.pathname.startsWith (item.base || item.to)))
|
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 raf = requestAnimationFrame (() => measure (moreVsbl ? -1 : activeIdx))
|
||||||
const onResize = () => requestAnimationFrame (() => measure (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
|
setOpenItemIdx (activeIdx)
|
||||||
.filter ((item): item is MenuVisibleItem => item.visible ?? true)
|
|
||||||
.findIndex (item => location.pathname.startsWith (item.base || item.to)))
|
|
||||||
}, [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)
|
{visibleMenu.map ((item, i) => (
|
||||||
.map ((item, i) => (
|
|
||||||
<motion.div
|
<motion.div
|
||||||
key={item.to}
|
key={item.to}
|
||||||
layoutId={`menu-${ item.name }`}
|
layoutId={`menu-${ item.name }`}
|
||||||
onMouseEnter={() => {
|
animate={{ opacity: moreVsbl ? 0 : 1 }}
|
||||||
setMoreVsbl (false)
|
transition={{ opacity: { duration: .12 },
|
||||||
setTimeout (() => measure (activeIdx), 300)
|
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',
|
(i === openItemIdx) && 'font-bold')}>
|
||||||
moreVsbl && 'pointer-events-none')}>
|
|
||||||
{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))
|
if (moreVsbl)
|
||||||
return
|
|
||||||
setMoreVsbl (false)
|
setMoreVsbl (false)
|
||||||
setTimeout (() => measure (activeIdx), 300)
|
|
||||||
}}
|
}}
|
||||||
initial={{ height: 0 }}
|
transition={{ layout: { duration: .2, ease: 'easeOut' } }}
|
||||||
animate={{ height: (moreVsbl ? 40 * menu.length : (activeIdx < 0 ? 0 : 40)) }}
|
onAnimationComplete={() => {
|
||||||
exit={{ height: 0 }}
|
measure (moreVsbl ? -1 : activeIdx)
|
||||||
transition={{ duration: .2, ease: 'easeOut' }}>
|
}}>
|
||||||
{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 },
|
: { initial: { x: 40, y: -40, opacity: 0 },
|
||||||
animate: { y: 0, opacity: 1 },
|
animate: { x: 0, y: 0, opacity: 1 },
|
||||||
exit: { y: -40, opacity: 0 },
|
exit: { x: 40, y: -40, opacity: 0 } })}
|
||||||
transition: { duration: .2, ease: 'easeOut' } })}
|
|
||||||
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}
|
<h2>{item.name}</h2>
|
||||||
</motion.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 },
|
exit: { y: -40, opacity: 0 } })}>
|
||||||
transition: { duration: .2, ease: 'easeOut' } })}>
|
|
||||||
{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-${
|
layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}>
|
||||||
menu.filter (x => x.visible ?? true)[activeIdx].name }-${
|
|
||||||
i }`}>
|
|
||||||
{item.component}
|
{item.component}
|
||||||
</motion.div>)
|
</motion.div>)
|
||||||
: (
|
: (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={`l-${ i }`}
|
key={`l-${ i }`}
|
||||||
layoutId={`submenu-${
|
layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}>
|
||||||
menu.filter (x => x.visible ?? true)[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)
|
{visibleMenu.map ((item, i) => (
|
||||||
.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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user