このコミットが含まれているのは:
2026-07-06 23:37:21 +09:00
コミット f2651a987a
17個のファイルの変更273行の追加68行の削除
+50 -5
ファイルの表示
@@ -1,5 +1,5 @@
import { AnimatePresence, LayoutGroup, MotionConfig, motion } from 'framer-motion'
import { useEffect, useMemo, useState } from 'react'
import { Fragment, useEffect, useMemo, useState } from 'react'
import { BrowserRouter,
Navigate,
Route,
@@ -14,6 +14,7 @@ import { Toaster } from '@/components/ui/toaster'
import { apiPost, isApiError } from '@/lib/api'
import { applyClientAnimationMode,
applyClientAppearance,
type ClientAnimationMode,
fetchUserThemeSlots,
fetchUserSettings,
getClientThemeMode,
@@ -61,11 +62,51 @@ import type { Dispatch, FC, SetStateAction } from 'react'
import type { User } from '@/types'
const RouteTransitionWrapper = ({ user, setUser }: {
const RouteTransitionWrapper = ({ animationMode, user, setUser }: {
animationMode: ClientAnimationMode
user: User | null
setUser: Dispatch<SetStateAction<User | null>> }) => {
const location = useLocation ()
if (animationMode === 'off')
{
return (
<Routes location={location}>
<Route path="/" element={<Navigate to="/posts" replace/>}/>
<Route path="/posts" element={<PostListPage/>}/>
<Route path="/posts/new" element={<PostNewPage user={user}/>}/>
<Route path="/posts/search" element={<PostSearchPage/>}/>
<Route path="/posts/:id" element={<PostDetailRoute user={user}/>}/>
<Route path="/posts/changes" element={<PostHistoryPage/>}/>
<Route path="/tags" element={<TagListPage/>}/>
<Route path="/tags/:id" element={<TagDetailPage/>}/>
<Route path="/tags/:id/deerjikists" element={<DeerjikistDetailPage/>}/>
<Route path="/tags/nico" element={<NicoTagListPage user={user}/>}/>
<Route path="/nico/tags" element={<NicoTagListPage user={user}/>}/>
<Route path="/tags/changes" element={<TagHistoryPage/>}/>
<Route path="/theatres/:id" element={<TheatreDetailPage user={user}/>}/>
<Route path="/materials" element={<MaterialBasePage/>}>
<Route index element={<MaterialListPage/>}/>
<Route path="changes" element={<MaterialHistoryPage/>}/>
<Route path="new" element={<MaterialNewPage/>}/>
<Route path="suppressions" element={<MaterialSyncSuppressionsPage/>}/>
<Route path=":id" element ={<MaterialDetailPage/>}/>
</Route>
<Route path="/wiki" element={<WikiSearchPage/>}/>
<Route path="/wiki/:title" element={<WikiDetailPage/>}/>
<Route path="/wiki/new" element={<WikiNewPage user={user}/>}/>
<Route path="/wiki/:id/edit" element={<WikiEditPage user={user}/>}/>
<Route path="/wiki/:id/diff" element={<WikiDiffPage/>}/>
<Route path="/wiki/changes" element={<WikiHistoryPage/>}/>
<Route path="/users/settings" element={<SettingPage user={user} setUser={setUser}/>}/>
<Route path="/settings" element={<Navigate to="/users/settings" replace/>}/>
<Route path="/tos" element={<TOSPage/>}/>
<Route path="/gekanator" element={<GekanatorPage user={user}/>}/>
<Route path="/more" element={<MorePage/>}/>
<Route path="*" element={<NotFound/>}/>
</Routes>)
}
return (
<AnimatePresence mode="wait">
<Routes location={location}>
@@ -118,6 +159,7 @@ const App: FC = () => {
const [status, setStatus] = useState (200)
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const LayoutWrapper = animationMode === 'off' ? Fragment : LayoutGroup
const appLayoutTransition = useMemo (
() => (
@@ -227,15 +269,18 @@ const App: FC = () => {
? 'user'
: 'always'
}>
<LayoutGroup>
<LayoutWrapper>
<motion.div
layout={animationMode === 'off' ? false : 'position'}
transition={{ layout: appLayoutTransition }}
className="relative flex flex-col h-dvh w-full overflow-y-hidden">
<TopNav user={user}/>
<RouteTransitionWrapper user={user} setUser={setUser}/>
<RouteTransitionWrapper
animationMode={animationMode}
user={user}
setUser={setUser}/>
</motion.div>
</LayoutGroup>
</LayoutWrapper>
</MotionConfig>
<Toaster/>
+12 -2
ファイルの表示
@@ -4,6 +4,8 @@ import { motion } from 'framer-motion'
import { useRef } from 'react'
import TagLink from '@/components/TagLink'
import { clientAnimationTransition } from '@/lib/clientAnimation'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { cn } from '@/lib/utils'
import type { CSSProperties, FC, MutableRefObject } from 'react'
@@ -20,6 +22,12 @@ type Props = {
const DraggableDroppableTagRow: FC<Props> = ({ tag, nestLevel, pathKey, parentTagId, suppressClickRef, sp }) => {
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const layoutTransition = clientAnimationTransition (
animationMode,
{ normal: { duration: .2, ease: 'easeOut' as const } },
)
const dndId = `tag-node:${ pathKey }`
const downPosRef = useRef<{ x: number; y: number } | null> (null)
@@ -94,8 +102,10 @@ const DraggableDroppableTagRow: FC<Props> = ({ tag, nestLevel, pathKey, parentTa
{...listeners}>
<motion.div
className="flex min-w-0 max-w-full items-baseline overflow-hidden"
transition={{ layout: { duration: .2, ease: 'easeOut' } }}
layoutId={`tag-${ sp ? 'sp-' : '' }${ tag.id }`}>
transition={{ layout: layoutTransition }}
layoutId={animationMode === 'off'
? undefined
: `tag-${ sp ? 'sp-' : '' }${ tag.id }`}>
<TagLink tag={tag} nestLevel={nestLevel}/>
</motion.div>
</div>)
+5 -1
ファイルの表示
@@ -1,3 +1,4 @@
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { cn } from '@/lib/utils'
import { useEffect, useRef, useState } from 'react'
@@ -20,6 +21,8 @@ const MIN_MARQUEE_OVERFLOW_PX = 1
const ResponsiveMarqueeText: FC<Props> = (
{ text, className, truncateOnMobile = false, title },
) => {
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const outerRef = useRef<HTMLSpanElement | null> (null)
const staticRef = useRef<HTMLSpanElement | null> (null)
const animatedRef = useRef<HTMLSpanElement | null> (null)
@@ -98,6 +101,7 @@ const ResponsiveMarqueeText: FC<Props> = (
const animated = animatedRef.current
const canMarquee = (
active
&& animationMode !== 'off'
&& desktopMarqueeEnabled
&& overflowPx >= MIN_MARQUEE_OVERFLOW_PX
&& animated != null)
@@ -207,7 +211,7 @@ const ResponsiveMarqueeText: FC<Props> = (
resetAnimated ()
setMarqueeVisible (false)
}
}, [active, desktopMarqueeEnabled, overflowPx, text])
}, [active, animationMode, desktopMarqueeEnabled, overflowPx, text])
return (
<span
+14 -4
ファイルの表示
@@ -20,6 +20,7 @@ import SidebarComponent from '@/components/layout/SidebarComponent'
import { toast } from '@/components/ui/use-toast'
import { CATEGORIES, CATEGORY_NAMES } from '@/consts'
import { apiDelete, apiGet, apiPatch, apiPost } from '@/lib/api'
import { clientAnimationTransition } from '@/lib/clientAnimation'
import { postsKeys, tagsKeys } from '@/lib/queryKeys'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { dateString, originalCreatedAtString } from '@/lib/utils'
@@ -189,6 +190,11 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
const qc = useQueryClient ()
const behaviourSettings = useClientBehaviourSettings ()
const tagRelationDisplay = behaviourSettings.tagRelationDisplay ?? 'grouped'
const animationMode = behaviourSettings.animation ?? 'normal'
const layoutTransition = clientAnimationTransition (
animationMode,
{ normal: { duration: .2, ease: 'easeOut' as const } },
)
const baseTags = useMemo<TagByCategory> (() => {
const tagsTmp = { } as TagByCategory
@@ -360,8 +366,10 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
<div className="my-3" key={cat}>
<SubsectionTitle>
<motion.div
layoutId={`tag-${ sp ? 'sp-' : '' }${ cat }`}
transition={{ layout: { duration: .2, ease: 'easeOut' } }}>
layoutId={animationMode === 'off'
? undefined
: `tag-${ sp ? 'sp-' : '' }${ cat }`}
transition={{ layout: layoutTransition }}>
{CATEGORY_NAMES[cat]}
</motion.div>
</SubsectionTitle>
@@ -393,8 +401,10 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
})}
{post && (
<motion.div
layoutId={`post-info-${ sp }`}
transition={{ layout: { duration: .2, ease: 'easeOut' } }}>
layoutId={animationMode === 'off'
? undefined
: `post-info-${ sp }`}
transition={{ layout: layoutTransition }}>
<SectionTitle></SectionTitle>
<ul>
<li>Id.: {post.id}</li>
+40 -18
ファイルの表示
@@ -8,6 +8,8 @@ import SectionTitle from '@/components/common/SectionTitle'
import SidebarComponent from '@/components/layout/SidebarComponent'
import { CATEGORIES } from '@/consts'
import { apiGet } from '@/lib/api'
import { clientAnimationTransition } from '@/lib/clientAnimation'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import type { FC, MouseEvent } from 'react'
@@ -21,6 +23,13 @@ type Props = { posts: Post[]
const TagSidebar: FC<Props> = ({ posts, onClick }) => {
const navigate = useNavigate ()
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const animationsOff = animationMode === 'off'
const layoutTransition = clientAnimationTransition (
animationMode,
{ normal: { duration: .2, ease: 'easeOut' as const } },
)
const [tagsVsbl, setTagsVsbl] = useState (false)
const [tags, setTags] = useState<TagByCategory> ({ })
@@ -65,12 +74,18 @@ const TagSidebar: FC<Props> = ({ posts, onClick }) => {
{CATEGORIES.flatMap (cat => cat in tags ? (
tags[cat].map (tag => (
<li key={tag.id} className="mb-1 min-w-0 max-w-full overflow-hidden">
<motion.div
className="flex min-w-0 max-w-full items-baseline overflow-hidden"
transition={{ layout: { duration: .2, ease: 'easeOut' } }}
layoutId={`tag-${ tag.id }`}>
<TagLink tag={tag} onClick={onClick}/>
</motion.div>
{animationsOff
? (
<div className="flex min-w-0 max-w-full items-baseline overflow-hidden">
<TagLink tag={tag} onClick={onClick}/>
</div>)
: (
<motion.div
className="flex min-w-0 max-w-full items-baseline overflow-hidden"
transition={{ layout: layoutTransition }}
layoutId={`tag-${ tag.id }`}>
<TagLink tag={tag} onClick={onClick}/>
</motion.div>)}
</li>))) : [])}
</ul>
<SectionTitle></SectionTitle>
@@ -103,18 +118,25 @@ const TagSidebar: FC<Props> = ({ posts, onClick }) => {
{posts.length > 0 && TagBlock}
</div>
<AnimatePresence initial={false}>
{tagsVsbl && (
<motion.div
key="sptags"
className="md:hidden overflow-hidden"
initial={{ height: 0 }}
animate={{ height: 'auto' }}
exit={{ height: 0 }}
transition={{ duration: .2, ease: 'easeOut' }}>
{posts.length > 0 && TagBlock}
</motion.div>)}
</AnimatePresence>
{animationsOff
? (
tagsVsbl && (
<div className="md:hidden overflow-hidden">
{posts.length > 0 && TagBlock}
</div>))
: (
<AnimatePresence initial={false}>
{tagsVsbl && (
<motion.div
key="sptags"
className="md:hidden overflow-hidden"
initial={{ height: 0 }}
animate={{ height: 'auto' }}
exit={{ height: 0 }}
transition={layoutTransition}>
{posts.length > 0 && TagBlock}
</motion.div>)}
</AnimatePresence>)}
<a href="#"
className="md:hidden block my-2 text-center text-sm
+19 -8
ファイルの表示
@@ -1,5 +1,7 @@
import { motion } from 'framer-motion'
import { clientAnimationTransition } from '@/lib/clientAnimation'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { cn } from '@/lib/utils'
import type { FC, ReactNode } from 'react'
@@ -9,12 +11,21 @@ type Props = {
className?: string }
const MainArea: FC<Props> = ({ children, className }) => (
<motion.main
transition={{ layout: { duration: .2, ease: 'easeOut' } }}
className={cn ('flex-1 overflow-y-auto p-4', className)}
layout="position">
{children}
</motion.main>)
const MainArea: FC<Props> = ({ children, className }) => {
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const layoutTransition = clientAnimationTransition (
animationMode,
{ normal: { duration: .2, ease: 'easeOut' as const } },
)
export default MainArea
return (
<motion.main
transition={{ layout: layoutTransition }}
className={cn ('flex-1 overflow-y-auto p-4', className)}
layout={animationMode === 'off' ? false : 'position'}>
{children}
</motion.main>)
}
export default MainArea
+10 -2
ファイルの表示
@@ -1,6 +1,8 @@
import { motion } from 'framer-motion'
import { Helmet } from 'react-helmet-async'
import { clientAnimationTransition } from '@/lib/clientAnimation'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { cn } from '@/lib/utils'
import type { CSSProperties, FC, MouseEvent, PointerEvent, ReactNode } from 'react'
@@ -68,6 +70,12 @@ const SidebarComponent: FC<Props> = ({
sidebarKey,
side = 'left',
}) => {
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const layoutTransition = clientAnimationTransition (
animationMode,
{ normal: { duration: .2, ease: 'easeOut' as const } },
)
const rootRef = useRef<HTMLDivElement | null> (null)
const maxWidthRef = useRef (maxWidth)
const getContainerWidth = useCallback (() => {
@@ -305,8 +313,8 @@ const SidebarComponent: FC<Props> = ({
return (
<motion.div
ref={rootRef}
layout="position"
transition={{ layout: { duration: .2, ease: 'easeOut' } }}
layout={animationMode === 'off' ? false : 'position'}
transition={{ layout: layoutTransition }}
style={style}
className={cn (
'relative w-full md:w-[var(--sidebar-width)] md:shrink-0 md:h-full',
+4 -2
ファイルの表示
@@ -137,9 +137,11 @@
:root[data-animation='off'] *::before,
:root[data-animation='off'] *::after
{
animation-duration: 0.001ms !important;
animation-duration: 0ms !important;
animation-delay: 0ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
transition-duration: 0ms !important;
transition-delay: 0ms !important;
}
a
+34
ファイルの表示
@@ -0,0 +1,34 @@
import type { Transition } from 'framer-motion'
import type { ClientAnimationMode } from '@/lib/settings'
type ClientAnimationTransitionOptions = {
normal: Transition
reduced?: Transition
off?: Transition }
export const clientAnimationsOff = (
animationMode: ClientAnimationMode,
): boolean =>
animationMode === 'off'
export const clientAnimationTransition = (
animationMode: ClientAnimationMode,
{ normal, reduced, off }: ClientAnimationTransitionOptions,
): Transition => {
if (animationMode === 'off')
return off ?? { duration: 0 }
if (animationMode === 'reduced')
return reduced ?? { duration: .08, ease: 'linear' as const }
return normal
}
export const clientScrollBehaviour = (
animationMode: ClientAnimationMode,
): ScrollBehavior =>
animationMode === 'off' ? 'auto' : 'smooth'
+7 -1
ファイルの表示
@@ -28,6 +28,7 @@ import {
getClientGekanatorBackgroundMotion,
setClientGekanatorBackgroundMotion,
} from '@/lib/settings'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { cn } from '@/lib/utils'
import type { FC } from 'react'
@@ -3677,6 +3678,8 @@ const expectedAnswerFor = (
const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const storedGame = useMemo (loadStoredGame, [])
const hasStoredRestore = storedGame != null && isStoredPhase (storedGame.phase)
const queryClient = useQueryClient ()
@@ -4069,9 +4072,12 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
return reviewGuessedPostId === reviewCorrectPostId
}) ()
const effectiveBackgroundMotionMode = (() => {
if (animationMode === 'off')
return 'off'
if (backgroundMotionMode === 'off')
return 'off'
if (prefersReducedMotion)
if (animationMode === 'reduced' || prefersReducedMotion)
return 'calm'
return backgroundMotionMode
+7 -2
ファイルの表示
@@ -9,14 +9,18 @@ import PageTitle from '@/components/common/PageTitle'
import Pagination from '@/components/common/Pagination'
import MainArea from '@/components/layout/MainArea'
import { SITE_TITLE } from '@/config'
import { clientScrollBehaviour } from '@/lib/clientAnimation'
import { fetchMaterialChanges } from '@/lib/materials'
import { materialsKeys } from '@/lib/queryKeys'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { dateString, inputClass } from '@/lib/utils'
import type { FC, FormEvent } from 'react'
const MaterialHistoryPage: FC = () => {
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const location = useLocation ()
const navigate = useNavigate ()
const query = new URLSearchParams (location.search)
@@ -55,8 +59,9 @@ const MaterialHistoryPage: FC = () => {
const totalPages = data ? Math.ceil (data.count / limit) : 0
useEffect (() => {
document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' })
}, [location.search])
document.querySelector ('table')?.scrollIntoView ({
behavior: clientScrollBehaviour (animationMode) })
}, [animationMode, location.search])
const handleSearch = (event: FormEvent) => {
event.preventDefault ()
+14 -4
ファイルの表示
@@ -12,9 +12,12 @@ import { useDialogue } from '@/components/dialogues/DialogueProvider'
import MainArea from '@/components/layout/MainArea'
import { toast } from '@/components/ui/use-toast'
import { SITE_TITLE } from '@/config'
import { clientAnimationTransition,
clientScrollBehaviour } from '@/lib/clientAnimation'
import { fetchPostChanges, updatePost } from '@/lib/posts'
import { postsKeys, tagsKeys } from '@/lib/queryKeys'
import { fetchTag } from '@/lib/tags'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { cn, dateString, originalCreatedAtString } from '@/lib/utils'
import type { FC, MouseEvent } from 'react'
@@ -37,6 +40,12 @@ const renderDiff = (diff: { current: string | null; prev: string | null }) => (
const PostHistoryPage: FC = () => {
const dialogue = useDialogue ()
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const layoutTransition = clientAnimationTransition (
animationMode,
{ normal: { duration: .2, ease: 'easeOut' as const } },
)
const location = useLocation ()
const query = new URLSearchParams (location.search)
@@ -113,8 +122,9 @@ const PostHistoryPage: FC = () => {
}
useEffect (() => {
document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' })
}, [location.search])
document.querySelector ('table')?.scrollIntoView ({
behavior: clientScrollBehaviour (animationMode) })
}, [animationMode, location.search])
const layoutIds: string[] = []
@@ -198,8 +208,8 @@ const PostHistoryPage: FC = () => {
rowSpan={rowsCnt}>
<PrefetchLink to={`/posts/${ change.postId }`}>
<motion.div
layoutId={layoutId}
transition={{ layout: { duration: .2, ease: 'easeOut' } }}>
layoutId={animationMode === 'off' ? undefined : layoutId}
transition={{ layout: layoutTransition }}>
<img src={change.thumbnail.current
|| change.thumbnailBase.current
|| undefined}
+19 -8
ファイルの表示
@@ -14,6 +14,8 @@ import Pagination from '@/components/common/Pagination'
import TagInput from '@/components/common/TagInput'
import MainArea from '@/components/layout/MainArea'
import { SITE_TITLE } from '@/config'
import { clientAnimationTransition,
clientScrollBehaviour } from '@/lib/clientAnimation'
import { fetchPosts } from '@/lib/posts'
import { postsKeys } from '@/lib/queryKeys'
import {
@@ -24,6 +26,7 @@ import {
LIST_LIMIT_OPTIONS,
setClientListSettings,
} from '@/lib/settings'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { useKeyboardShortcuts } from '@/lib/useKeyboardShortcuts'
import { dateString, inputClass, originalCreatedAtString } from '@/lib/utils'
@@ -67,6 +70,13 @@ const parseOrder = (value: string | null): FetchPostsOrder | null => {
const PostSearchPage: FC = () => {
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const thumbnailTransition = clientAnimationTransition (
animationMode,
{ normal: { type: 'spring', stiffness: 500, damping: 40, mass: .5 },
reduced: { duration: .08, ease: 'linear' as const } },
)
const location = useLocation ()
const navigate = useNavigate ()
@@ -141,9 +151,11 @@ const PostSearchPage: FC = () => {
setUpdatedFrom (qUpdatedFrom)
setUpdatedTo (qUpdatedTo)
document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' })
}, [location.search, qCreatedFrom, qCreatedTo, qMatch, qOriginalCreatedFrom,
qOriginalCreatedTo, qTags, qTitle, qUpdatedFrom, qUpdatedTo, qURL])
document.querySelector ('table')?.scrollIntoView ({
behavior: clientScrollBehaviour (animationMode) })
}, [animationMode, location.search, qCreatedFrom, qCreatedTo, qMatch,
qOriginalCreatedFrom, qOriginalCreatedTo, qTags, qTitle, qUpdatedFrom,
qUpdatedTo, qURL])
const search = async () => {
const qs = new URLSearchParams ()
@@ -400,11 +412,10 @@ const PostSearchPage: FC = () => {
<td className="p-2">
<PrefetchLink to={`/posts/${ row.id }`} title={row.title || undefined}>
<motion.div
layoutId={`page-${ row.id }`}
transition={{ type: 'spring',
stiffness: 500,
damping: 40,
mass: .5 }}>
layoutId={animationMode === 'off'
? undefined
: `page-${ row.id }`}
transition={thumbnailTransition}>
<img src={row.thumbnail || row.thumbnailBase || undefined}
alt={row.title || row.url}
title={row.title || row.url || undefined}
+7 -2
ファイルの表示
@@ -17,8 +17,10 @@ import { toast } from '@/components/ui/use-toast'
import { SITE_TITLE } from '@/config'
import { apiPut } from '@/lib/api'
import { extractValidationError } from '@/lib/apiErrors'
import { clientScrollBehaviour } from '@/lib/clientAnimation'
import { tagsKeys } from '@/lib/queryKeys'
import { fetchNicoTags } from '@/lib/tags'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { cn, dateString, inputClass } from '@/lib/utils'
import { canEditContent } from '@/lib/users'
@@ -38,6 +40,8 @@ const setIf = (qs: URLSearchParams, key: string, value: string) => {
const NicoTagListPage: FC<Props> = ({ user }) => {
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const dialogue = useDialogue ()
const location = useLocation ()
const navigate = useNavigate ()
@@ -152,8 +156,9 @@ const NicoTagListPage: FC<Props> = ({ user }) => {
setLinkStatus (qLinkStatus)
setEditingId (null)
document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' })
}, [location.search, qLinkedTag, qLinkStatus, qName])
document.querySelector ('table')?.scrollIntoView ({
behavior: clientScrollBehaviour (animationMode) })
}, [animationMode, location.search, qLinkedTag, qLinkStatus, qName])
useEffect (() => {
if (!(data))
+7 -2
ファイルの表示
@@ -11,8 +11,10 @@ import { toast } from '@/components/ui/use-toast'
import { SITE_TITLE } from '@/config'
import { CATEGORY_NAMES } from '@/consts'
import { apiPut } from '@/lib/api'
import { clientScrollBehaviour } from '@/lib/clientAnimation'
import { postsKeys, tagsKeys } from '@/lib/queryKeys'
import { fetchTagChanges } from '@/lib/tags'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { cn, dateString } from '@/lib/utils'
import type { FC } from 'react'
@@ -43,6 +45,8 @@ const renderStateDiff = (diff: { current: string | null; prev: string | null })
const TagHistoryPage: FC = () => {
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const location = useLocation ()
const query = new URLSearchParams (location.search)
const id = query.get ('id')
@@ -58,8 +62,9 @@ const TagHistoryPage: FC = () => {
const qc = useQueryClient ()
useEffect (() => {
document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' })
}, [location.search])
document.querySelector ('table')?.scrollIntoView ({
behavior: clientScrollBehaviour (animationMode) })
}, [animationMode, location.search])
return (
<MainArea>
+9 -3
ファイルの表示
@@ -13,6 +13,7 @@ import Pagination from '@/components/common/Pagination'
import MainArea from '@/components/layout/MainArea'
import { SITE_TITLE } from '@/config'
import { CATEGORIES, CATEGORY_NAMES } from '@/consts'
import { clientScrollBehaviour } from '@/lib/clientAnimation'
import { tagsKeys } from '@/lib/queryKeys'
import {
DEFAULT_POST_LIST_LIMIT,
@@ -22,6 +23,7 @@ import {
LIST_LIMIT_OPTIONS,
setClientListSettings,
} from '@/lib/settings'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { useKeyboardShortcuts } from '@/lib/useKeyboardShortcuts'
import { fetchTags } from '@/lib/tags'
import { dateString, inputClass } from '@/lib/utils'
@@ -71,6 +73,8 @@ const parseOrder = (value: string | null): FetchTagsOrder | null => {
const TagListPage: FC = () => {
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const location = useLocation ()
const navigate = useNavigate ()
@@ -145,9 +149,11 @@ const TagListPage: FC = () => {
setUpdatedTo (qUpdatedTo)
setDeprecated (qDeprecated)
document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' })
}, [location.search, qCategory, qCreatedFrom, qCreatedTo, qName, qPostCountGTE,
qPostCountLTE, qUpdatedFrom, qUpdatedTo, qDeprecated])
document.querySelector ('table')?.scrollIntoView ({
behavior: clientScrollBehaviour (animationMode) })
}, [animationMode, location.search, qCategory, qCreatedFrom, qCreatedTo,
qName, qPostCountGTE, qPostCountLTE, qUpdatedFrom, qUpdatedTo,
qDeprecated])
const handleSearch = (e: FormEvent) => {
e.preventDefault ()
+15 -4
ファイルの表示
@@ -15,6 +15,8 @@ import { Button } from '@/components/ui/button'
import { SITE_TITLE } from '@/config'
import { CATEGORIES, CATEGORY_NAMES } from '@/consts'
import { apiDelete, apiGet, apiPatch, apiPost, apiPut, isApiError } from '@/lib/api'
import { clientAnimationTransition,
clientScrollBehaviour } from '@/lib/clientAnimation'
import { fetchPost } from '@/lib/posts'
import {
getClientTheatreLayoutMode,
@@ -22,6 +24,7 @@ import {
setClientTheatreLayoutMode,
setClientTheatreTagFlow,
} from '@/lib/settings'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { canEditContent } from '@/lib/users'
import { cn, dateString, inputClass } from '@/lib/utils'
import { useValidationErrors } from '@/lib/useValidationErrors'
@@ -212,6 +215,12 @@ type Props = { user: User | null }
const TheatreDetailPage: FC<Props> = ({ user }: Props) => {
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const layoutTransition = clientAnimationTransition (
animationMode,
{ normal: { duration: .2, ease: 'easeOut' as const } },
)
const { id } = useParams ()
const dialogue = useDialogue ()
@@ -618,7 +627,9 @@ const TheatreDetailPage: FC<Props> = ({ user }: Props) => {
clearValidationErrors ()
await apiPost (`/theatres/${ id }/comments`, { content })
setContent ('')
commentsRef.current?.scrollTo ({ top: 0, behavior: 'smooth' })
commentsRef.current?.scrollTo ({
top: 0,
behavior: clientScrollBehaviour (animationMode) })
}
catch (error)
{
@@ -835,8 +846,8 @@ const TheatreDetailPage: FC<Props> = ({ user }: Props) => {
return (
<motion.div
layout="position"
transition={{ layout: { duration: .2, ease: 'easeOut' } }}
layout={animationMode === 'off' ? false : 'position'}
transition={{ layout: layoutTransition }}
className="min-h-0 flex-1 overflow-y-auto bg-zinc-50 text-zinc-950
md:overflow-hidden dark:bg-zinc-950 dark:text-zinc-50">
<Helmet>
@@ -860,7 +871,7 @@ const TheatreDetailPage: FC<Props> = ({ user }: Props) => {
</SidebarComponent>)}
<motion.main
layout="position"
layout={animationMode === 'off' ? false : 'position'}
className="order-1 min-w-0 flex-1 space-y-4 md:order-none
md:min-w-[360px] md:overflow-y-auto">
<div className="space-y-4">