diff --git a/frontend/src/components/TagDetailSidebar.tsx b/frontend/src/components/TagDetailSidebar.tsx index 0006738..5d02358 100644 --- a/frontend/src/components/TagDetailSidebar.tsx +++ b/frontend/src/components/TagDetailSidebar.tsx @@ -76,7 +76,7 @@ export default (({ post }: Props) => { return ( - + {CATEGORIES.map ((cat: Category) => cat in tags && ( {categoryNames[cat]} diff --git a/frontend/src/components/TopNav.tsx b/frontend/src/components/TopNav.tsx index 35a1a57..57bf4a7 100644 --- a/frontend/src/components/TopNav.tsx +++ b/frontend/src/components/TopNav.tsx @@ -1,7 +1,7 @@ import axios from 'axios' import toCamel from 'camelcase-keys' import { AnimatePresence, motion } from 'framer-motion' -import { Fragment, useState, useEffect } from 'react' +import { Fragment, useEffect, useLayoutEffect, useRef, useState } from 'react' import { Link, useLocation } from 'react-router-dom' import Separator from '@/components/MenuSeparator' @@ -20,9 +20,31 @@ type Props = { user: User | null } export default (({ user }: Props) => { const location = useLocation () + const itemRefs = useRef<(HTMLAnchorElement | null)[]> ([]) + const navRef = useRef (null) + + const measure = () => { + const nav = navRef.current + const el = itemRefs.current[activeIdx] + if (!(nav) || !(el) || activeIdx < 0) + return + + const navRect = nav.getBoundingClientRect () + const elRect = el.getBoundingClientRect () + + setHl ({ left: elRect.left - navRect.left, + width: elRect.width, + visible: true }) + } + + const [hl, setHl] = useState<{ left: number; width: number; visible: boolean }> ({ + left: 0, + width: 0, + visible: false }) const [menuOpen, setMenuOpen] = useState (false) const [openItemIdx, setOpenItemIdx] = useState (-1) const [postCount, setPostCount] = useState (null) + const [subDir, setSubDir] = useState<(-1) | 1> (1) const [wikiId, setWikiId] = useState (WikiIdBus.get ()) const wikiPageFlg = Boolean (/^\/wiki\/(?!new|changes)[^\/]+/.test (location.pathname) && wikiId) @@ -54,11 +76,38 @@ export default (({ user }: Props) => { { name: 'お前', to: `/users/${ user?.id }`, visible: false }, { name: '設定', to: '/users/settings', visible: Boolean (user) }] }] + const activeIdx = menu.findIndex (item => location.pathname.startsWith (item.base || item.to)) + + const prevActiveIdxRef = useRef (activeIdx) + + useLayoutEffect (() => { + if (activeIdx < 0) + return + + const raf = requestAnimationFrame (measure) + const onResize = () => requestAnimationFrame (measure) + + addEventListener ('resize', onResize) + return () => { + cancelAnimationFrame (raf) + removeEventListener ('resize', onResize) + } + }, [activeIdx]) + useEffect (() => { const unsubscribe = WikiIdBus.subscribe (setWikiId) return () => unsubscribe () }, []) + useEffect (() => { + const prev = prevActiveIdxRef.current + if (prev !== activeIdx) + { + setSubDir (activeIdx > prev ? 1 : -1) + prevActiveIdxRef.current = activeIdx + } + }, [activeIdx]) + useEffect (() => { setMenuOpen (false) setOpenItemIdx (menu.findIndex (item => ( @@ -99,16 +148,28 @@ export default (({ user }: Props) => { ぼざクリ タグ広場 - {menu.map ((item, i) => ( - - {item.name} - - ))} +
+
+ + {menu.map ((item, i) => ( + { + itemRefs.current[i] = el + }} + className={cn ('relative z-10 flex h-full items-center px-5', + ((i === openItemIdx) + ? 'font-bold' + : 'opacity-90 hover:opacity-100'))}> + {item.name} + ))} +
@@ -126,15 +187,28 @@ export default (({ user }: Props) => {
- {menu.find (item => location.pathname.startsWith (item.base || item.to))?.subMenu - .filter (item => item.visible ?? true) - .map ((item, i) => 'component' in item ? item.component : ( - - {item.name} - ))} + items-center w-full min-h-[40px] px-3 overflow-hidden"> + + + {(menu[activeIdx]?.subMenu ?? []) + .filter (item => item.visible ?? true) + .map ((item, i) => ( + 'component' in item + ? {item.component} + : ( + + {item.name} + )))} + +
@@ -167,16 +241,38 @@ export default (({ user }: Props) => { }}> {item.name} - {i === openItemIdx && ( - item.subMenu - .filter (subItem => subItem.visible ?? true) - .map ((subItem, j) => 'component' in subItem ? subItem.component : ( - - {subItem.name} - )))} + + + {i === openItemIdx && ( + + {item.subMenu + .filter (subItem => subItem.visible ?? true) + .map ((subItem, j) => ( + 'component' in subItem + ? ( + + {subItem.component} + ) + : ( + + {subItem.name} + )))} + )} + ))}