このコミットが含まれているのは:
2026-10-17 23:49:20 +09:00
コミット 851d5e8e09
8個のファイルの変更、105行の追加、158行の削除
+50 -19
ファイルの表示
@@ -139,6 +139,7 @@ const TopNav: FC<Props> = ({ user, locales }) => {
const navRef = useRef<HTMLDivElement | null> (null)
const layoutRef = useRef<HTMLDivElement | null> (null)
const barRef = useRef<HTMLElement | null> (null)
const submenuContentRef = useRef<HTMLDivElement | null> (null)
const measure = (idx: number) => {
const nav = navRef.current
@@ -167,6 +168,7 @@ const TopNav: FC<Props> = ({ user, locales }) => {
const [density, setDensity] = useState<NavDensity> ('auto')
const [moreVsbl, setMoreVsbl] = useState (false)
const [openItemIdx, setOpenItemIdx] = useState (-1)
const [submenuHeight, setSubmenuHeight] = useState (0)
const [wikiId, setWikiId] = useState<number | null> (WikiIdBus.get ())
const wikiIdStr = String (wikiId ?? '')
@@ -201,7 +203,6 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|| item.subMenu.filter (subItem => subItem.visible ?? true).length > 0)
const activeIdx =
visibleMenu.findIndex (item => pathname.startsWith (item.base || item.to))
const submenuHeight = moreVsbl ? 40 * moreMenu.length : (activeIdx < 0 ? 0 : 40)
const topNavTransition =
animationsOff
? { duration: 0 }
@@ -260,11 +261,16 @@ const TopNav: FC<Props> = ({ user, locales }) => {
{
layout.setAttribute ('data-density', candidate)
const style = getComputedStyle (bar)
const required = primary.getBoundingClientRect ().width
+ (account?.getBoundingClientRect ().width ?? 0)
const primaryWidth = Math.max (
primary.getBoundingClientRect ().width, primary.scrollWidth)
const accountWidth =
account == null
? 0
: Math.max (account.getBoundingClientRect ().width, account.scrollWidth)
const required = primaryWidth + accountWidth
+ parseFloat (style.paddingLeft) + parseFloat (style.paddingRight)
+ (account == null ? 0 : parseFloat (style.columnGap))
if (required <= bar.clientWidth)
if (Math.ceil (required) <= bar.clientWidth)
return candidate
}
return null
@@ -313,6 +319,26 @@ const TopNav: FC<Props> = ({ user, locales }) => {
return () => unsubscribe ()
}, [])
useLayoutEffect (() => {
const content = submenuContentRef.current
if (content == null)
return
const update = () => {
setSubmenuHeight (Math.ceil (content.getBoundingClientRect ().height))
}
const observer =
typeof ResizeObserver === 'undefined' ? null : new ResizeObserver (update)
observer?.observe (content)
update ()
addEventListener ('resize', update)
return () => {
observer?.disconnect ()
removeEventListener ('resize', update)
}
}, [activeIdx, moreVsbl, desktop, density, locale])
useEffect (() => {
setMenuOpen (false)
setOpenItemIdx (activeIdx)
@@ -447,12 +473,11 @@ const TopNav: FC<Props> = ({ user, locales }) => {
onAnimationComplete={() => {
measure (moreVsbl ? -1 : activeIdx)
}}>
<div ref={submenuContentRef} className={cn (moreVsbl && 'top-nav-more-menu')}>
{moreVsbl
? (
moreMenu.map ((item, i) => (
<div key={i} className="relative h-[40px]">
<div className="absolute inset-0 flex items-center px-3
overflow-x-auto whitespace-nowrap">
<div key={i} className="contents">
<motion.div
transition={topNavTransition}
{...(animationsOff
@@ -462,13 +487,14 @@ const TopNav: FC<Props> = ({ user, locales }) => {
: { 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 shrink-0 items-center px-3 font-bold w-28">
className="z-10 min-w-0 px-3 py-2 font-bold">
<h2>
<LocalisedText language={item.nameLanguage ?? 'ja'}>
{item.name}
</LocalisedText>
</h2>
</motion.div>
<div className="top-nav-more-links flex min-w-0 flex-wrap items-center">
{item.subMenu
.filter (subItem => subItem.visible ?? true)
.map ((subItem, j) => (
@@ -476,6 +502,7 @@ const TopNav: FC<Props> = ({ user, locales }) => {
? (
<motion.div
key={`c-${ i }-${ j }`}
className="min-w-0 max-w-full"
transition={topNavTransition}
{...(animationsOff
? { }
@@ -490,6 +517,7 @@ const TopNav: FC<Props> = ({ user, locales }) => {
: (
<motion.div
key={`l-${ i }-${ j }`}
className="min-w-0 max-w-full"
transition={topNavTransition}
{...(animationsOff
? { }
@@ -503,7 +531,7 @@ const TopNav: FC<Props> = ({ user, locales }) => {
to={subItem.to}
target={subItem.to.slice (0, 2) === '//' ? '_blank' : undefined}
onClick={() => setMoreVsbl (false)}
className="top-nav-menu-link h-full flex items-center px-3">
className="top-nav-menu-link flex min-h-[40px] items-center px-3 py-2">
<LocalisedText language={subItem.nameLanguage ?? 'ja'}>
{subItem.name}
</LocalisedText>
@@ -513,27 +541,27 @@ const TopNav: FC<Props> = ({ user, locales }) => {
</div>)))
: ((visibleMenu[activeIdx]?.subMenu ?? []).length > 0
&& (
<div className="relative h-[40px]">
<div className="relative min-h-[40px]">
{animationsOff
? (
<div className="absolute inset-0 flex items-center px-3
overflow-x-auto whitespace-nowrap">
<div className="flex min-h-[40px] flex-wrap items-center px-3">
{(visibleMenu[activeIdx]?.subMenu ?? [])
.filter (item => item.visible ?? true)
.map ((item, i) => (
'component' in item
? (
<div key={`c-${ i }`}>
<div key={`c-${ i }`} className="min-w-0 max-w-full">
{item.component}
</div>)
: (
<div key={`l-${ i }`}>
<div key={`l-${ i }`} className="min-w-0 max-w-full">
<PrefetchLink
to={item.to}
target={item.to.slice (0, 2) === '//'
? '_blank'
: undefined}
className="top-nav-menu-link h-full flex items-center px-3">
className="top-nav-menu-link flex min-h-[40px]
items-center px-3 py-2">
<LocalisedText language={item.nameLanguage ?? 'ja'}>
{item.name}
</LocalisedText>
@@ -541,15 +569,14 @@ const TopNav: FC<Props> = ({ user, locales }) => {
</div>)))}
</div>)
: (
<AnimatePresence initial={false} custom={dir}>
<AnimatePresence initial={false} custom={dir} mode="popLayout">
<motion.div
key={activeIdx}
custom={dir}
variants={{ enter: (d: -1 | 1) => ({ y: d * 24, opacity: 0 }),
centre: { y: 0, opacity: 1 },
exit: (d: -1 | 1) => ({ y: (-d) * 24, opacity: 0 }) }}
className="absolute inset-0 flex items-center px-3
overflow-x-auto whitespace-nowrap"
className="relative flex min-h-[40px] flex-wrap items-center px-3"
initial="enter"
animate="centre"
exit="exit"
@@ -561,6 +588,7 @@ const TopNav: FC<Props> = ({ user, locales }) => {
? (
<motion.div
key={`c-${ i }`}
className="min-w-0 max-w-full"
transition={{ layout: topNavTransition }}
layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}>
{item.component}
@@ -568,6 +596,7 @@ const TopNav: FC<Props> = ({ user, locales }) => {
: (
<motion.div
key={`l-${ i }`}
className="min-w-0 max-w-full"
transition={{ layout: topNavTransition }}
layoutId={`submenu-${ visibleMenu[activeIdx].name }-${ i }`}>
<PrefetchLink
@@ -575,7 +604,8 @@ const TopNav: FC<Props> = ({ user, locales }) => {
target={item.to.slice (0, 2) === '//'
? '_blank'
: undefined}
className="top-nav-menu-link h-full flex items-center px-3">
className="top-nav-menu-link flex min-h-[40px]
items-center px-3 py-2">
<LocalisedText language={item.nameLanguage ?? 'ja'}>
{item.name}
</LocalisedText>
@@ -584,6 +614,7 @@ const TopNav: FC<Props> = ({ user, locales }) => {
</motion.div>
</AnimatePresence>)}
</div>))}
</div>
</motion.div>
</AnimatePresence>