diff --git a/frontend/src/components/TopNav.tsx b/frontend/src/components/TopNav.tsx index 3b79a05..936b96b 100644 --- a/frontend/src/components/TopNav.tsx +++ b/frontend/src/components/TopNav.tsx @@ -14,7 +14,7 @@ import { fetchWikiPage } from '@/lib/wiki' import type { FC, MouseEvent } from 'react' -import type { Menu, Tag, User } from '@/types' +import type { Menu, MenuVisibleItem, Tag, User } from '@/types' type Props = { user: User | null } @@ -78,9 +78,9 @@ export default (({ user }: Props) => { const itemsRef = useRef<(HTMLAnchorElement | null)[]> ([]) const navRef = useRef (null) - const measure = () => { + const measure = (idx: number) => { const nav = navRef.current - const el = itemsRef.current[activeIdx < 0 ? menu.length : activeIdx] + const el = itemsRef.current[idx < 0 ? menu.length : idx] if (!(nav) || !(el)) { @@ -101,6 +101,7 @@ export default (({ user }: Props) => { width: 0, visible: false }) const [menuOpen, setMenuOpen] = useState (false) + const [moreVsbl, setMoreVsbl] = useState (false) const [openItemIdx, setOpenItemIdx] = useState (-1) const [wikiId, setWikiId] = useState (WikiIdBus.get ()) @@ -120,8 +121,8 @@ export default (({ user }: Props) => { const menu = menuOutline ({ tag, wikiId, user }) const activeIdx = (menu - .filter (item => item.visible ?? true) - .findIndex (item => location.pathname.startsWith (item.base || item.to!))) + .filter ((item): item is MenuVisibleItem => item.visible ?? true) + .findIndex (item => location.pathname.startsWith (item.base || item.to))) const prevActiveIdxRef = useRef (activeIdx) @@ -134,8 +135,8 @@ export default (({ user }: Props) => { const dir = dirRef.current useLayoutEffect (() => { - const raf = requestAnimationFrame (measure) - const onResize = () => requestAnimationFrame (measure) + const raf = requestAnimationFrame (() => measure (activeIdx)) + const onResize = () => requestAnimationFrame (() => measure (activeIdx)) addEventListener ('resize', onResize) return () => { @@ -151,8 +152,9 @@ export default (({ user }: Props) => { useEffect (() => { setMenuOpen (false) - setOpenItemIdx (menu.filter (item => item.visible ?? true).findIndex (item => ( - location.pathname.startsWith (item.base || item.to!)))) + setOpenItemIdx (menu + .filter ((item): item is MenuVisibleItem => item.visible ?? true) + .findIndex (item => location.pathname.startsWith (item.base || item.to))) }, [location]) return ( @@ -179,22 +181,36 @@ export default (({ user }: Props) => { transform: `translateX(${ hl.left }px)`, opacity: hl.visible ? 1 : 0 }}/> - {menu.filter (item => item.visible ?? true).map ((item, i) => ( - { - itemsRef.current[i] = el - }} - className={cn ('relative z-10 flex h-full items-center px-5', - (i === openItemIdx) && 'font-bold')}> - {item.name} - ))} + {menu.filter ((item): item is MenuVisibleItem => item.visible ?? true) + .map ((item, i) => ( + { + setMoreVsbl (false) + setTimeout (() => measure (activeIdx), 300) + }}> + { + itemsRef.current[i] = el + }} + className={cn ('relative z-10 flex h-full items-center px-5', + (i === openItemIdx) && 'font-bold', + moreVsbl && 'pointer-events-none')}> + {item.name} + + ))} { itemsRef.current[menu.length] = el }} + onClick={e => e.preventDefault ()} + onMouseEnter={() => { + setMoreVsbl (true) + measure (-1) + }} className={cn ('relative z-10 flex h-full items-center px-5', (openItemIdx < 0) && 'font-bold')}> その他 » @@ -217,45 +233,110 @@ export default (({ user }: Props) => { - {(menu[activeIdx]?.subMenu ?? []).length > 0 && ( - -
- - ({ 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" - initial="enter" - animate="centre" - exit="exit" - transition={{ duration: .2, ease: 'easeOut' }}> - {(menu[activeIdx]?.subMenu ?? []) - .filter (item => item.visible ?? true) - .map ((item, i) => ( - 'component' in item - ? {item.component} + { + if (!(moreVsbl)) + return + setMoreVsbl (false) + setTimeout (() => measure (activeIdx), 300) + }} + initial={{ height: 0 }} + animate={{ height: (moreVsbl ? 40 * menu.length : (activeIdx < 0 ? 0 : 40)) }} + exit={{ height: 0 }} + transition={{ duration: .2, ease: 'easeOut' }}> + {moreVsbl + ? ( + menu.map ((item, i) => ( +
+
+ + {item.name} + + {item.subMenu + .filter (subItem => subItem.visible ?? true) + .map ((subItem, j) => ( + 'component' in subItem + ? ( + x.visible ?? true)[activeIdx]?.name + === item.name) + ? { layoutId: `submenu-${ item.name }-${ j }` } + : { initial: { y: -40, opacity: 0 }, + animate: { y: 0, opacity: 1 }, + exit: { y: -40, opacity: 0 }, + transition: { duration: .2, ease: 'easeOut' } })}> + {subItem.component} + ) : ( - - {item.name} - )))} - - -
- )} + x.visible ?? true)[activeIdx]?.name + === item.name) + && { layoutId: `submenu-${ item.name }-${ j }` })}> + + {subItem.name} + + )))} +
+
))) + : ((menu.filter (item => item.visible ?? true)[activeIdx]?.subMenu ?? []).length > 0 + && ( +
+ + ({ 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" + initial="enter" + animate="centre" + exit="exit" + transition={{ duration: .2, ease: 'easeOut' }}> + {(menu.filter (item => item.visible ?? true)[activeIdx]?.subMenu ?? []) + .filter (item => item.visible ?? true) + .map ((item, i) => ( + 'component' in item + ? ( + x.visible ?? true)[activeIdx].name }-${ + i }`}> + {item.component} + ) + : ( + x.visible ?? true)[activeIdx].name }-${ + i }`}> + + {item.name} + + )))} + + +
))} +
@@ -273,10 +354,11 @@ export default (({ user }: Props) => { exit="closed" transition={{ duration: .2, ease: 'easeOut' }}> - {menu.filter (item => item.visible ?? true).map ((item, i) => ( + {menu.filter ((item): item is MenuVisibleItem => item.visible ?? true) + .map ((item, i) => ( { ref={(el: (HTMLAnchorElement | null)) => { itemsRef.current[menu.length] = el }} - className="w-full min-h-[40px] flex items-center pl-8"> + className={cn ('w-full min-h-[40px] flex items-center pl-8', + ((openItemIdx < 0) + && 'font-bold bg-yellow-50 dark:bg-red-950'))}> その他 » diff --git a/frontend/src/components/layout/MainArea.tsx b/frontend/src/components/layout/MainArea.tsx index 273793d..1067101 100644 --- a/frontend/src/components/layout/MainArea.tsx +++ b/frontend/src/components/layout/MainArea.tsx @@ -8,7 +8,6 @@ type Props = { export default (({ children, className }: Props) => ( -
+
{children}
)) satisfies FC diff --git a/frontend/src/components/layout/SidebarComponent.tsx b/frontend/src/components/layout/SidebarComponent.tsx index d6e8803..946a251 100644 --- a/frontend/src/components/layout/SidebarComponent.tsx +++ b/frontend/src/components/layout/SidebarComponent.tsx @@ -6,10 +6,7 @@ type Props = { children: ReactNode } export default (({ children }: Props) => ( -
+