このコミットが含まれているのは:
2026-10-18 01:39:03 +09:00
コミット eda81cfa89
30個のファイルの変更、965行の追加、473行の削除
+49 -23
ファイルの表示
@@ -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) === '//'