Browse Source

#183

pull/185/head
みてるぞ 3 weeks ago
parent
commit
d5e35f0049
2 changed files with 127 additions and 31 deletions
  1. +1
    -1
      frontend/src/components/TagDetailSidebar.tsx
  2. +126
    -30
      frontend/src/components/TopNav.tsx

+ 1
- 1
frontend/src/components/TagDetailSidebar.tsx View File

@@ -76,7 +76,7 @@ export default (({ post }: Props) => {
return (
<SidebarComponent>
<TagSearch/>
<motion.div layout>
<motion.div key={post?.id ?? 0} layout>
{CATEGORIES.map ((cat: Category) => cat in tags && (
<motion.div layout className="my-3" key={cat}>
<SubsectionTitle>{categoryNames[cat]}</SubsectionTitle>


+ 126
- 30
frontend/src/components/TopNav.tsx View File

@@ -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<HTMLDivElement | null> (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<number | null> (null)
const [subDir, setSubDir] = useState<(-1) | 1> (1)
const [wikiId, setWikiId] = useState<number | null> (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<number> (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) => {
ぼざクリ タグ広場
</Link>

{menu.map ((item, i) => (
<Link key={i}
to={item.to}
className={cn ('hidden md:flex h-full items-center',
(location.pathname.startsWith (item.base || item.to)
? 'bg-yellow-200 dark:bg-red-950 px-4 font-bold'
: 'px-2'))}>
{item.name}
</Link>
))}
<div ref={navRef} className="relative hidden md:flex h-full items-center">
<div aria-hidden
className={cn ('absolute top-1/2 -translate-y-1/2 h-full rounded-md',
'bg-yellow-200 dark:bg-red-950',
'transition-[transform,width,opacity] duration-200 ease-out')}
style={{ width: hl.width,
transform: `translate(${ hl.left }px, -50%)`,
opacity: hl.visible ? 1 : 0 }}/>

{menu.map ((item, i) => (
<Link key={i}
to={item.to}
ref={el => {
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}
</Link>))}
</div>
</div>

<TopNavUser user={user}/>
@@ -126,15 +187,28 @@ export default (({ user }: Props) => {
</nav>

<div className="hidden md:flex bg-yellow-200 dark:bg-red-950
items-center w-full min-h-[40px] px-3">
{menu.find (item => location.pathname.startsWith (item.base || item.to))?.subMenu
.filter (item => item.visible ?? true)
.map ((item, i) => 'component' in item ? item.component : (
<Link key={i}
to={item.to}
className="h-full flex items-center px-3">
{item.name}
</Link>))}
items-center w-full min-h-[40px] px-3 overflow-hidden">
<AnimatePresence mode="wait" initial={false}>
<motion.div
key={activeIdx}
className="flex items-center"
initial={{ y: subDir * 24, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: subDir * 24, opacity: 0 }}
transition={{ duration: .1, ease: 'easeOut' }}>
{(menu[activeIdx]?.subMenu ?? [])
.filter (item => item.visible ?? true)
.map ((item, i) => (
'component' in item
? <Fragment key={`c-${ i }`}>{item.component}</Fragment>
: (
<Link key={`l-${ i }`}
to={item.to}
className="h-full flex items-center px-3">
{item.name}
</Link>)))}
</motion.div>
</AnimatePresence>
</div>

<AnimatePresence initial={false}>
@@ -167,16 +241,38 @@ export default (({ user }: Props) => {
}}>
{item.name}
</Link>
{i === openItemIdx && (
item.subMenu
.filter (subItem => subItem.visible ?? true)
.map ((subItem, j) => 'component' in subItem ? subItem.component : (
<Link key={j}
to={subItem.to}
className="w-full min-h-[36px] flex items-center pl-12
bg-yellow-50 dark:bg-red-950">
{subItem.name}
</Link>)))}

<AnimatePresence initial={false}>
{i === openItemIdx && (
<motion.div
key={`sp-sub-${ i }`}
className="w-full bg-yellow-50 dark:bg-red-950"
variants={{ closed: { clipPath: 'inset(0 0 100% 0)',
height: 0,
opacity: 0 },
open: { clipPath: 'inset(0 0 0% 0)',
height: 'auto',
opacity: 1 } }}
initial="closed"
animate="open"
exit="closed"
transition={{ duration: .2, ease: 'easeOut' }}>
{item.subMenu
.filter (subItem => subItem.visible ?? true)
.map ((subItem, j) => (
'component' in subItem
? (
<Fragment key={`sp-c-${ i }-${ j }`}>
{subItem.component}
</Fragment>)
: (
<Link key={`sp-l-${ i }-${ j }`}
to={subItem.to}
className="w-full min-h-[36px] flex items-center pl-12">
{subItem.name}
</Link>)))}
</motion.div>)}
</AnimatePresence>
</Fragment>))}
<TopNavUser user={user} sp/>
<Separator/>


Loading…
Cancel
Save