feat: アニメーションの一部実装(#176) (#178)
#176 完了 Co-authored-by: miteruzo <miteruzo@naver.com> Reviewed-on: #178
This commit was merged in pull request #178.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import TagLink from '@/components/TagLink'
|
||||
@@ -18,18 +19,23 @@ const renderTagTree = (
|
||||
tag: Tag,
|
||||
nestLevel: number,
|
||||
path: string,
|
||||
): ReactNode[] => {
|
||||
): ReactNode[] => {
|
||||
const key = `${ path }-${ tag.id }`
|
||||
|
||||
const self = (
|
||||
<li key={key} className="mb-1">
|
||||
<motion.li
|
||||
key={key}
|
||||
layout
|
||||
transition={{ duration: .2, ease: 'easeOut' }}
|
||||
className="mb-1">
|
||||
<TagLink tag={tag} nestLevel={nestLevel}/>
|
||||
</li>)
|
||||
</motion.li>)
|
||||
|
||||
return [self,
|
||||
...(tag.children
|
||||
?.sort ((a, b) => a.name < b.name ? -1 : 1)
|
||||
.flatMap (child => renderTagTree (child, nestLevel + 1, key)) ?? [])]
|
||||
...((tag.children
|
||||
?.sort ((a, b) => a.name < b.name ? -1 : 1)
|
||||
.flatMap (child => renderTagTree (child, nestLevel + 1, key)))
|
||||
?? [])]
|
||||
}
|
||||
|
||||
|
||||
@@ -70,55 +76,60 @@ export default (({ post }: Props) => {
|
||||
return (
|
||||
<SidebarComponent>
|
||||
<TagSearch/>
|
||||
{CATEGORIES.map ((cat: Category) => cat in tags && (
|
||||
<div className="my-3" key={cat}>
|
||||
<SubsectionTitle>{categoryNames[cat]}</SubsectionTitle>
|
||||
<ul>
|
||||
{tags[cat].map (tag => renderTagTree (tag, 0, `cat-${ cat }`))}
|
||||
</ul>
|
||||
</div>))}
|
||||
{post && (
|
||||
<div>
|
||||
<SectionTitle>情報</SectionTitle>
|
||||
<ul>
|
||||
<li>Id.: {post.id}</li>
|
||||
{/* TODO: uploadedUser の取得を対応したらコメント外す */}
|
||||
{/*
|
||||
<li>
|
||||
<>耕作者: </>
|
||||
{post.uploadedUser
|
||||
? (
|
||||
<Link to={`/users/${ post.uploadedUser.id }`}>
|
||||
{post.uploadedUser.name || '名もなきニジラー'}
|
||||
</Link>)
|
||||
: 'bot操作'}
|
||||
</li>
|
||||
*/}
|
||||
<li>耕作日時: {(new Date (post.createdAt)).toLocaleString ()}</li>
|
||||
<li>
|
||||
<>リンク: </>
|
||||
<a
|
||||
className="break-all"
|
||||
href={post.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow">
|
||||
{post.url}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{/* TODO: 表示形式きしょすぎるので何とかする */}
|
||||
<>オリジナルの投稿日時: </>
|
||||
{!(post.originalCreatedFrom) && !(post.originalCreatedBefore)
|
||||
? '不明'
|
||||
: (
|
||||
<>
|
||||
{post.originalCreatedFrom
|
||||
&& `${ (new Date (post.originalCreatedFrom)).toLocaleString () } 以降 `}
|
||||
{post.originalCreatedBefore
|
||||
&& `${ (new Date (post.originalCreatedBefore)).toLocaleString () } より前`}
|
||||
</>)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>)}
|
||||
<motion.div layout>
|
||||
{CATEGORIES.map ((cat: Category) => cat in tags && (
|
||||
<motion.div layout className="my-3" key={cat}>
|
||||
<SubsectionTitle>{categoryNames[cat]}</SubsectionTitle>
|
||||
|
||||
<motion.ul layout>
|
||||
<AnimatePresence initial={false}>
|
||||
{tags[cat].map (tag => renderTagTree (tag, 0, `cat-${ cat }`))}
|
||||
</AnimatePresence>
|
||||
</motion.ul>
|
||||
</motion.div>))}
|
||||
{post && (
|
||||
<div>
|
||||
<SectionTitle>情報</SectionTitle>
|
||||
<ul>
|
||||
<li>Id.: {post.id}</li>
|
||||
{/* TODO: uploadedUser の取得を対応したらコメント外す */}
|
||||
{/*
|
||||
<li>
|
||||
<>耕作者: </>
|
||||
{post.uploadedUser
|
||||
? (
|
||||
<Link to={`/users/${ post.uploadedUser.id }`}>
|
||||
{post.uploadedUser.name || '名もなきニジラー'}
|
||||
</Link>)
|
||||
: 'bot操作'}
|
||||
</li>
|
||||
*/}
|
||||
<li>耕作日時: {(new Date (post.createdAt)).toLocaleString ()}</li>
|
||||
<li>
|
||||
<>リンク: </>
|
||||
<a
|
||||
className="break-all"
|
||||
href={post.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow">
|
||||
{post.url}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{/* TODO: 表示形式きしょすぎるので何とかする */}
|
||||
<>オリジナルの投稿日時: </>
|
||||
{!(post.originalCreatedFrom) && !(post.originalCreatedBefore)
|
||||
? '不明'
|
||||
: (
|
||||
<>
|
||||
{post.originalCreatedFrom
|
||||
&& `${ (new Date (post.originalCreatedFrom)).toLocaleString () } 以降 `}
|
||||
{post.originalCreatedBefore
|
||||
&& `${ (new Date (post.originalCreatedBefore)).toLocaleString () } より前`}
|
||||
</>)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>)}
|
||||
</motion.div>
|
||||
</SidebarComponent>)
|
||||
}) satisfies FC<Props>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from 'axios'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
@@ -8,7 +9,6 @@ import SectionTitle from '@/components/common/SectionTitle'
|
||||
import SidebarComponent from '@/components/layout/SidebarComponent'
|
||||
import { API_BASE_URL } from '@/config'
|
||||
import { CATEGORIES } from '@/consts'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
import type { FC } from 'react'
|
||||
|
||||
@@ -61,37 +61,52 @@ export default (({ posts }: Props) => {
|
||||
return (
|
||||
<SidebarComponent>
|
||||
<TagSearch/>
|
||||
<div className={cn (!(tagsVsbl) && 'hidden', 'md:block mt-4')}>
|
||||
<SectionTitle>タグ</SectionTitle>
|
||||
<ul>
|
||||
{CATEGORIES.flatMap (cat => cat in tags ? (
|
||||
tags[cat].map (tag => (
|
||||
<li key={tag.id} className="mb-1">
|
||||
<TagLink tag={tag}/>
|
||||
</li>))) : [])}
|
||||
</ul>
|
||||
<SectionTitle>関聯</SectionTitle>
|
||||
{posts.length > 0 && (
|
||||
<a href="#"
|
||||
onClick={ev => {
|
||||
ev.preventDefault ()
|
||||
void ((async () => {
|
||||
try
|
||||
{
|
||||
const { data } = await axios.get (`${ API_BASE_URL }/posts/random`,
|
||||
{ params: { tags: tagsQuery.split (' ').filter (e => e !== '').join (' '),
|
||||
match: (anyFlg ? 'any' : 'all') } })
|
||||
navigate (`/posts/${ (data as Post).id }`)
|
||||
}
|
||||
catch
|
||||
{
|
||||
;
|
||||
}
|
||||
}) ())
|
||||
}}>
|
||||
ランダム
|
||||
</a>)}
|
||||
</div>
|
||||
|
||||
<AnimatePresence initial={false}>
|
||||
{tagsVsbl && (
|
||||
<motion.div
|
||||
key="sptags"
|
||||
className="md:block mt-4"
|
||||
variants={{ hidden: { clipPath: 'inset(0 0 100% 0)',
|
||||
height: 0 },
|
||||
visible: { clipPath: 'inset(0 0 0% 0)',
|
||||
height: 'auto'} }}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="hidden"
|
||||
transition={{ duration: .2, ease: 'easeOut' }}>
|
||||
<SectionTitle>タグ</SectionTitle>
|
||||
<ul>
|
||||
{CATEGORIES.flatMap (cat => cat in tags ? (
|
||||
tags[cat].map (tag => (
|
||||
<li key={tag.id} className="mb-1">
|
||||
<TagLink tag={tag}/>
|
||||
</li>))) : [])}
|
||||
</ul>
|
||||
<SectionTitle>関聯</SectionTitle>
|
||||
{posts.length > 0 && (
|
||||
<a href="#"
|
||||
onClick={ev => {
|
||||
ev.preventDefault ()
|
||||
void ((async () => {
|
||||
try
|
||||
{
|
||||
const { data } = await axios.get (`${ API_BASE_URL }/posts/random`,
|
||||
{ params: { tags: tagsQuery.split (' ').filter (e => e !== '').join (' '),
|
||||
match: (anyFlg ? 'any' : 'all') } })
|
||||
navigate (`/posts/${ (data as Post).id }`)
|
||||
}
|
||||
catch
|
||||
{
|
||||
;
|
||||
}
|
||||
}) ())
|
||||
}}>
|
||||
ランダム
|
||||
</a>)}
|
||||
</motion.div>)}
|
||||
</AnimatePresence>
|
||||
|
||||
<a href="#"
|
||||
className="md:hidden block my-2 text-center text-sm
|
||||
text-gray-500 hover:text-gray-400
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import axios from 'axios'
|
||||
import toCamel from 'camelcase-keys'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { Fragment, useState, useEffect } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
|
||||
@@ -136,37 +137,50 @@ export default (({ user }: Props) => {
|
||||
</Link>))}
|
||||
</div>
|
||||
|
||||
<div className={cn (menuOpen ? 'flex flex-col md:hidden' : 'hidden',
|
||||
'bg-yellow-200 dark:bg-red-975 items-start')}>
|
||||
<Separator/>
|
||||
{menu.map ((item, i) => (
|
||||
<Fragment key={i}>
|
||||
<Link to={i === openItemIdx ? item.to : '#'}
|
||||
className={cn ('w-full min-h-[40px] flex items-center pl-8',
|
||||
((i === openItemIdx)
|
||||
&& 'font-bold bg-yellow-50 dark:bg-red-950'))}
|
||||
onClick={ev => {
|
||||
if (i !== openItemIdx)
|
||||
{
|
||||
ev.preventDefault ()
|
||||
setOpenItemIdx (i)
|
||||
}
|
||||
}}>
|
||||
{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>)))}
|
||||
</Fragment>))}
|
||||
<TopNavUser user={user} sp/>
|
||||
<Separator/>
|
||||
</div>
|
||||
<AnimatePresence initial={false}>
|
||||
{menuOpen && (
|
||||
<motion.div
|
||||
key="spmenu"
|
||||
className={cn ('flex flex-col md:hidden',
|
||||
'bg-yellow-200 dark:bg-red-975 items-start')}
|
||||
variants={{ closed: { clipPath: 'inset(0 0 100% 0)',
|
||||
height: 0 },
|
||||
open: { clipPath: 'inset(0 0 0% 0)',
|
||||
height: 'auto' } }}
|
||||
initial="closed"
|
||||
animate="open"
|
||||
exit="closed"
|
||||
transition={{ duration: .2, ease: 'easeOut' }}>
|
||||
<Separator/>
|
||||
{menu.map ((item, i) => (
|
||||
<Fragment key={i}>
|
||||
<Link to={i === openItemIdx ? item.to : '#'}
|
||||
className={cn ('w-full min-h-[40px] flex items-center pl-8',
|
||||
((i === openItemIdx)
|
||||
&& 'font-bold bg-yellow-50 dark:bg-red-950'))}
|
||||
onClick={ev => {
|
||||
if (i !== openItemIdx)
|
||||
{
|
||||
ev.preventDefault ()
|
||||
setOpenItemIdx (i)
|
||||
}
|
||||
}}>
|
||||
{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>)))}
|
||||
</Fragment>))}
|
||||
<TopNavUser user={user} sp/>
|
||||
<Separator/>
|
||||
</motion.div>)}
|
||||
</AnimatePresence>
|
||||
</>)
|
||||
}) satisfies FC<Props>
|
||||
|
||||
Reference in New Issue
Block a user