このコミットが含まれているのは:
@@ -142,23 +142,34 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
const layoutRef = useRef<HTMLDivElement | null> (null)
|
||||
const barRef = useRef<HTMLElement | null> (null)
|
||||
const submenuContentRef = useRef<HTMLDivElement | null> (null)
|
||||
const highlightTargetRef = useRef (-1)
|
||||
|
||||
const measure = (idx: number) => {
|
||||
const nav = navRef.current
|
||||
const el = itemsRef.current[idx < 0 ? visibleMenu.length : idx]
|
||||
|
||||
if (!(nav) || !(el))
|
||||
{
|
||||
setHL ({ left: 0, width: 0, visible: true })
|
||||
return
|
||||
}
|
||||
if (nav == null || el == null || nav.offsetWidth === 0
|
||||
|| layoutRef.current?.dataset.desktop !== 'true')
|
||||
{
|
||||
setHL (current => current.visible
|
||||
? { left: 0, width: 0, visible: false } : current)
|
||||
return
|
||||
}
|
||||
|
||||
const navRect = nav.getBoundingClientRect ()
|
||||
const elRect = el.getBoundingClientRect ()
|
||||
|
||||
setHL ({ left: elRect.left - navRect.left,
|
||||
width: elRect.width,
|
||||
visible: true })
|
||||
// Layout offsets exclude Framer Motion's temporary projection transforms.
|
||||
let left = 0
|
||||
let node: HTMLElement | null = el
|
||||
while (node != null && node !== nav)
|
||||
{
|
||||
left += node.offsetLeft
|
||||
const parent: Element | null = node.offsetParent
|
||||
node = parent instanceof HTMLElement ? parent : null
|
||||
}
|
||||
const width = node === nav ? el.offsetWidth : 0
|
||||
const visible = width > 0
|
||||
setHL (current =>
|
||||
(current.left === left && current.width === width && current.visible === visible)
|
||||
? current : { left, width, visible })
|
||||
}
|
||||
|
||||
const [hl, setHL] = useState<{ left: number; width: number; visible: boolean }> ({
|
||||
@@ -200,11 +211,14 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
|
||||
const menu = menuOutline ({ tag, material, wikiId, user, pathName: location.pathname })
|
||||
const visibleMenu = menu.filter ((item): item is MenuVisibleItem => item.visible ?? true)
|
||||
const menuLabels = visibleMenu.map (item => item.name).join ('\u0000')
|
||||
const moreMenu = menu.filter (item =>
|
||||
!(item.visible ?? true)
|
||||
|| item.subMenu.filter (subItem => subItem.visible ?? true).length > 0)
|
||||
const activeIdx =
|
||||
visibleMenu.findIndex (item => pathname.startsWith (item.base || item.to))
|
||||
highlightTargetRef.current = moreVsbl ? -1 : activeIdx
|
||||
const activeSubmenuId = `submenu-${ menu.indexOf (visibleMenu[activeIdx]) }`
|
||||
const topNavTransition =
|
||||
animationsOff
|
||||
? { duration: 0 }
|
||||
@@ -300,6 +314,9 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
typeof ResizeObserver === 'undefined' ? null : new ResizeObserver (schedule)
|
||||
observer?.observe (bar)
|
||||
observer?.observe (primary)
|
||||
for (const item of itemsRef.current)
|
||||
if (item != null)
|
||||
observer?.observe (item)
|
||||
if (account != null)
|
||||
observer?.observe (account)
|
||||
update ()
|
||||
@@ -314,7 +331,13 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
removeEventListener ('resize', schedule)
|
||||
document.fonts?.removeEventListener ('loadingdone', schedule)
|
||||
}
|
||||
}, [activeIdx, moreVsbl, desktop, density, user?.name, locale, locales])
|
||||
}, [activeIdx, moreVsbl, desktop, density, user?.name, locale, locales, menuLabels])
|
||||
|
||||
useLayoutEffect (() => {
|
||||
measure (moreVsbl ? -1 : activeIdx)
|
||||
if (!(desktop))
|
||||
setMoreVsbl (false)
|
||||
}, [activeIdx, moreVsbl, desktop, density, locale, animationMode, menuLabels])
|
||||
|
||||
useEffect (() => {
|
||||
const unsubscribe = WikiIdBus.subscribe (setWikiId)
|
||||
@@ -403,7 +426,8 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
className="shrink-0 whitespace-nowrap"
|
||||
{...(animationsOff
|
||||
? { }
|
||||
: { layoutId: `menu-${ item.name }` })}
|
||||
: { layoutId: `menu-${ menu.indexOf (item) }` })}
|
||||
onLayoutAnimationComplete={() => measure (highlightTargetRef.current)}
|
||||
animate={{ opacity: moreVsbl ? 0 : 1 }}
|
||||
transition={{ opacity: opacityTransition,
|
||||
layout: topNavTransition }}
|
||||
@@ -472,7 +496,10 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
}}
|
||||
transition={{ layout: topNavTransition }}
|
||||
onAnimationComplete={() => {
|
||||
measure (moreVsbl ? -1 : activeIdx)
|
||||
measure (highlightTargetRef.current)
|
||||
}}
|
||||
onLayoutAnimationComplete={() => {
|
||||
measure (highlightTargetRef.current)
|
||||
}}>
|
||||
<div ref={submenuContentRef} className={cn (moreVsbl && 'top-nav-more-menu')}>
|
||||
{moreVsbl
|
||||
@@ -484,10 +511,11 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
{...(animationsOff
|
||||
? { }
|
||||
: ((item.visible ?? true)
|
||||
? { layoutId: `menu-${ item.name }` }
|
||||
? { layoutId: `menu-${ menu.indexOf (item) }` }
|
||||
: { initial: { x: 40, y: -40, opacity: 0 },
|
||||
animate: { x: 0, y: 0, opacity: 1 },
|
||||
exit: { x: 40, y: -40, opacity: 0 } }))}
|
||||
onLayoutAnimationComplete={() => measure (highlightTargetRef.current)}
|
||||
className="z-10 min-w-0 px-3 py-2 font-bold">
|
||||
<h2>
|
||||
<LocalisedText language={item.nameLanguage ?? 'ja'}>
|
||||
@@ -507,9 +535,8 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
transition={topNavTransition}
|
||||
{...(animationsOff
|
||||
? { }
|
||||
: ((visibleMenu[activeIdx]?.name
|
||||
=== item.name)
|
||||
? { layoutId: `submenu-${ item.name }-${ j }` }
|
||||
: ((visibleMenu[activeIdx] === item)
|
||||
? { layoutId: `submenu-${ menu.indexOf (item) }-${ j }` }
|
||||
: { initial: { y: -40, opacity: 0 },
|
||||
animate: { y: 0, opacity: 1 },
|
||||
exit: { y: -40, opacity: 0 } }))}>
|
||||
@@ -522,9 +549,8 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
transition={topNavTransition}
|
||||
{...(animationsOff
|
||||
? { }
|
||||
: ((visibleMenu[activeIdx]?.name
|
||||
=== item.name)
|
||||
? { layoutId: `submenu-${ item.name }-${ j }` }
|
||||
: ((visibleMenu[activeIdx] === item)
|
||||
? { layoutId: `submenu-${ menu.indexOf (item) }-${ j }` }
|
||||
: { initial: { y: -40, opacity: 0 },
|
||||
animate: { y: 0, opacity: 1 },
|
||||
exit: { y: -40, opacity: 0 } }))}>
|
||||
@@ -591,7 +617,7 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
key={`c-${ i }`}
|
||||
className="min-w-0 max-w-full"
|
||||
transition={{ layout: topNavTransition }}
|
||||
layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}>
|
||||
layoutId={`${ activeSubmenuId }-${ i }`}>
|
||||
{item.component}
|
||||
</motion.div>)
|
||||
: (
|
||||
@@ -599,7 +625,7 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
key={`l-${ i }`}
|
||||
className="min-w-0 max-w-full"
|
||||
transition={{ layout: topNavTransition }}
|
||||
layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}>
|
||||
layoutId={`${ activeSubmenuId }-${ i }`}>
|
||||
<PrefetchLink
|
||||
to={item.to}
|
||||
target={item.to.slice (0, 2) === '//'
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする